Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/bpmn-support.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ The default rendering uses `white` as fill color and `black` as stroke color.
|Stroke fits the BPMN specification but no icon

|Message Intermediate Catch Event
|
|Stroke fits the BPMN specification but no icon
|icon:check-circle-o[]
|The stroke & icon width may be adjusted
|===

[cols="1,1,4", options="header"]
Expand Down
113 changes: 43 additions & 70 deletions src/component/mxgraph/shape/event-shapes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,44 +24,63 @@ const mxUtils: typeof mxgraph.mxUtils = MxGraphFactoryService.getMxGraphProperty
const mxConstants: typeof mxgraph.mxConstants = MxGraphFactoryService.getMxGraphProperty('mxConstants');

abstract class EventShape extends mxEllipse {
// when all/more event types will be supported, we could move to a Record/MappedType
private iconPainters: Map<ShapeBpmnEventKind, (c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number) => void> = new Map([
[ShapeBpmnEventKind.MESSAGE, (c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number) => this.paintMessageIcon(c, x, y, w, h)],
[ShapeBpmnEventKind.TERMINATE, (c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number) => this.paintTerminateIcon(c, x, y, w, h)],
]);
protected isUsingThrowIcons = false;

protected constructor(bounds: mxgraph.mxRectangle, fill: string, stroke: string, strokewidth: number) {
super(bounds, fill, stroke, strokewidth);
}

public paintVertexShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
this.markNonFullyRenderedEvents(c);
this.paintOuterShape(c, x, y, w, h);
this.paintInnerShape(c, x, y, w, h);
}

// This will be removed when managing the render of all events
private markNonFullyRenderedEvents(c: mxgraph.mxXmlCanvas2D): void {
const eventKind = this.getBpmnEventKind();
// will be removed when managing the timer rendering
if (eventKind == ShapeBpmnEventKind.TIMER) {
c.setFillColor('green');
c.setFillAlpha(0.3);
} // eslint-disable-next-line @typescript-eslint/no-use-before-define
else if (eventKind == ShapeBpmnEventKind.MESSAGE && (this instanceof StartEventShape || this instanceof EndEventShape)) {
c.setFillColor('yellow');
c.setFillAlpha(0.3);
}

this.paintOuterShape(c, x, y, w, h);
this.paintInnerShape(c, x, y, w, h);
}

protected paintOuterShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
super.paintVertexShape(c, x, y, w, h);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected paintInnerShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
// do nothing by default
const paintIcon =
this.iconPainters.get(this.getBpmnEventKind()) || ((c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number) => this.paintEmptyIcon(c, x, y, w, h));
paintIcon(c, x, y, w, h);
}

