It is not BPMN example anymore. Here’s the actual full code. Node.locationSpot is set.
import * as go from 'gojs/release/go-module';
import 'gojs/extensionsJSM/Figures';
import './Icons';
import { DrawCommandHandler } from './DrawCommandHandler.js';
export default class Diagram {
diagram = null;
GroupMargin = new go.Margin(5, 5);
#gridEnabled = false;
get gridEnabled() {
return this.#gridEnabled;
}
set gridEnabled(isEnabled) {
this.#gridEnabled = isEnabled;
const { diagram } = this;
diagram.startTransaction('grid');
diagram.grid.visible = isEnabled;
const { draggingTool, resizingTool } = diagram.toolManager;
if (isEnabled) {
draggingTool.isGridSnapEnabled = true;
resizingTool.isGridSnapEnabled = true;
} else {
draggingTool.isGridSnapEnabled = false;
resizingTool.isGridSnapEnabled = false;
}
diagram.commitTransaction('grid');
}
// this is a Part.dragComputation function for limiting where a Node may be dragged
stayInGroup(part, pt, gridpt) {
// don't constrain top-level nodes
const grp = part.containingGroup;
if (grp === null) return gridpt;
// try to stay within the background Shape of the Group
const back = grp.resizeObject;
if (back === null) return gridpt;
// allow dragging a Node out of a Group if the Shift key is down
//if (part.diagram.lastInput.shift) return pt;
const p1 = back.getDocumentPoint(go.Spot.TopLeft);
const p2 = back.getDocumentPoint(go.Spot.BottomRight);
const b = part.actualBounds;
const loc = part.location;
// no placeholder -- just assume some Margin
const m = this.GroupMargin;
// now limit the location appropriately
const x =
Math.max(
p1.x + m.left,
Math.min(gridpt.x, p2.x - m.right - b.width - 1)
) +
(loc.x - b.x);
const y =
Math.max(
p1.y + m.top,
Math.min(gridpt.y, p2.y - m.bottom - b.height - 1)
) +
(loc.y - b.y);
return new go.Point(x, y);
}
constructor(diagramElement) {
const $ = go.GraphObject.make;
const { GroupMargin } = this;
this.diagram = new go.Diagram(diagramElement, {
nodeTemplateMap: this.initNodes(),
groupTemplateMap: this.initGroups(),
linkTemplate: this.initLink(),
commandHandler: new DrawCommandHandler(),
'commandHandler.arrowKeyBehavior': 'move', // default to having arrow keys move selected nodes
'resizingTool.computeMinSize': function () {
// method override
const group = this.adornedObject.part;
const membnds = group.diagram.computePartsBounds(
group.memberParts
);
membnds.addMargin(GroupMargin);
membnds.unionPoint(group.location);
return membnds.size;
},
layout: $(
go.LayeredDigraphLayout, // automatically lay out the lane's subgraph
{
isInitial: false, // don't even do initial layout
isOngoing: false, // don't invalidate layout when nodes or links are added or removed
direction: 0,
columnSpacing: 10,
layeringOption:
go.LayeredDigraphLayout.LayerLongestPathSource,
setsPortSpots: false,
}
),
grid: $(
go.Panel,
'Grid',
{
name: 'GRID',
visible: false,
gridCellSize: new go.Size(10, 10),
gridOrigin: new go.Point(0, 0),
},
$(go.Shape, 'LineH', {
stroke: '#EEEEEE',
strokeWidth: 0.5,
interval: 1,
}),
$(go.Shape, 'LineH', {
stroke: '#E1E1E1',
strokeWidth: 0.5,
interval: 5,
}),
$(go.Shape, 'LineH', {
stroke: '#E1E1E1',
strokeWidth: 1.0,
interval: 10,
}),
$(go.Shape, 'LineV', {
stroke: '#EEEEEE',
strokeWidth: 0.5,
interval: 1,
}),
$(go.Shape, 'LineV', {
stroke: '#E1E1E1',
strokeWidth: 0.5,
interval: 5,
}),
$(go.Shape, 'LineV', {
stroke: '#E1E1E1',
strokeWidth: 1.0,
interval: 10,
})
),
});
this.gridEnabled = true;
}
setModel(model) {
const { nodes, links, initialLayoutEnabled } = model;
const { diagram } = this;
this.diagram.model = new go.GraphLinksModel({
linkFromPortIdProperty: 'fromPort', // required information:
linkToPortIdProperty: 'toPort', // identifies data property names
nodeDataArray: nodes,
linkDataArray: links,
});
if (initialLayoutEnabled) {
diagram.layoutDiagram(true);
}
diagram.model.undoManager.isEnabled = true;
diagram.isModified = false;
}
initLink() {
const $ = go.GraphObject.make;
return $(
go.Link, // the whole link panel
{
routing: go.Link.AvoidsNodes,
curve: go.Link.JumpOver,
corner: 5,
toShortLength: 4,
toEndSegmentLength: 20,
relinkableFrom: true,
relinkableTo: true,
reshapable: true,
// mouse-overs subtly highlight links:
mouseEnter: (e, link) =>
(link.findObject('HIGHLIGHT').stroke =
'rgba(30,144,255,0.2)'),
mouseLeave: (e, link) =>
(link.findObject('HIGHLIGHT').stroke = 'transparent'),
selectionAdorned: false,
},
new go.Binding('points').makeTwoWay(),
$(
go.Shape, // the highlight shape, normally transparent
{
isPanelMain: true,
strokeWidth: 8,
stroke: 'transparent',
name: 'HIGHLIGHT',
}
),
$(
go.Shape, // the link path shape
{ isPanelMain: true, strokeWidth: 1 },
new go.Binding('stroke', 'color')
),
$(
go.Shape,
{ toArrow: 'Standard', strokeWidth: 0, scale: 4 / 3 },
new go.Binding('fill', 'color'),
new go.Binding('scale', 'thickness', (t) => (2 + t) / 3)
),
$(
go.Panel,
'Auto', // the link label, normally not visible
{
visible: false,
name: 'LABEL',
segmentOffset: new go.Point(0, 0),
segmentOrientation: go.Link.OrientUpright,
//segmentOffset: new go.Point(-12, -12),
},
new go.Binding('visible', 'text', function (t) {
return !!t;
}),
$(
go.Shape,
'RoundedRectangle', // the label shape
{ fill: '#FFF', strokeWidth: 0 }
),
$(
go.TextBlock,
{
textAlign: 'center',
stroke: '#333333',
editable: false,
},
new go.Binding('text')
)
)
);
}
initNodes() {
const $ = go.GraphObject.make;
// constants for design choices
const GradientYellow = $(go.Brush, 'Linear', {
0: 'LightGoldenRodYellow',
1: '#FFFF66',
});
const GradientLightGreen = $(go.Brush, 'Linear', {
0: '#E0FEE0',
1: 'PaleGreen',
});
const ActivityNodeFill = $(go.Brush, 'Linear', {
0: 'OldLace',
1: 'PapayaWhip',
});
const ActivityNodeStroke = '#CDAA7D';
const ActivityNodeWidth = 120;
const ActivityNodeHeight = 80;
const EventNodeSize = 42;
const EventNodeInnerSize = EventNodeSize - 6;
const EventEndOuterFillColor = 'pink';
const EventBackgroundColor = GradientLightGreen;
const EventDimensionStrokeColor = 'green';
const EventDimensionStrokeEndColor = 'red';
const EventNodeStrokeWidthIsEnd = 4;
const GatewayNodeSize = 80;
const GatewayNodeSymbolSize = 35;
const GatewayNodeFill = GradientYellow;
const GatewayNodeStroke = 'darkgoldenrod';
const GatewayNodeSymbolStroke = 'darkgoldenrod';
const GatewayNodeSymbolFill = GradientYellow;
const GatewayNodeSymbolStrokeWidth = 3;
function nodeEventDimensionStrokeColorConverter(s) {
if (s === 8) return EventDimensionStrokeEndColor;
return EventDimensionStrokeColor;
}
const activityNodeTemplate = $(
go.Node,
'Table',
{
locationSpot: go.Spot.Center,
doubleClick: (e, node) => {
this.onActivityyDoubleClick(node);
},
},
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(
go.Point.stringify
),
$(
go.Panel,
'Auto',
{
name: 'PANEL',
minSize: new go.Size(ActivityNodeWidth, ActivityNodeHeight),
desiredSize: new go.Size(
ActivityNodeWidth,
ActivityNodeHeight
),
},
new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(
go.Size.stringify
),
$(
go.Panel,
'Spot',
$(
go.Shape,
'RoundedRectangle', // the outside rounded rectangle
{
name: 'SHAPE',
fill: ActivityNodeFill,
stroke: ActivityNodeStroke,
parameter1: 10, // corner size
fromLinkable: true,
toLinkable: true,
portId: '', // the default port: if no spot on link data, use closest side
cursor: 'pointer',
}
),
// task icon
$(
go.Shape,
'SetProperties', // will be SetProperties, ChangeState, ExecuteScript
{
alignment: new go.Spot(0, 0, 5, 5),
alignmentFocus: go.Spot.TopLeft,
width: 18,
height: 18,
},
new go.Binding('figure', 'actionType')
) // end Task Icon
), // end main body rectangles spot panel
$(
go.TextBlock, // the center text
{
alignment: go.Spot.Center,
textAlign: 'center',
margin: 12,
},
new go.Binding('text')
)
), // end Auto Panel
// four named ports, one on each side:
makePort('T', go.Spot.Top, true, true),
makePort('L', go.Spot.Left, true, true),
makePort('R', go.Spot.Right, true, true),
makePort('B', go.Spot.Bottom, true, true)
); // end go.Node, which is a Spot Panel with bound itemArray
// ------------------------------------------ Event Node Template ----------------------------------------------
const eventNodeTemplate = $(
go.Node,
'Vertical',
{
locationObjectName: 'SHAPE',
locationSpot: go.Spot.Center,
//dragComputation: this.stayInGroup.bind(this), // limit dragging of Nodes to stay within the containing Group, defined above
},
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(
go.Point.stringify
),
// can be resided according to the user's desires
{ resizable: false, resizeObjectName: 'SHAPE' },
$(
go.Panel,
'Spot',
$(
go.Shape,
'Circle', // Outer circle
{
strokeWidth: 1,
name: 'SHAPE',
desiredSize: new go.Size(EventNodeSize, EventNodeSize),
portId: '',
fromLinkable: true,
toLinkable: true,
cursor: 'pointer',
fromSpot: go.Spot.RightSide,
toSpot: go.Spot.AllSides,
},
// allows the color to be determined by the node data
new go.Binding('fill', 'eventDimension', function (s) {
return s === 8
? EventEndOuterFillColor
: EventBackgroundColor;
}),
new go.Binding('strokeWidth', 'eventDimension', function (
s
) {
return s === 8 ? EventNodeStrokeWidthIsEnd : 1;
}),
new go.Binding(
'stroke',
'eventDimension',
nodeEventDimensionStrokeColorConverter
),
new go.Binding(
'desiredSize',
'size',
go.Size.parse
).makeTwoWay(go.Size.stringify)
), // end main shape
$(
go.Shape,
'Circle', // Inner circle
{
alignment: go.Spot.Center,
desiredSize: new go.Size(
EventNodeInnerSize,
EventNodeInnerSize
),
fill: null,
},
new go.Binding(
'stroke',
'eventDimension',
nodeEventDimensionStrokeColorConverter
),
new go.Binding('visible', 'eventDimension', function (s) {
return s > 3 && s <= 7;
}) // inner only visible for 4 thru 7
)
), // end Auto Panel
$(
go.TextBlock,
{
alignment: go.Spot.Center,
textAlign: 'center',
margin: 5,
},
new go.Binding('text')
)
); // end go.Node Vertical
// ------------------------------------------ Gateway Node Template ----------------------------------------------
function nodeGatewaySymbolTypeConverter(s) {
return s === 0 ? 'AtLeastOne' : 'ALL';
}
// Define a function for creating a "port" that is normally transparent.
// The "name" is used as the GraphObject.portId, the "spot" is used to control how links connect
// and where the port is positioned on the node, and the boolean "output" and "input" arguments
// control whether the user can draw links from or to the port.
function makePort(name, spot, output, input) {
// the port is basically just a small transparent circle
return $(go.Shape, 'Circle', {
fill: null, // not seen, by default; set to a translucent gray by showSmallPorts, defined below
stroke: null,
desiredSize: new go.Size(7, 7),
alignment: spot, // align the port on the main Shape
alignmentFocus: spot, // just inside the Shape
portId: name, // declare this object to be a "port"
fromSpot: spot,
toSpot: spot, // declare where links may connect at this port
fromLinkable: output,
toLinkable: input, // declare whether the user may draw links to/from here
cursor: 'pointer', // show a different cursor to indicate potential link point
});
}
const gatewayNodeTemplate = $(
go.Node,
'Table',
{
locationSpot: go.Spot.Center,
//dragComputation: this.stayInGroup.bind(this), // limit dragging of Nodes to stay within the containing Group, defined above
doubleClick: (e, node) => {
this.onGatewayDoubleClick(node);
},
},
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(
go.Point.stringify
),
$(
go.Panel,
'Spot',
$(go.Shape, 'Diamond', {
name: 'SHAPE',
strokeWidth: 1,
fill: GatewayNodeFill,
stroke: GatewayNodeStroke,
desiredSize: new go.Size(GatewayNodeSize, GatewayNodeSize),
cursor: 'pointer',
fromLinkable: true,
toLinkable: true,
portId: '', // the default port: if no spot on link data, use closest side
}), // end main shape
$(
go.TextBlock,
{
alignment: go.Spot.Center,
stroke: GatewayNodeSymbolStroke,
font: '25px Roboto, sans-serif',
},
new go.Binding('text', 'gatewayType', (gatewayType) => {
return gatewayType === 0 ? '1+' : 'ALL';
})
),
new go.Binding('visible', 'hidden', (hidden) => {
return !hidden;
})
),
// four small named ports, one on each side:
makePort('T', go.Spot.Top, false, true),
makePort('L', go.Spot.Left, true, true),
makePort('R', go.Spot.Right, true, true),
makePort('B', go.Spot.Bottom, true, false)
); // end go.Node Vertical
const nodeTemplateMap = new go.Map();
nodeTemplateMap.add('activity', activityNodeTemplate);
nodeTemplateMap.add('event', eventNodeTemplate);
nodeTemplateMap.add('gateway', gatewayNodeTemplate);
return nodeTemplateMap;
}
// ------------------------ Lanes and Pools ------------------------------------------------------------
initGroups() {
const $ = go.GraphObject.make;
const ActivityNodeWidth = 120;
const ActivityNodeHeight = 80;
const subProcessGroupTemplate = $(
go.Group,
'Auto',
{
name: 'Step',
selectionObjectName: 'PANEL',
isSubGraphExpanded: true,
resizable: true,
resizeObjectName: 'PANEL',
computesBoundsAfterDrag: true,
computesBoundsIncludingLinks: false,
layerName: 'Background', // all pools and lanes are always behind all nodes and links
background: 'transparent', // can grab anywhere in bounds
movable: true, // allows users to re-order by dragging
copyable: false, // can't copy lanes or pools
avoidable: false, // don't impede AvoidsNodes routed Links
layout: $(
go.LayeredDigraphLayout, // automatically lay out the lane's subgraph
{
isInitial: false, // don't even do initial layout
isOngoing: false, // don't invalidate layout when nodes or links are added or removed
direction: 0,
columnSpacing: 50,
setsPortSpots: false,
}
),
subGraphExpandedChanged: (grp) => {
if (grp.diagram === null) return;
if (grp.diagram.undoManager.isUndoingRedoing) return;
const shp = grp.resizeObject;
if (grp.isSubGraphExpanded) {
shp.height = grp.data.height;
shp.width = grp.data.width;
grp.resizable = true;
} else {
grp.resizable = false;
if (!isNaN(shp.height)) {
grp.diagram.model.set(
grp.data,
'height',
shp.height
);
}
if (!isNaN(shp.width)) {
grp.diagram.model.set(grp.data, 'width', shp.width);
}
shp.height = NaN;
shp.width = NaN;
}
// if (grp.isSubGraphExpanded) grp.isSelected = true;
// this.assignGroupLayer(grp);
},
doubleClick: (e, node) => {
this.onLaneDoubleClick(node);
},
selectionChanged: (part) => {
const data = part.isSelected ? part.data : null;
this.onLaneSelectionChanged(data);
},
},
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(
go.Point.stringify
),
new go.Binding('isSubGraphExpanded', 'expanded'),
$(go.Shape, 'RoundedRectangle', { fill: 'white' }),
$(
go.Panel,
'Table',
{
name: 'PANEL',
defaultAlignment: go.Spot.Left,
minSize: new go.Size(ActivityNodeWidth, ActivityNodeHeight),
},
new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(
go.Size.stringify
),
$(
go.Panel,
'Horizontal',
{
defaultAlignment: go.Spot.Left,
margin: 5,
alignment: go.Spot.Left,
row: 0,
},
new go.Binding('alignment', 'isSubGraphExpanded', function (
s
) {
return s ? go.Spot.Left : go.Spot.Center;
}),
$('SubGraphExpanderButton'),
$(
go.TextBlock,
{ margin: new go.Margin(2, 0, 0, 5) },
new go.Binding('text')
)
),
// create a placeholder to represent the area where the contents of the group are
$(go.Placeholder, { padding: this.GroupMargin, row: 1 })
) // end Vertical Panel
); // end Group
// ------------------------------------------ Template Maps ----------------------------------------------
const groupTemplateMap = new go.Map();
groupTemplateMap.add('subprocess', subProcessGroupTemplate);
return groupTemplateMap;
}
undo() {
this.diagram.commandHandler.undo();
}
redo() {
this.diagram.commandHandler.redo();
}
cutSelection() {
this.diagram.commandHandler.cutSelection();
}
copySelection() {
this.diagram.commandHandler.copySelection();
}
pasteSelection() {
this.diagram.commandHandler.pasteSelection();
}
deleteSelection() {
this.diagram.commandHandler.deleteSelection();
}
selectAll() {
this.diagram.commandHandler.selectAll();
}
destroy() {
this.diagram.div = null;
this.diagram = null;
}
}