Skip to content

Commit c20777e

Browse files
committed
WIP
1 parent 6bc932c commit c20777e

File tree

10 files changed

+660
-4
lines changed

10 files changed

+660
-4
lines changed

assets/css/bpmn-js-token-simulation.css

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
}
1818

1919
.bjs-container.simulation .djs-container {
20-
box-shadow: inset 0px 0px 0px 4px var(--token-simulation-green-base-44, #10D070);
20+
box-shadow: inset 0px 0px 0px 4px #0f62fe;
2121
}
2222

2323
.bjs-container.simulation.paused .djs-container {
@@ -115,6 +115,27 @@
115115
50% { top: 5px; }
116116
}
117117

118+
@keyframes bts-pulsate {
119+
0%, 100% {
120+
transform: scale(1);
121+
opacity: 1;
122+
}
123+
50% {
124+
transform: scale(1.2);
125+
opacity: 0.7;
126+
}
127+
}
128+
129+
.bts-active-indicator {
130+
background-color: #0f62fe;
131+
border-radius: 100%;
132+
width: 12px;
133+
height: 12px;
134+
display: inline-block;
135+
box-shadow: 0 0 4px #0f62fe;
136+
animation: bts-pulsate 1.5s ease-in-out infinite;
137+
}
138+
118139
.bts-notifications {
119140
position: absolute;
120141
bottom: 20px;
@@ -258,7 +279,7 @@
258279
display: none !important;
259280
}
260281

261-
.bjs-container.simulation .djs-overlay:not(.djs-overlay-bts-context-menu, .djs-overlay-bts-element-notification, .djs-overlay-bts-token-count, .djs-overlay-drilldown) {
282+
.bjs-container.simulation .djs-overlay:not(.djs-overlay-bts-context-menu, .djs-overlay-bts-element-notification, .djs-overlay-bts-token-count, .djs-overlay-bts-active-indicator, .djs-overlay-drilldown) {
262283
display: none !important;
263284
}
264285

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Execution Visualizer
2+
3+
This module provides a way to visualize actual BPMN execution without running autonomous simulation. Instead of simulating token flow, you can drive the visualization programmatically from external execution data.
4+
5+
## Key Features
6+
7+
- **No Token Animation**: Elements are styled instantly without animated token movement
8+
- **Hidden Controls**: No log panel, play/pause buttons, or reset controls
9+
- **Programmatic Control**: Mode switching and execution state controlled via API
10+
- **Simple API**: Set executed elements, executed flows, and active element via single method call
11+
12+
## Usage
13+
14+
### Basic Setup
15+
16+
```javascript
17+
import BpmnViewer from 'bpmn-js/lib/NavigatedViewer';
18+
import TokenSimulationVisualizerModule from 'bpmn-js-token-simulation/lib/visualizer';
19+
20+
const viewer = new BpmnViewer({
21+
container: '#canvas',
22+
additionalModules: [
23+
TokenSimulationVisualizerModule
24+
]
25+
});
26+
27+
await viewer.importXML(bpmnXML);
28+
```
29+
30+
### Accessing Services
31+
32+
```javascript
33+
const toggleMode = viewer.get('toggleMode');
34+
const executionVisualizer = viewer.get('executionVisualizer');
35+
```
36+
37+
### API Methods
38+
39+
#### Enable Visualization Mode
40+
41+
```javascript
42+
// Turn on visualization mode (activates visual styling)
43+
toggleMode.toggleMode(true);
44+
45+
// Turn off visualization mode (restores original diagram)
46+
toggleMode.toggleMode(false);
47+
```
48+
49+
#### Set Execution State
50+
51+
```javascript
52+
executionVisualizer.setExecutionState({
53+
executedElements: ['StartEvent_1', 'Flow_1', 'Task_1', 'Flow_2', 'Task_2', 'Flow_3'], // IDs of executed elements and flows
54+
activeElement: 'Task_3' // ID of currently active element
55+
});
56+
```
57+
58+
- **executedElements**: Array of element IDs that have been executed, including both shapes and sequence flows (styled in gray)
59+
- **activeElement**: ID of the currently active element (styled in green)
60+
61+
#### Clear Visualization
62+
63+
```javascript
64+
// Remove all execution visualization styling
65+
executionVisualizer.clear();
66+
```
67+
68+
#### Get Current State
69+
70+
```javascript
71+
const state = executionVisualizer.getExecutionState();
72+
// Returns: { executedElements: [...], activeElement: '...' }
73+
```
74+
75+
## Visual Styling
76+
77+
- **Executed Elements**: Blue stroke, no background fill
78+
- **Active Element**: Red stroke, no background fill
79+
- **Priority**: Active element styling (priority 2000) overrides executed styling (priority 1000)
80+
81+
## Events
82+
83+
The execution visualizer fires custom events on the event bus:
84+
85+
```javascript
86+
const eventBus = viewer.get('eventBus');
87+
88+
// Fired when execution state changes
89+
eventBus.on('executionVisualizer.stateChanged', (event) => {
90+
console.log('Executed elements:', event.executedElements);
91+
console.log('Active element:', event.activeElement);
92+
});
93+
94+
// Fired when visualization is cleared
95+
eventBus.on('executionVisualizer.cleared', () => {
96+
console.log('Visualization cleared');
97+
});
98+
```
99+
100+
## Differences from Simulation Mode
101+
102+
| Feature | Simulation Mode | Visualizer Mode |
103+
|---------|----------------|-----------------|
104+
| Token Animation | ✅ Animated tokens | ❌ No animation |
105+
| Log Panel | ✅ Visible | ❌ Hidden |
106+
| Play/Pause Controls | ✅ Visible | ❌ Hidden |
107+
| Reset Button | ✅ Visible | ❌ Hidden |
108+
| Context Pads | ✅ Visible | ❌ Hidden |
109+
| Mode Toggle | ✅ UI Button | ✅ Programmatic only |
110+
| Token Count | ✅ Visible | ✅ Visible |
111+
| Execution Control | ⚙️ Autonomous simulation | ⚙️ External control |
112+
113+
## Example
114+
115+
See [example/visualizer.html](../example/visualizer.html) and [example/src/visualizer.js](../example/src/visualizer.js) for a complete working example.
116+
117+
## Module Structure
118+
119+
The visualizer mode includes these modules (from [lib/visualizer-base.js](../lib/visualizer-base.js)):
120+
121+
- **SimulatorModule**: Core scope and state management (behaviors disabled)
122+
- **ColoredScopesModule**: Scope coloring for multi-instance scenarios
123+
- **SimulationStateModule**: State tracking
124+
- **ShowScopesModule**: Scope visualization
125+
- **ElementSupportModule**: BPMN element type support
126+
- **TokenCountModule**: Token count overlays
127+
- **ExclusiveGatewaySettingsModule**: Gateway configuration
128+
- **InclusiveGatewaySettingsModule**: Gateway configuration
129+
- **NeutralElementColors**: Base element coloring
130+
- **ExecutionVisualizerModule**: External execution visualization (new)
131+
132+
Excluded modules (compared to full simulation):
133+
- AnimationModule
134+
- LogModule
135+
- PauseSimulationModule
136+
- ResetSimulationModule
137+
- TokenSimulationPaletteModule
138+
- ContextPadsModule

example/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ <h1>bpmn-js-token-simulation</h1>
5050
</p>
5151

5252
<p style="margin-top: 3em">
53-
<a class="link" href="viewer.html">Open Viewer</a> or <a class="link" href="modeler.html">Open Modeler</a>
53+
<a class="link" href="viewer.html">Open Viewer</a> or <a class="link" href="modeler.html">Open Modeler</a> or <a class="link" href="visualizer.html">Open Visualizer</a>
5454
</p>
5555
</body>
5656
</html>

example/src/visualizer.js

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import TokenSimulationVisualizerModule from '../../lib/visualizer';
2+
3+
import BpmnViewer from 'bpmn-js/lib/NavigatedViewer';
4+
5+
import fileDrop from 'file-drops';
6+
7+
import fileOpen from 'file-open';
8+
9+
import exampleXML from '../resources/example.bpmn';
10+
11+
12+
const url = new URL(window.location.href);
13+
14+
const persistent = url.searchParams.has('p');
15+
const active = url.searchParams.has('e');
16+
17+
const initialDiagram = (() => {
18+
try {
19+
return persistent && localStorage['diagram-xml'] || exampleXML;
20+
} catch (err) {
21+
return exampleXML;
22+
}
23+
})();
24+
25+
const ExampleModule = {
26+
__init__: [
27+
[ 'eventBus', 'bpmnjs', 'toggleMode', 'executionVisualizer', function(eventBus, bpmnjs, toggleMode, executionVisualizer) {
28+
29+
if (persistent) {
30+
eventBus.on('commandStack.changed', function() {
31+
bpmnjs.saveXML().then(result => {
32+
localStorage['diagram-xml'] = result.xml;
33+
});
34+
});
35+
}
36+
37+
if ('history' in window) {
38+
eventBus.on('tokenSimulation.toggleMode', event => {
39+
40+
if (event.active) {
41+
url.searchParams.set('e', '1');
42+
} else {
43+
url.searchParams.delete('e');
44+
}
45+
46+
history.replaceState({}, document.title, url.toString());
47+
});
48+
}
49+
50+
eventBus.on('diagram.init', 500, () => {
51+
toggleMode.toggleMode(active);
52+
});
53+
54+
// Wire up example UI controls
55+
setupControls(toggleMode, executionVisualizer, eventBus);
56+
} ]
57+
]
58+
};
59+
60+
function setupControls(toggleMode, executionVisualizer, eventBus) {
61+
const toggleBtn = document.getElementById('toggleMode');
62+
const step1Btn = document.getElementById('step1');
63+
const step2Btn = document.getElementById('step2');
64+
const step3Btn = document.getElementById('step3');
65+
const step4Btn = document.getElementById('step4');
66+
const clearBtn = document.getElementById('clear');
67+
68+
let modeActive = false;
69+
70+
toggleBtn.addEventListener('click', () => {
71+
modeActive = !modeActive;
72+
toggleMode.toggleMode(modeActive);
73+
toggleBtn.textContent = modeActive ? 'Mode: ON' : 'Mode: OFF';
74+
});
75+
76+
// Example execution steps using actual IDs from example.bpmn
77+
step1Btn.addEventListener('click', () => {
78+
if (!modeActive) {
79+
modeActive = true;
80+
toggleMode.toggleMode(modeActive);
81+
toggleBtn.textContent = 'Mode: ON';
82+
}
83+
executionVisualizer.setExecutionState({
84+
executedElements: [],
85+
activeElement: 'StartEvent_0j9yk1o'
86+
});
87+
});
88+
89+
step2Btn.addEventListener('click', () => {
90+
if (!modeActive) {
91+
modeActive = true;
92+
toggleMode.toggleMode(modeActive);
93+
toggleBtn.textContent = 'Mode: ON';
94+
}
95+
executionVisualizer.setExecutionState({
96+
executedElements: ['StartEvent_0j9yk1o', 'SequenceFlow_1bpznq3'],
97+
activeElement: 'ParallelGateway_0s75uad'
98+
});
99+
});
100+
101+
step3Btn.addEventListener('click', () => {
102+
if (!modeActive) {
103+
modeActive = true;
104+
toggleMode.toggleMode(modeActive);
105+
toggleBtn.textContent = 'Mode: ON';
106+
}
107+
executionVisualizer.setExecutionState({
108+
executedElements: ['StartEvent_0j9yk1o', 'SequenceFlow_1bpznq3', 'ParallelGateway_0s75uad', 'SequenceFlow_10d6h3a'],
109+
activeElement: 'Task_1upmjgh'
110+
});
111+
});
112+
113+
step4Btn.addEventListener('click', () => {
114+
if (!modeActive) {
115+
modeActive = true;
116+
toggleMode.toggleMode(modeActive);
117+
toggleBtn.textContent = 'Mode: ON';
118+
}
119+
executionVisualizer.setExecutionState({
120+
executedElements: ['StartEvent_0j9yk1o', 'SequenceFlow_1bpznq3', 'ParallelGateway_0s75uad', 'SequenceFlow_10d6h3a', 'Task_1upmjgh', 'SequenceFlow_1dzm18n'],
121+
activeElement: 'ParallelGateway_158jo5x'
122+
});
123+
});
124+
125+
clearBtn.addEventListener('click', () => {
126+
executionVisualizer.clear();
127+
});
128+
}
129+
130+
const viewer = new BpmnViewer({
131+
container: '#canvas',
132+
additionalModules: [
133+
ExampleModule,
134+
TokenSimulationVisualizerModule
135+
]
136+
});
137+
138+
function openDiagram(diagram) {
139+
return viewer.importXML(diagram)
140+
.then(({ warnings }) => {
141+
if (warnings.length) {
142+
console.warn(warnings);
143+
}
144+
145+
if (persistent) {
146+
localStorage['diagram-xml'] = diagram;
147+
}
148+
149+
viewer.get('canvas').zoom('fit-viewport');
150+
})
151+
.catch(err => {
152+
console.error(err);
153+
});
154+
}
155+
156+
function openFile(files) {
157+
if (!files.length) {
158+
return;
159+
}
160+
161+
const file = files[0];
162+
163+
const reader = new FileReader();
164+
165+
reader.onload = function(e) {
166+
const xml = e.target.result;
167+
168+
openDiagram(xml);
169+
};
170+
171+
reader.readAsText(file);
172+
}
173+
174+
fileDrop('Drop a BPMN diagram', function(files) {
175+
openFile(files);
176+
});
177+
178+
document.querySelector('#canvas').addEventListener('dragover', hideDropMessage);
179+
180+
function hideDropMessage() {
181+
const dropMessage = document.querySelector('.drop-message');
182+
183+
if (dropMessage) {
184+
dropMessage.style.display = 'none';
185+
}
186+
187+
document.querySelector('#canvas').removeEventListener('dragover', hideDropMessage);
188+
}
189+
190+
openDiagram(initialDiagram);
191+
192+
// open on click
193+
document.body.addEventListener('click', event => {
194+
195+
// we only react if clicked in a non-input area
196+
if (!event.target.closest('.djs-palette, input, textarea, button, select, a')) {
197+
fileOpen(openFile, {
198+
accept: '.bpmn',
199+
multiple: false
200+
});
201+
}
202+
});
203+
204+
// expose to window for external access
205+
window.bpmnViewer = viewer;

0 commit comments

Comments
 (0)