maxGraph/packages/html/stories/SwimLanes.stories.js

568 lines
14 KiB
JavaScript

import mxgraph from '@mxgraph/core';
import { globalTypes } from '../.storybook/preview';
export default {
title: 'Layouts/SwimLanes',
argTypes: {
...globalTypes
}
};
const Template = ({ label, ...args }) => {
const {
mxEditor,
mxConnectionHandler,
mxImage,
mxPerimeter,
mxPoint,
mxConstants,
mxCloneUtils,
mxEdgeStyle,
mxEvent,
mxSwimlaneManager,
mxStackLayout,
mxLayoutManager
} = mxgraph;
const container = document.createElement('div');
container.style.position = 'relative';
container.style.overflow = 'hidden';
container.style.width = `${args.width}px`;
container.style.height = `${args.height}px`;
container.style.background = 'url(/images/grid.gif)';
container.style.cursor = 'default';
// Defines an icon for creating new connections in the connection handler.
// This will automatically disable the highlighting of the source vertex.
mxConnectionHandler.prototype.connectImage = new mxImage(
'images/connector.gif',
16,
16
);
// Creates a wrapper editor around a new graph inside
// the given container using an XML config for the
// keyboard bindings
// const config = mxUtils
// .load('editors/config/keyhandler-commons.xml')
// .getDocumentElement();
// const editor = new mxEditor(config);
const editor = new mxEditor(null);
editor.setGraphContainer(container);
const { graph } = editor;
const model = graph.getModel();
// Auto-resizes the container
graph.border = 80;
graph.getView().translate = new mxPoint(graph.border / 2, graph.border / 2);
graph.setResizeContainer(true);
graph.graphHandler.setRemoveCellsFromParent(false);
// Changes the default vertex style in-place
let style = graph.getStylesheet().getDefaultVertexStyle();
style.shape = mxConstants.SHAPE_SWIMLANE;
style.verticalAlign = 'middle';
style.labelBackgroundColor = 'white';
style.fontSize = 11;
style.startSize = 22;
style.horizontal = false;
style.fontColor = 'black';
style.strokeColor = 'black';
delete style.fillColor;
style = mxCloneUtils.clone(style);
style.shape = mxConstants.SHAPE_RECTANGLE;
style.fontSize = 10;
style.rounded = true;
style.horizontal = true;
style.verticalAlign = 'middle';
delete style.startSize;
style.labelBackgroundColor = 'none';
graph.getStylesheet().putCellStyle('process', style);
style = mxCloneUtils.clone(style);
style.shape = mxConstants.SHAPE_ELLIPSE;
style.perimiter = mxPerimeter.EllipsePerimeter;
delete style.rounded;
graph.getStylesheet().putCellStyle('state', style);
style = mxCloneUtils.clone(style);
style.shape = mxConstants.SHAPE_RHOMBUS;
style.perimiter = mxPerimeter.RhombusPerimeter;
style.verticalAlign = 'top';
style.spacingTop = 40;
style.spacingRight = 64;
graph.getStylesheet().putCellStyle('condition', style);
style = mxCloneUtils.clone(style);
style.shape = mxConstants.SHAPE_DOUBLE_ELLIPSE;
style.perimiter = mxPerimeter.EllipsePerimeter;
style.spacingTop = 28;
style.fontSize = 14;
style.fontStyle = 1;
delete style.spacingRight;
graph.getStylesheet().putCellStyle('end', style);
style = graph.getStylesheet().getDefaultEdgeStyle();
style.edge = mxEdgeStyle.ElbowConnector;
style.endArrow = mxConstants.ARROW_BLOCK;
style.rounded = true;
style.fontColor = 'black';
style.strokeColor = 'black';
style = mxCloneUtils.clone(style);
style.dashed = true;
style.endArrow = mxConstants.ARROW_OPEN;
style.startArrow = mxConstants.ARROW_OVAL;
graph.getStylesheet().putCellStyle('crossover', style);
// Installs double click on middle control point and
// changes style of edges between empty and this value
graph.alternateEdgeStyle = 'elbow=vertical';
// Adds automatic layout and various switches if the
// graph is enabled
if (graph.isEnabled()) {
// Allows new connections but no dangling edges
graph.setConnectable(true);
graph.setAllowDanglingEdges(false);
// End-states are no valid sources
const previousIsValidSource = graph.isValidSource;
graph.isValidSource = function(cell) {
if (previousIsValidSource.apply(this, arguments)) {
const style = cell.getStyle();
return (
style == null || !(style == 'end' || style.indexOf('end') == 0)
);
}
return false;
};
// Start-states are no valid targets, we do not
// perform a call to the superclass function because
// this would call isValidSource
// Note: All states are start states in
// the example below, so we use the state
// style below
graph.isValidTarget = function(cell) {
const style = cell.getStyle();
return (
!cell.isEdge() &&
!this.isSwimlane(cell) &&
(style == null || !(style == 'state' || style.indexOf('state') == 0))
);
};
// Allows dropping cells into new lanes and
// lanes into new pools, but disallows dropping
// cells on edges to split edges
graph.setDropEnabled(true);
graph.setSplitEnabled(false);
// Returns true for valid drop operations
graph.isValidDropTarget = function(target, cells, evt) {
if (this.isSplitEnabled() && this.isSplitTarget(target, cells, evt)) {
return true;
}
const model = this.getModel();
let lane = false;
let pool = false;
let cell = false;
// Checks if any lanes or pools are selected
for (let i = 0; i < cells.length; i++) {
const tmp = cells[i].getParent();
lane = lane || this.isPool(tmp);
pool = pool || this.isPool(cells[i]);
cell = cell || !(lane || pool);
}
return (
!pool &&
cell != lane &&
((lane && this.isPool(target)) ||
(cell && this.isPool(target.getParent())))
);
};
// Adds new method for identifying a pool
graph.isPool = function(cell) {
const model = this.getModel();
const parent = cell.getParent();
return parent != null && parent.getParent() == model.getRoot();
};
// Keeps widths on collapse/expand
const foldingHandler = function(sender, evt) {
const cells = evt.getProperty('cells');
for (let i = 0; i < cells.length; i++) {
const geo = cells[i].getGeometry();
if (geo.alternateBounds != null) {
geo.width = geo.alternateBounds.width;
}
}
};
graph.addListener(mxEvent.FOLD_CELLS, foldingHandler);
}
// Changes swimlane orientation while collapsed
const getStyle = function() {
// TODO super cannot be used here
// let style = super.getStyle();
let style;
if (this.isCollapsed()) {
if (style != null) {
style += ';';
} else {
style = '';
}
style += 'horizontal=1;align=left;spacingLeft=14;';
}
return style;
};
// Applies size changes to siblings and parents
new mxSwimlaneManager(graph);
// Creates a stack depending on the orientation of the swimlane
const layout = new mxStackLayout(graph, false);
// Makes sure all children fit into the parent swimlane
layout.resizeParent = true;
// Applies the size to children if parent size changes
layout.fill = true;
// Only update the size of swimlanes
layout.isVertexIgnored = function(vertex) {
return !graph.isSwimlane(vertex);
};
// Keeps the lanes and pools stacked
const layoutMgr = new mxLayoutManager(graph);
layoutMgr.getLayout = function(cell) {
if (
!cell.isEdge() &&
cell.getChildCount() > 0 &&
(cell.getParent() == model.getRoot() || graph.isPool(cell))
) {
layout.fill = graph.isPool(cell);
return layout;
}
return null;
};
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
const parent = graph.getDefaultParent();
const insertVertex = options => {
const v = graph.insertVertex(options);
v.getStyle = getStyle;
return v;
};
const insertEdge = options => {
const e = graph.insertEdge(options);
e.getStyle = getStyle;
return e;
};
// Adds cells to the model in a single step
graph.batchUpdate(() => {
const pool1 = insertVertex({
parent,
value: 'Pool 1',
position: [0, 0],
size: [640, 0],
});
pool1.setConnectable(false);
const lane1a = insertVertex({
parent: pool1,
value: 'Lane A',
position: [0, 0],
size: [640, 110],
});
lane1a.setConnectable(false);
const lane1b = insertVertex({
parent: pool1,
value: 'Lane B',
position: [0, 0],
size: [640, 110],
});
lane1b.setConnectable(false);
const pool2 = insertVertex({
parent,
value: 'Pool 2',
position: [0, 0],
size: [640, 0],
});
pool2.setConnectable(false);
const lane2a = insertVertex({
parent: pool2,
value: 'Lane A',
position: [0, 0],
size: [640, 140],
});
lane2a.setConnectable(false);
const lane2b = insertVertex({
parent: pool2,
value: 'Lane B',
position: [0, 0],
size: [640, 110],
});
lane2b.setConnectable(false);
const start1 = insertVertex({
parent: lane1a,
position: [40, 40],
size: [30, 30],
style: 'state',
});
const end1 = insertVertex({
parent: lane1a,
value: 'A',
position: [560, 40],
size: [30, 30],
style: 'end',
});
const step1 = insertVertex({
parent: lane1a,
value: 'Contact\nProvider',
position: [90, 30],
size: [80, 50],
style: 'process',
});
const step11 = insertVertex({
parent: lane1a,
value: 'Complete\nAppropriate\nRequest',
position: [190, 30],
size: [80, 50],
style: 'process',
});
const step111 = insertVertex({
parent: lane1a,
value: 'Receive and\nAcknowledge',
position: [385, 30],
size: [80, 50],
style: 'process',
});
const start2 = insertVertex({
parent: lane2b,
position: [40, 40],
size: [30, 30],
style: 'state',
});
const step2 = insertVertex({
parent: lane2b,
value: 'Receive\nRequest',
position: [90, 30],
size: [80, 50],
style: 'process',
});
const step22 = insertVertex({
parent: lane2b,
value: 'Refer to Tap\nSystems\nCoordinator',
position: [190, 30],
size: [80, 50],
style: 'process',
});
const step3 = insertVertex({
parent: lane1b,
value: 'Request 1st-\nGate\nInformation',
position: [190, 30],
size: [80, 50],
style: 'process',
});
const step33 = insertVertex({
parent: lane1b,
value: 'Receive 1st-\nGate\nInformation',
position: [290, 30],
size: [80, 50],
style: 'process',
});
const step4 = insertVertex({
parent: lane2a,
value: 'Receive and\nAcknowledge',
position: [290, 20],
size: [80, 50],
style: 'process',
});
const step44 = insertVertex({
parent: lane2a,
value: 'Contract\nConstraints?',
position: [400, 20],
size: [50, 50],
style: 'condition',
});
const step444 = insertVertex({
parent: lane2a,
value: 'Tap for gas\ndelivery?',
position: [480, 20],
size: [50, 50],
style: 'condition',
});
const end2 = insertVertex({
parent: lane2a,
value: 'B',
position: [560, 30],
size: [30, 30],
style: 'end',
});
const end3 = insertVertex({
parent: lane2a,
value: 'C',
position: [560, 84],
size: [30, 30],
style: 'end',
});
let e = null;
insertEdge({
parent: lane1a,
source: start1,
target: step1,
});
insertEdge({
parent: lane1a,
source: step1,
target: step11,
});
insertEdge({
parent: lane1a,
source: step11,
target: step111,
});
insertEdge({
parent: lane2b,
source: start2,
target: step2,
});
insertEdge({
parent: lane2b,
source: step2,
target: step22,
});
insertEdge({
parent,
source: step22,
target: step3,
});
insertEdge({
parent: lane1b,
source: step3,
target: step33,
});
insertEdge({
parent: lane2a,
source: step4,
target: step44,
});
insertEdge({
parent: lane2a,
value: 'No',
source: step44,
target: step444,
style: 'verticalAlign=bottom',
});
insertEdge({
parent,
value: 'Yes',
source: step44,
target: step111,
style: 'verticalAlign=bottom;horizontal=0;labelBackgroundColor=white;',
});
insertEdge({
parent: lane2a,
value: 'Yes',
source: step444,
target: end2,
style: 'verticalAlign=bottom',
});
e = insertEdge({
parent: lane2a,
value: 'No',
source: step444,
target: end3,
style: 'verticalAlign=top',
});
e.geometry.points = [
new mxPoint(
step444.geometry.x + step444.geometry.width / 2,
end3.geometry.y + end3.geometry.height / 2
),
];
insertEdge({
parent,
source: step1,
target: step2,
style: 'crossover',
});
insertEdge({
parent,
source: step3,
target: step11,
style: 'crossover',
});
e = insertEdge({
parent: lane1a,
source: step11,
target: step33,
style: 'crossover',
});
e.geometry.points = [
new mxPoint(
step33.geometry.x + step33.geometry.width / 2 + 20,
step11.geometry.y + (step11.geometry.height * 4) / 5
),
];
insertEdge({
parent,
source: step33,
target: step4,
});
insertEdge({
parent: lane1a,
source: step111,
target: end1,
});
});
return container;
}
export const Default = Template.bind({});