protected getBpmnEventKind(): ShapeBpmnEventKind {
private getBpmnEventKind(): ShapeBpmnEventKind {
return mxUtils.getValue(this.style, StyleConstant.BPMN_STYLE_EVENT_KIND, ShapeBpmnEventKind.NONE);
}

// TODO: will be removed when managing the message rendering
protected paintOuterMessageShape(c: mxgraph.mxXmlCanvas2D): void {
c.setFillColor('yellow');
c.setFillAlpha(0.3);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
private paintEmptyIcon(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
// empty by nature
}

// this implementation is adapted from the draw.io BPMN 'message' symbol
// https://github.com/jgraph/drawio/blob/0e19be6b42755790a749af30450c78c0d83be765/src/main/webapp/shapes/bpmn/mxBpmnShape2.js#L465
private paintMessageIcon(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number, isInverse = false): void {
private paintMessageIcon(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
if (!(this instanceof IntermediateEventShape)) {
return;
}
const isInverse = this.isUsingThrowIcons;
// Change the coordinate referential
c.translate(x + w * 0.24, y + h * 0.34);
w = w * 0.52;
Expand Down Expand Up @@ -111,41 +130,8 @@ abstract class EventShape extends mxEllipse {
c.stroke();
}

protected paintThrowMessageIcon(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
this.paintMessageIcon(c, x, y, w, h, true);
}
}

export class StartEventShape extends EventShape {
public constructor(bounds: mxgraph.mxRectangle, fill: string, stroke: string, strokewidth: number = StyleConstant.STROKE_WIDTH_THIN) {
super(bounds, fill, stroke, strokewidth);
}

// TODO: will be removed when managing the message rendering
protected paintOuterShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
const eventKind = this.getBpmnEventKind();
if (eventKind == ShapeBpmnEventKind.MESSAGE) {
this.paintOuterMessageShape(c);
}

super.paintOuterShape(c, x, y, w, h);
}
}

export class EndEventShape extends EventShape {
public constructor(bounds: mxgraph.mxRectangle, fill: string, stroke: string, strokewidth: number = StyleConstant.STROKE_WIDTH_THICK) {
super(bounds, fill, stroke, strokewidth);
}

protected paintInnerShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
const eventKind = this.getBpmnEventKind();
if (eventKind == ShapeBpmnEventKind.TERMINATE) {
this.paintTerminateEventIcon(c, x, y, w, h);
}
}

// highly inspired from mxDoubleEllipse
private paintTerminateEventIcon(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
private paintTerminateIcon(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
c.setFillColor(this.stroke);
c.setStrokeWidth(0);
const inset = mxUtils.getValue(this.style, mxConstants.STYLE_MARGIN, Math.min(3 + this.strokewidth, Math.min(w / 5, h / 5)));
Expand All @@ -160,15 +146,17 @@ export class EndEventShape extends EventShape {

c.fillAndStroke();
}
}

// TODO: will be removed when managing the message rendering
protected paintOuterShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
const eventKind = this.getBpmnEventKind();
if (eventKind == ShapeBpmnEventKind.MESSAGE) {
this.paintOuterMessageShape(c);
}
export class StartEventShape extends EventShape {
public constructor(bounds: mxgraph.mxRectangle, fill: string, stroke: string, strokewidth: number = StyleConstant.STROKE_WIDTH_THIN) {
super(bounds, fill, stroke, strokewidth);
}
}

super.paintOuterShape(c, x, y, w, h);
export class EndEventShape extends EventShape {
public constructor(bounds: mxgraph.mxRectangle, fill: string, stroke: string, strokewidth: number = StyleConstant.STROKE_WIDTH_THICK) {
super(bounds, fill, stroke, strokewidth);
}
}

Expand All @@ -193,26 +181,11 @@ export class CatchIntermediateEventShape extends IntermediateEventShape {
public constructor(bounds: mxgraph.mxRectangle, fill: string, stroke: string, strokewidth?: number) {
super(bounds, fill, stroke, strokewidth);
}

// TODO: will be removed when managing the message rendering
protected paintOuterShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
const eventKind = this.getBpmnEventKind();
if (eventKind == ShapeBpmnEventKind.MESSAGE) {
this.paintOuterMessageShape(c);
}

super.paintOuterShape(c, x, y, w, h);
}
}

export class ThrowIntermediateEventShape extends IntermediateEventShape {
public constructor(bounds: mxgraph.mxRectangle, fill: string, stroke: string, strokewidth?: number) {
super(bounds, fill, stroke, strokewidth);
}

protected paintInnerShape(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
const eventKind = this.getBpmnEventKind();
if (eventKind == ShapeBpmnEventKind.MESSAGE) {
this.paintThrowMessageIcon(c, x, y, w, h);
}
this.isUsingThrowIcons = true;
}
}
7 changes: 7 additions & 0 deletions test/e2e/View.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ describe('BPMN Visualization JS', () => {
<semantic:intermediateThrowEvent name="Throw Message Intermediate Event" id="messageIntermediateThrowEvent">
<semantic:messageEventDefinition />
</semantic:intermediateThrowEvent>
<semantic:intermediateCatchEvent name="Catch Message Intermediate Event" id="messageIntermediateCatchEvent">
<semantic:messageEventDefinition />
</semantic:intermediateCatchEvent>
<semantic:intermediateCatchEvent id="IntermediateCatchEvent_Timer_01" name="Timer Intermediate Catch Event">
<semantic:incoming>Flow_028jkgv</semantic:incoming>
<semantic:timerEventDefinition id="TimerEventDefinition_0t6k83a" />
Expand Down Expand Up @@ -114,6 +117,9 @@ describe('BPMN Visualization JS', () => {
<bpmndi:BPMNShape bpmnElement="messageIntermediateThrowEvent" id="S1373649849862_messageIntermediateThrowEvent">
<dc:Bounds height="32.0" width="32.0" x="698.0" y="335.0" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="messageIntermediateCatchEvent" id="S1373649849862_messageIntermediateCatchEvent">
<dc:Bounds height="32.0" width="32.0" x="98.0" y="335.0" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endEvent_1" id="S1373649849862_endEvent_1">
<dc:Bounds height="32.0" width="32.0" x="648.0" y="335.0"/>
<bpmndi:BPMNLabel labelStyle="LS1373649849858">
Expand Down Expand Up @@ -202,6 +208,7 @@ describe('BPMN Visualization JS', () => {
expectModelContainsBpmnEvent('endEvent_1', ShapeBpmnElementKind.EVENT_END, ShapeBpmnEventKind.TERMINATE);
expectModelContainsBpmnEvent('noneIntermediateThrowEvent', ShapeBpmnElementKind.EVENT_INTERMEDIATE_THROW, ShapeBpmnEventKind.NONE);
expectModelContainsBpmnEvent('messageIntermediateThrowEvent', ShapeBpmnElementKind.EVENT_INTERMEDIATE_THROW, ShapeBpmnEventKind.MESSAGE);
expectModelContainsBpmnEvent('messageIntermediateCatchEvent', ShapeBpmnElementKind.EVENT_INTERMEDIATE_CATCH, ShapeBpmnEventKind.MESSAGE);
expectModelContainsBpmnEvent('IntermediateCatchEvent_Timer_01', ShapeBpmnElementKind.EVENT_INTERMEDIATE_CATCH, ShapeBpmnEventKind.TIMER);
expectModelContainsCell('task_1', ShapeBpmnElementKind.TASK);
expectModelContainsCell('serviceTask_2', ShapeBpmnElementKind.TASK_SERVICE);
Expand Down