Skip to content

Commit 4044a86

Browse files
committed
REA-458: Add click listener for transition events
1 parent 21c5cff commit 4044a86

File tree

6 files changed

+188
-56
lines changed

6 files changed

+188
-56
lines changed

docs/features/Transition-Events.mdx

Lines changed: 65 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -21,48 +21,55 @@ By default, the <TypeLink type="ProcessMining" /> component provides bluish tran
2121

2222
<Playground
2323
code={`
24-
function App() {
25-
const [showTransitionEvents, setShowTransitionEvents] = useState(true);
26-
const [timestamp, setTimestamp] = useState(5);
27-
const maxCaseId = 100;
28-
29-
return (
30-
<ProcessMining
31-
eventLog={data}
32-
timestamp={timestamp}
33-
showTransitionEvents={showTransitionEvents}
34-
transitionEventStyling={(previousEvent, _) => ({
35-
size: 15,
36-
hue: (parseInt(previousEvent.caseId as string) / maxCaseId) * 360,
37-
})}
38-
showHeatmap={false}
39-
>
40-
<div className="options">
41-
<div>
42-
<span>Timestamp</span>
43-
<input
44-
type="range"
45-
min={0}
46-
max={30}
47-
onInput={(e) => {
48-
setTimestamp(parseFloat((e.target as HTMLInputElement).value));
49-
}}
50-
value={timestamp}
51-
/>
52-
</div>
53-
<div>
54-
<input
55-
type="checkbox"
56-
onChange={(event) => setShowTransitionEvents(event.target.checked)}
57-
checked={showTransitionEvents}
58-
/>
59-
<span>Hide Transition Events</span>
60-
</div>
61-
</div>
62-
</ProcessMining>
63-
);
64-
}
24+
function App() {
25+
const [showTransitionEvents, setShowTransitionEvents] = useState(true);
26+
const [timestamp, setTimestamp] = useState(5);
27+
const [clickedEventIds, setClickedEventIds] = useState([])
28+
const maxCaseId = 100;
6529
30+
return (
31+
<ProcessMining
32+
eventLog={data}
33+
timestamp={timestamp}
34+
showTransitionEvents={showTransitionEvents}
35+
transitionEventStyling={(previousEvent, _) => ({
36+
size: 15,
37+
hue: (parseInt(previousEvent.caseId as string) / maxCaseId) * 360,
38+
})}
39+
onTransitionEventsClick={(caseIds) => setClickedEventIds(caseIds)}
40+
showHeatmap={false}
41+
>
42+
<div className="options">
43+
<div>
44+
<span>Timestamp</span>
45+
<input
46+
type="range"
47+
min={0}
48+
max={30}
49+
onInput={(e) => {
50+
setTimestamp(parseFloat((e.target as HTMLInputElement).value));
51+
}}
52+
value={timestamp}
53+
/>
54+
</div>
55+
<div>
56+
<input
57+
type="checkbox"
58+
onChange={(event) => setShowTransitionEvents(event.target.checked)}
59+
checked={showTransitionEvents}
60+
/>
61+
<span>Hide Transition Events</span>
62+
</div>
63+
</div>
64+
{clickedEventIds.length > 0 && <div className="ids-display">
65+
<div>
66+
<span>Transition Events: </span>
67+
<span>{clickedEventIds.join(", ")}</span>
68+
</div>
69+
</div>}
70+
</ProcessMining>
71+
);
72+
}
6673
`}
6774
data={`[
6875
{ caseId: 0, activity: 'Start', timestamp: 8.383561495922297, duration: 0.0006804154279300233 },
@@ -2058,15 +2065,23 @@ By default, the <TypeLink type="ProcessMining" /> component provides bluish tran
20582065
]`}
20592066
css={`
20602067
.options {
2061-
position: absolute;
2062-
top: 10px;
2063-
left: 10px;
2064-
display: flex;
2065-
gap: 1em;
2066-
flex-direction: column;
2067-
background-color: #393939;
2068-
padding: 10px;
2069-
border-radius: 10px;
2068+
position: absolute;
2069+
top: 10px;
2070+
left: 10px;
2071+
display: flex;
2072+
gap: 1em;
2073+
flex-direction: column;
2074+
background-color: #393939;
2075+
padding: 10px;
2076+
border-radius: 10px;
2077+
}
2078+
.ids-display {
2079+
position: absolute;
2080+
bottom: 10px;
2081+
right: 10px;
2082+
background-color: #393939;
2083+
padding: 10px;
2084+
border-radius: 10px;
20702085
}
20712086
`}
20722087
/>

