diff --git a/examples/browser-app.ts b/examples/browser-app.ts
index 3b1043f..ee6a51e 100644
--- a/examples/browser-app.ts
+++ b/examples/browser-app.ts
@@ -22,8 +22,9 @@ import runRandomGraph from "./random-graph/src/standalone";
import runRandomGraphDistributed from "./random-graph-distributed/src/standalone";
import runSvgPreRendered from "./svg/src/standalone";
import runMulticore from "./multicore/src/multicore";
+import runFlowchart from "./flowchart/src/standalone";
-const appDiv = document.getElementById('sprotty-app')
+const appDiv = document.getElementById('sprotty-app');
if (appDiv) {
const appMode = appDiv.getAttribute('data-app');
if (appMode === 'circlegraph')
@@ -38,6 +39,8 @@ if (appDiv) {
runSvgPreRendered();
else if (appMode === 'multicore')
runMulticore();
+ else if (appMode === 'flowchart')
+ runFlowchart();
else
throw new Error('Dunno what to do :-(');
}
diff --git a/examples/flowchart/css/diagram.css b/examples/flowchart/css/diagram.css
new file mode 100644
index 0000000..7751e8d
--- /dev/null
+++ b/examples/flowchart/css/diagram.css
@@ -0,0 +1,102 @@
+/********************************************************************************
+ * Copyright (c) 2024 TypeFox and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+
+:root {
+ --black: #000;
+ --white: #fff;
+ --stroke-normal: 2;
+ --stroke-medium: 3;
+ --stroke-bold: 5;
+ --red-500: #f44336;
+ --yellow-500: #ffeb3b;
+ --amber-500: #ffc107;
+}
+
+/* graph */
+.sprotty-graph {
+ font-size: 15pt;
+}
+
+/* nodes */
+.sprotty-node {
+ stroke: var(--black);
+ stroke-width: var(--stroke-normal);
+}
+
+.sprotty-node.terminal {
+ fill: var(--red-500);
+}
+
+.sprotty-node.process {
+ fill: var(--yellow-500);
+}
+
+.sprotty-node.decision {
+ fill: var(--amber-500);
+}
+
+.sprotty-node.selected {
+ stroke-width: var(--stroke-bold);
+}
+
+.sprotty-node.mouseover:not(.selected) {
+ stroke-width: var(--stroke-medium);
+}
+
+/* labels */
+.sprotty-label {
+ stroke-width: 0;
+ fill: var(--black);
+ font-size: 100%;
+ font-weight: inherit;
+ text-anchor: middle;
+}
+
+.edge-label-background {
+ fill: var(--white);
+ stroke: none;
+}
+
+/* edges */
+.sprotty-edge {
+ fill: none;
+ stroke: var(--black);
+ stroke-width: var(--stroke-normal);
+}
+
+.sprotty-edge.mouseover:not(.selected) {
+ stroke-width: var(--stroke-medium);
+}
+
+.sprotty-edge.selected {
+ stroke: #844;
+ stroke-width: var(--stroke-bold);
+}
+
+.sprotty-edge>.sprotty-routing-handle {
+ fill: #884;
+ stroke: none;
+ z-index: 1000;
+}
+
+.sprotty-edge>.sprotty-routing-handle[data-kind='line'] {
+ opacity: 0.35;
+}
+
+.arrowhead {
+ fill: var(--black);
+ stroke: var(--black);
+}
diff --git a/examples/flowchart/css/page.css b/examples/flowchart/css/page.css
new file mode 100644
index 0000000..8821149
--- /dev/null
+++ b/examples/flowchart/css/page.css
@@ -0,0 +1,50 @@
+/********************************************************************************
+ * Copyright (c) 2024 TypeFox and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+
+
+.footer {
+ margin-top: 10px;
+ text-align: right;
+ font-size: 10px;
+ color: #888;
+
+ display: flex;
+ justify-content: space-between;
+}
+
+.shortcuts {
+ text-align: left;
+}
+
+.copyright {
+ text-align: right;
+}
+
+.help {
+ margin-top: 24px;
+ text-align: right;
+ font-size: 16px;
+ color: #888;
+}
+
+svg {
+ margin-top: 15px;
+ width: 100%;
+ height: 600px;
+ border-style: solid;
+ border-width: 1px;
+ border-color: #bbb;
+}
diff --git a/examples/flowchart/flowchart.html b/examples/flowchart/flowchart.html
new file mode 100644
index 0000000..2fdf76e
--- /dev/null
+++ b/examples/flowchart/flowchart.html
@@ -0,0 +1,57 @@
+
+
+
+
+
+ Sprotty Flowchart Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Sprotty Flowchart Example
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/flowchart/src/data.ts b/examples/flowchart/src/data.ts
new file mode 100644
index 0000000..14f5eac
--- /dev/null
+++ b/examples/flowchart/src/data.ts
@@ -0,0 +1,670 @@
+/********************************************************************************
+ * Copyright (c) 2024 TypeFox and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+
+import { SEdge, SGraph, SLabel, SModelRoot, SNode } from "sprotty-protocol";
+
+export function initializeModel(): SModelRoot {
+ const nodes: SNode[] = [];
+ const edges: SEdge[] = [];
+
+ const node0: SNode = {
+ id: '0',
+ type: 'node:terminal',
+ position: { x: 150, y: 10 },
+ children: [
+ {
+ id: '0_label',
+ text: 'Start',
+ type: 'label',
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 20,
+ paddingRight: 20,
+ }
+ };
+ nodes.push(node0);
+
+ const node1: SNode = {
+ id: '1',
+ type: 'node:process',
+ position: { x: 109.15, y: 153.9 },
+ children: [{
+ id: '1_label',
+ text: 'Patient arrives',
+ type: 'label'
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 20,
+ paddingRight: 20,
+ }
+ };
+ nodes.push(node1);
+
+ const node2: SNode = {
+ id: '2',
+ type: 'node:decision',
+ position: { x: 71.4, y: 297.8 },
+ children: [{
+ id: '2_label',
+ text: 'Registered patient',
+ type: 'label'
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 40,
+ paddingRight: 40,
+ minHeight: 100
+ }
+
+ };
+ nodes.push(node2);
+
+ const node3: SNode = {
+ id: '3',
+ type: 'node:process',
+ position: { x: 102.35, y: 517.8 },
+ children: [{
+ id: '3_label',
+ text: 'Register patient',
+ type: 'label'
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 20,
+ paddingRight: 20,
+ }
+ };
+ nodes.push(node3);
+
+ const node4: SNode = {
+ id: '4',
+ type: 'node:decision',
+ position: { x: 411.1, y: 297.8 },
+ children: [{
+ id: '4_label',
+ text: 'Available nurse',
+ type: 'label'
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 40,
+ paddingRight: 40,
+ minHeight: 100
+ }
+
+ };
+ nodes.push(node4);
+
+ const node5: SNode = {
+ id: '5',
+ type: 'node:process',
+ position: { x: 395.7, y: 517.8},
+ children: [{
+ id: '5_label',
+ text: 'Wait for available nurse',
+ type: 'label'
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 20,
+ paddingRight: 20,
+ }
+
+ };
+ nodes.push(node5);
+
+ const node6: SNode = {
+ id: '6',
+ type: 'node:process',
+ position: { x: 722.3, y: 335.85 },
+ children: [{
+ id: '6_label',
+ text: 'Record health condition',
+ type: 'label'
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 20,
+ paddingRight: 20,
+ }
+
+ };
+ nodes.push(node6);
+
+ const node7: SNode = {
+ id: '7',
+ type: 'node:decision',
+ position: { x: 736, y: 479.75 },
+ children: [{
+ id: '7_label',
+ text: 'Available doctor',
+ type: 'label'
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 40,
+ paddingRight: 40,
+ minHeight: 100
+ }
+
+ };
+ nodes.push(node7);
+
+ const node8: SNode = {
+ id: '8',
+ type: 'node:process',
+ position: { x: 720.5, y: 699.75},
+ children: [{
+ id: '8_label',
+ text: 'Wait for available doctor',
+ type: 'label'
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 20,
+ paddingRight: 20,
+ }
+
+ };
+ nodes.push(node8);
+
+ const node9: SNode = {
+ id: '9',
+ type: 'node:process',
+ position: { x: 1055.7, y: 517.8 },
+ children: [{
+ id: '9_label',
+ text: 'Assign patient to doctor',
+ type: 'label'
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 20,
+ paddingRight: 20,
+ }
+
+ };
+ nodes.push(node9);
+
+ const node10: SNode = {
+ id: '10',
+ type: 'node:decision',
+ position: { x: 1074.9, y: 661.7 },
+ children: [{
+ id: '10_label',
+ text: 'Need follow up',
+ type: 'label'
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 40,
+ paddingRight: 40,
+ minHeight: 100
+ }
+
+ };
+ nodes.push(node10);
+
+ const node11: SNode = {
+ id: '11',
+ type: 'node:decision',
+ position: { x: 1065.75, y: 881.7 },
+ children: [{
+ id: '11_label',
+ text: 'Need medication',
+ type: 'label'
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 40,
+ paddingRight: 40,
+ minHeight: 100
+ }
+
+ };
+ nodes.push(node11);
+
+ const node12: SNode = {
+ id: '12',
+ type: 'node:process',
+ position: { x: 1099.7, y: 1101.7 },
+ children: [{
+ id: '12_label',
+ text: 'Patient leaves',
+ type: 'label'
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 20,
+ paddingRight: 20,
+ }
+
+ };
+ nodes.push(node12);
+
+ const node13: SNode = {
+ id: '13',
+ type: 'node:terminal',
+ position: { x: 1143.25, y: 1245.6 },
+ children: [{
+ id: '13_label',
+ text: 'End',
+ type: 'label'
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 20,
+ paddingRight: 20,
+ }
+
+ };
+ nodes.push(node13);
+
+ const node14: SNode = {
+ id: '14',
+ type: 'node:process',
+ position: { x: 1385.4, y: 699.75 },
+ children: [{
+ id: '14_label',
+ text: 'Arrange appointment',
+ type: 'label'
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 20,
+ paddingRight: 20,
+ }
+
+ };
+ nodes.push(node14);
+
+ const node15: SNode = {
+ id: '15',
+ type: 'node:process',
+ position: { x: 1386.1, y: 919.75 },
+ children: [{
+ id: '15_label',
+ text: 'Prescribe medication',
+ type: 'label'
+ }],
+ layout: 'stack',
+ layoutOptions: {
+ hAlign: 'center',
+ vAlign: 'center',
+ paddingTop: 10,
+ paddingBottom: 10,
+ paddingLeft: 20,
+ paddingRight: 20,
+ }
+
+ };
+ nodes.push(node15);
+
+ const edge0: SEdge = {
+ id: '0-1',
+ type: 'edge',
+ sourceId: '0',
+ targetId: '1',
+ routerKind: 'manhattan'
+ };
+ edges.push(edge0);
+
+ const edge1: SEdge = {
+ id: '1-2',
+ type: 'edge',
+ sourceId: '1',
+ targetId: '2',
+ routerKind: 'manhattan',
+ };
+ edges.push(edge1);
+
+ const edge2: SEdge = {
+ id: '2-3',
+ type: 'edge',
+ sourceId: '2',
+ targetId: '3',
+ routerKind: 'manhattan',
+ children: [{
+ id: '2-3_label',
+ text: 'No',
+ type: 'label:edge',
+ edgePlacement: {
+ position: 0.5,
+ side: 'on',
+ rotate: false
+ }
+ }]
+ };
+ edges.push(edge2);
+
+ const edge3: SEdge = {
+ id: '3-2',
+ type: 'edge',
+ sourceId: '3',
+ targetId: '2',
+ routerKind: 'manhattan',
+ routingPoints: [{ x: 52.35, y: 539.75 }, { x: 52.35, y: 357.8 }],
+ };
+ edges.push(edge3);
+
+ const edge4: SEdge = {
+ id: '2-4',
+ type: 'edge',
+ sourceId: '2',
+ targetId: '4',
+ routerKind: 'manhattan',
+ children: [{
+ id: '2-4_label',
+ text: 'Yes',
+ type: 'label:edge',
+ edgePlacement: {
+ position: 0.5,
+ side: 'on',
+ rotate: false
+ }
+ }]
+ };
+ edges.push(edge4);
+
+ const edge5: SEdge = {
+ id: '4-5',
+ type: 'edge',
+ sourceId: '4',
+ targetId: '5',
+ routerKind: 'manhattan',
+ children: [{
+ id: '4-5_label',
+ text: 'No',
+ type: 'label:edge',
+ edgePlacement: {
+ position: 0.5,
+ side: 'on',
+ rotate: false
+ }
+ }]
+ };
+ edges.push(edge5);
+
+ const edge6: SEdge = {
+ id: '5-4',
+ type: 'edge',
+ sourceId: '5',
+ targetId: '4',
+ routerKind: 'manhattan',
+ routingPoints: [{ x: 362, y: 539.75 }, { x: 362, y: 357.8 }],
+ };
+ edges.push(edge6);
+
+ const edge7: SEdge = {
+ id: '4-6',
+ type: 'edge',
+ sourceId: '4',
+ targetId: '6',
+ routerKind: 'manhattan',
+ children: [{
+ id: '4-6_label',
+ text: 'Yes',
+ type: 'label:edge',
+ edgePlacement: {
+ position: 0.5,
+ side: 'on',
+ rotate: false
+ }
+ }]
+ };
+ edges.push(edge7);
+
+ const edge8: SEdge = {
+ id: '6-7',
+ type: 'edge',
+ sourceId: '6',
+ targetId: '7',
+ routerKind: 'manhattan',
+ };
+ edges.push(edge8);
+
+ const edge9: SEdge = {
+ id: '7-8',
+ type: 'edge',
+ sourceId: '7',
+ targetId: '8',
+ routerKind: 'manhattan',
+ children: [{
+ id: '7-8_label',
+ text: 'No',
+ type: 'label:edge',
+ edgePlacement: {
+ position: 0.5,
+ side: 'on',
+ rotate: false
+ }
+ }]
+ };
+ edges.push(edge9);
+
+ const edge10: SEdge = {
+ id: '8-7',
+ type: 'edge',
+ sourceId: '8',
+ targetId: '7',
+ routerKind: 'manhattan',
+ routingPoints: [{ x: 670.5, y: 721.7 }, { x: 670.5, y: 539.75 }],
+ };
+ edges.push(edge10);
+
+ const edge11: SEdge = {
+ id: '7-9',
+ type: 'edge',
+ sourceId: '7',
+ targetId: '9',
+ routerKind: 'manhattan',
+ children: [{
+ id: '7-9_label',
+ text: 'Yes',
+ type: 'label:edge',
+ edgePlacement: {
+ position: 0.5,
+ side: 'on',
+ rotate: false
+ }
+ }]
+ };
+ edges.push(edge11);
+
+ const edge12: SEdge = {
+ id: '9-10',
+ type: 'edge',
+ sourceId: '9',
+ targetId: '10',
+ routerKind: 'manhattan',
+ };
+ edges.push(edge12);
+
+ const edge13: SEdge = {
+ id: '10-11',
+ type: 'edge',
+ sourceId: '10',
+ targetId: '11',
+ routerKind: 'manhattan',
+ children: [{
+ id: '10-11_label',
+ text: 'No',
+ type: 'label:edge',
+ edgePlacement: {
+ position: 0.5,
+ side: 'on',
+ rotate: false
+ }
+ }]
+ };
+ edges.push(edge13);
+
+ const edge14: SEdge = {
+ id: '11-12',
+ type: 'edge',
+ sourceId: '11',
+ targetId: '12',
+ routerKind: 'manhattan',
+ children: [{
+ id: '11-12_label',
+ text: 'No',
+ type: 'label:edge',
+ edgePlacement: {
+ position: 0.5,
+ side: 'on',
+ rotate: false
+ }
+ }]
+ };
+ edges.push(edge14);
+
+ const edge15: SEdge = {
+ id: '12-13',
+ type: 'edge',
+ sourceId: '12',
+ targetId: '13',
+ routerKind: 'manhattan',
+ };
+ edges.push(edge15);
+
+ const edge16: SEdge = {
+ id: '10-14',
+ type: 'edge',
+ sourceId: '10',
+ targetId: '14',
+ routerKind: 'manhattan',
+ children: [{
+ id: '10-14_label',
+ text: 'Yes',
+ type: 'label:edge',
+ edgePlacement: {
+ position: 0.5,
+ side: 'on',
+ rotate: false
+ }
+ }]
+ };
+ edges.push(edge16);
+
+ const edge17: SEdge = {
+ id: '14-11',
+ type: 'edge',
+ sourceId: '14',
+ targetId: '11',
+ routerKind: 'manhattan',
+ routingPoints: [{ x: 1498.3, y: 831.7 }, { x: 1180.15, y: 831.7 }],
+ };
+ edges.push(edge17);
+
+ const edge18: SEdge = {
+ id: '11-15',
+ type: 'edge',
+ sourceId: '11',
+ targetId: '15',
+ routerKind: 'manhattan',
+ children: [{
+ id: '11-15_label',
+ text: 'Yes',
+ type: 'label:edge',
+ edgePlacement: {
+ position: 0.5,
+ side: 'on',
+ rotate: false
+ }
+ }]
+ };
+ edges.push(edge18);
+
+ const edge19: SEdge = {
+ id: '15-12',
+ type: 'edge',
+ sourceId: '15',
+ targetId: '12',
+ routerKind: 'manhattan',
+ routingPoints: [{ x: 1498.3, y: 1051.7 }, { x: 1180.15, y: 1051.7 }],
+ };
+ edges.push(edge19);
+
+ const graph: SGraph = {
+ id: 'graph',
+ type: 'graph',
+ children: [...nodes, ...edges],
+ };
+
+ return graph;
+}
diff --git a/examples/flowchart/src/di.config.ts b/examples/flowchart/src/di.config.ts
new file mode 100644
index 0000000..4221e8a
--- /dev/null
+++ b/examples/flowchart/src/di.config.ts
@@ -0,0 +1,76 @@
+/********************************************************************************
+ * Copyright (c) 2024 TypeFox and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+
+import { Container, ContainerModule } from "inversify";
+import {
+ CircularNodeView,
+ ConsoleLogger,
+ LogLevel,
+ SCompartmentImpl,
+ SCompartmentView,
+ SEdgeImpl,
+ SGraphImpl,
+ SGraphView,
+ SLabelImpl,
+ SLabelView,
+ SNodeImpl,
+ SPortImpl,
+ SRoutingHandleImpl,
+ SRoutingHandleView,
+ TYPES,
+ configureModelElement,
+ configureViewerOptions,
+ editLabelFeature,
+ loadDefaultModules,
+ moveFeature,
+ selectFeature
+} from "sprotty";
+import { FlowchartModelSource } from "./model-source";
+import { DecisionNodeView, EdgeLabel, EdgeWithArrow, ProcessNodeView, TerminalNodeView } from "./views";
+
+export default (containerId: string) => {
+ require('../css/diagram.css');
+
+ const flowchartModule = new ContainerModule((bind, unbind, isBound, rebind) => {
+ bind(TYPES.ModelSource).to(FlowchartModelSource).inSingletonScope();
+ rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope();
+ rebind(TYPES.LogLevel).toConstantValue(LogLevel.log);
+
+ const context = { bind, unbind, isBound, rebind };
+
+ configureModelElement(context, 'graph', SGraphImpl, SGraphView);
+ configureModelElement(context, 'node:terminal', SNodeImpl, TerminalNodeView);
+ configureModelElement(context, 'node:process', SNodeImpl, ProcessNodeView);
+ configureModelElement(context, 'node:decision', SNodeImpl, DecisionNodeView);
+ configureModelElement(context, 'label', SLabelImpl, SLabelView, {enable: [editLabelFeature]});
+ configureModelElement(context, 'label:edge', SLabelImpl, EdgeLabel, {enable: [moveFeature, selectFeature, editLabelFeature]});
+ configureModelElement(context, 'edge', SEdgeImpl, EdgeWithArrow);
+ configureModelElement(context, 'routing-point', SRoutingHandleImpl, SRoutingHandleView);
+ configureModelElement(context, 'volatile-routing-point', SRoutingHandleImpl, SRoutingHandleView);
+ configureModelElement(context, 'port:in', SPortImpl, CircularNodeView);
+ configureModelElement(context, 'port:out', SPortImpl, CircularNodeView);
+ configureModelElement(context, 'compartment', SCompartmentImpl, SCompartmentView);
+
+ configureViewerOptions(context, {
+ needsClientLayout: true,
+ });
+ });
+
+ const container = new Container();
+ loadDefaultModules(container);
+ container.load(flowchartModule);
+ return container;
+};
diff --git a/examples/flowchart/src/model-source.ts b/examples/flowchart/src/model-source.ts
new file mode 100644
index 0000000..44ea589
--- /dev/null
+++ b/examples/flowchart/src/model-source.ts
@@ -0,0 +1,27 @@
+/********************************************************************************
+ * Copyright (c) 2024 TypeFox and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+
+import { injectable } from "inversify";
+import { LocalModelSource } from "sprotty";
+import { initializeModel } from "./data";
+
+@injectable()
+export class FlowchartModelSource extends LocalModelSource {
+ constructor() {
+ super();
+ this.currentRoot = initializeModel();
+ }
+}
diff --git a/examples/flowchart/src/standalone.ts b/examples/flowchart/src/standalone.ts
new file mode 100644
index 0000000..3f1f832
--- /dev/null
+++ b/examples/flowchart/src/standalone.ts
@@ -0,0 +1,38 @@
+/********************************************************************************
+ * Copyright (c) 2024 TypeFox and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+
+import { LocalModelSource, TYPES } from 'sprotty';
+import { BringToFrontAction, FitToScreenAction } from 'sprotty-protocol';
+import createContainer from './di.config';
+
+export default async function runFlowchart() {
+ const container = createContainer('sprotty');
+ const modelSource = container.get(TYPES.ModelSource);
+ await modelSource.updateModel();
+
+ const bringToFrontAction: BringToFrontAction = {
+ kind: 'bringToFront',
+ elementIDs: ['2-4', '10-11', '11-12']
+ };
+
+ const fitToScreenAction: FitToScreenAction = {
+ kind: 'fit',
+ animate: true,
+ elementIds: [],
+ padding: 20
+ };
+ await modelSource.actionDispatcher.dispatchAll([fitToScreenAction, bringToFrontAction]);
+}
diff --git a/examples/flowchart/src/views.tsx b/examples/flowchart/src/views.tsx
new file mode 100644
index 0000000..8740763
--- /dev/null
+++ b/examples/flowchart/src/views.tsx
@@ -0,0 +1,118 @@
+/********************************************************************************
+ * Copyright (c) 2024 TypeFox and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+/** @jsx svg */
+import { PolylineEdgeView, SEdgeImpl, SLabelImpl, isEdgeLayoutable, setAttr, svg } from 'sprotty';
+
+import { injectable } from "inversify";
+import { VNode } from "snabbdom";
+import { IViewArgs, RenderingContext, SShapeElementImpl, ShapeView } from "sprotty";
+import { Hoverable, Point, Selectable, getSubType, toDegrees } from "sprotty-protocol";
+
+@injectable()
+export class TerminalNodeView extends ShapeView {
+ override render(node: Readonly, context: RenderingContext, args?: IViewArgs): VNode | undefined {
+ if (!this.isVisible(node, context)) {
+ return undefined;
+ }
+
+ return
+
+ {/* {label.text} */}
+ {context.renderChildren(node)}
+ ;
+ }
+}
+
+@injectable()
+export class ProcessNodeView extends ShapeView {
+ override render(node: Readonly, context: RenderingContext, args?: IViewArgs): VNode | undefined {
+ if (!this.isVisible(node, context)) {
+ return undefined;
+ }
+
+ return
+
+ {context.renderChildren(node)}
+ ;
+ }
+}
+
+@injectable()
+export class DecisionNodeView extends ShapeView {
+ override render(node: Readonly, context: RenderingContext, args?: IViewArgs): VNode | undefined {
+ const width = node.size.width;
+ const height = node.size.height;
+
+ if (!this.isVisible(node, context)) {
+ return undefined;
+ }
+
+ return
+
+ {context.renderChildren(node)}
+ ;
+ }
+}
+
+@injectable()
+export class EdgeLabel extends ShapeView {
+ override render(label: Readonly, context: RenderingContext, args?: IViewArgs | undefined): VNode | undefined {
+ if (!isEdgeLayoutable(label) && !this.isVisible(label, context)) {
+ return undefined;
+ }
+ const vnode = {label.text} ;
+ const subType = getSubType(label);
+ if (subType) {
+ setAttr(vnode, 'class', subType);
+ }
+
+ return
+
+ {vnode}
+ ;
+ }
+}
+
+@injectable()
+export class EdgeWithArrow extends PolylineEdgeView {
+
+ protected override renderAdditionals(edge: SEdgeImpl, segments: Point[], context: RenderingContext): VNode[] {
+ const p1 = segments[segments.length - 1];
+ const p2 = segments[segments.length - 2];
+
+ return [
+
+ ];
+ }
+
+ angle(x0: Point, x1: Point): number {
+ return toDegrees(Math.atan2(x1.y - x0.y, x1.x - x0.x));
+ }
+}
diff --git a/examples/index.html b/examples/index.html
index 1d9e2a5..0a418e4 100644
--- a/examples/index.html
+++ b/examples/index.html
@@ -28,6 +28,8 @@ Without Server
Multicore:
Shows a multi-core chip. The color of the cores changes with the code they execute.
If you zoom in, the communication channels between the cores appear.
+ Flowchart:
+ A flowchart with custom views for nodes and edges. The labels on nodes and edges are editable and the nodes and edges are moveable.
With Server