examples/src/examples/TransitionEvents/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const maxTime = 30
1414
function TransitionEvents() {
1515
const [timestamp, setTimestamp] = useState(5)
1616
const [showTransitionEvents, setShowTransitionEvents] = useState(true)
17+
const [clickedEventIds, setClickedEventIds] = useState('')
1718

1819
const { startAnimation, stopAnimation } = useProcessMiningContext()
1920

@@ -36,6 +37,9 @@ function TransitionEvents() {
3637
}
3738
}
3839
}}
40+
onTransitionEventsClick={clickedEventIds => {
41+
setClickedEventIds(JSON.stringify(clickedEventIds))
42+
}}
3943
>
4044
<div
4145
style={{
@@ -71,6 +75,8 @@ function TransitionEvents() {
7175
setShowTransitionEvents((e.target as HTMLInputElement).checked)
7276
}}
7377
/>
78+
<label>Transition Event IDs</label>
79+
<textarea readOnly value={clickedEventIds} />
7480
</div>
7581
</ProcessMining>
7682
)

src/ProcessMining.tsx

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
useReactNodeRendering,
2626
withGraphComponent
2727
} from '@yworks/react-yfiles-core'
28-
import { GraphViewerInputMode, ICanvasObject, IEdge, INode } from 'yfiles'
28+
import { ClickEventArgs, GraphViewerInputMode, ICanvasObject, IEdge, INode } from 'yfiles'
2929
import {
3030
ProcessMiningProvider,
3131
useProcessMiningContext,
@@ -36,7 +36,8 @@ import {
3636
initializeFocus,
3737
initializeHover,
3838
initializeInputMode,
39-
initializeSelection
39+
initializeSelection,
40+
initializeTransitionEventsClick
4041
} from './core/input.ts'
4142
import {
4243
extractGraph,
@@ -59,12 +60,17 @@ import { RenderProcessStepProps } from './styles/RenderProcessStep.tsx'
5960
import { RenderTooltipProps } from './components/ProcessMiningTooltip.tsx'
6061
import { RenderPopupProps } from './components/ProcessMiningPopup.tsx'
6162

63+
/**
64+
* The id of the entity that passes through the process steps
65+
*/
66+
export type CaseId = number | string
67+
6268
/**
6369
* An event that marks one step in the process.
6470
*/
6571
export type ActivityEvent = {
6672
/** The id of the entity that passes through the process steps. */
67-
caseId: number | string
73+
caseId: CaseId
6874

6975
/** The name of the activity that is executed in this event. */
7076
activity: string
@@ -106,6 +112,11 @@ export type ItemHoveredListener<ProcessStep> = (
106112
oldItem?: ProcessStep | null
107113
) => void
108114

115+
/**
116+
* A callback type invoked when transition event(s) has been clicked.
117+
*/
118+
export type TransitionEventsClickedListener = (transitionEventIds: CaseId[]) => void
119+
109120
/**
110121
* A function that returns whether the given item matches the search needle.
111122
*/
@@ -209,6 +220,10 @@ export interface ProcessMiningProps<TEvent extends ActivityEvent, TNeedle> {
209220
* An optional callback that's called when the hovered item has changed.
210221
*/
211222
onItemHover?: ItemHoveredListener<ProcessStep>
223+
/**
224+
* An optional callback that's called when transition event(s) is clicked.
225+
*/
226+
onTransitionEventsClick?: TransitionEventsClickedListener
212227
/**
213228
* A string or a complex object to search for.
214229
*
@@ -363,6 +378,7 @@ const ProcessMiningCore = withGraphComponent(
363378
showHeatmap = true,
364379
showTransitionEvents = true,
365380
onItemHover,
381+
onTransitionEventsClick,
366382
onSearch,
367383
onItemFocus,
368384
onItemSelect,
@@ -482,6 +498,33 @@ const ProcessMiningCore = withGraphComponent(
482498
}
483499
}, [onItemHover])
484500

501+
useEffect(() => {
502+
let transitionEventsClickedListener: (
503+
inputMode: GraphViewerInputMode,
504+
event: ClickEventArgs
505+
) => void
506+
507+
if (showTransitionEvents) {
508+
transitionEventsClickedListener = initializeTransitionEventsClick(
509+
onTransitionEventsClick,
510+
graphComponent,
511+
transitionEventVisualSupport
512+
)
513+
}
514+
515+
return () => {
516+
// clean up
517+
if (transitionEventsClickedListener) {
518+
;(graphComponent.inputMode as GraphViewerInputMode).removeCanvasClickedListener(
519+
transitionEventsClickedListener
520+
)
521+
;(graphComponent.inputMode as GraphViewerInputMode).removeItemClickedListener(
522+
transitionEventsClickedListener
523+
)
524+
}
525+
}
526+
}, [onTransitionEventsClick, showTransitionEvents])
527+
485528
useEffect(() => {
486529
// initialize the focus and selection to display the information of the selected element
487530
const currentItemChangedListener = initializeFocus(onItemFocus, graphComponent)

src/core/input.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
ClickEventArgs,
23
type GraphComponent,
34
GraphFocusIndicatorManager,
45
GraphItemTypes,
@@ -13,6 +14,8 @@ import { enableSingleSelection } from './SingleSelectionHelper.ts'
1314
import { ProcessMiningModel } from '../ProcessMiningModel.ts'
1415
import { getProcessStepData, ProcessStep } from './process-graph-extraction.ts'
1516
import { enableSmartClickNavigation } from './configure-smart-click-navigation.ts'
17+
import type { TransitionEventVisualSupport } from '../styles/TransitionEventVisual.ts'
18+
import type { TransitionEventsClickedListener } from '../ProcessMining.tsx'
1619

1720
/**
1821
* Set up the graph viewer input mode to and enables interactivity to expand and collapse subtrees.
@@ -73,6 +76,29 @@ export function initializeHover(
7376
return hoverItemChangedListener
7477
}
7578

79+
/**
80+
* Adds and returns the listener when transition events are clicked. The return is needed
81+
* so that the listener can be removed from the graph.
82+
*/
83+
export function initializeTransitionEventsClick(
84+
onTransitionEventsClick: TransitionEventsClickedListener | undefined,
85+
graphComponent: GraphComponent,
86+
transitionEventVisualSupport: TransitionEventVisualSupport
87+
) {
88+
const inputMode = graphComponent.inputMode as GraphViewerInputMode
89+
const transitionEventsClickedListener = (_: GraphViewerInputMode, evt: ClickEventArgs): void => {
90+
if (onTransitionEventsClick) {
91+
const clickedTransitionEntries = transitionEventVisualSupport.getEntriesAtLocation(
92+
evt.location
93+
)
94+
onTransitionEventsClick(clickedTransitionEntries.map(entry => entry.caseId))
95+
}
96+
}
97+
inputMode.addItemClickedListener(transitionEventsClickedListener)
98+
inputMode.addCanvasClickedListener(transitionEventsClickedListener)
99+
return transitionEventsClickedListener
100+
}
101+
76102
/**
77103
* Adds and returns the listener when the currentItem changes. The return is needed so that the listener
78104
* can be removed from the graph.

src/core/process-visualization.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export function prepareProcessVisualization<TEvent extends ActivityEvent>(
8484
// add an item to the transition representing the event
8585
const { hue, size } = transitionEventStyling(event, nextEvent)
8686
transitionEventVisualSupport.addItem(
87+
events[0].event.caseId,
8788
transition,
8889
false,
8990
event.timestamp,

0 commit comments

Comments
 (0)