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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ To build the project, see the [Contributing guide](CONTRIBUTING.md#Build) :sligh
Some BPMN icons used by `bpmn-visualization-js` are derived from existing projects. See the [BPMN Support page](docs/bpmn-support.adoc)
for more details:
- [draw.io](https://github.com/jgraph/drawio) (Apache-2.0)
- [primer octicons](https://github.com/primer/octicons) (MIT)
12 changes: 12 additions & 0 deletions docs/architecture/bpmn-support-how-to.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,15 @@ used for the rendering.

The `mxShape` can be a standard `mxGraph` class or a custom BPMN `mxShape` defined by the lib. The custom `mxShapes` are registered by `ShapeConfigurator`
which associates the `mxShape` name (used in style definition) with the `mxShape` class to be used.


===== BPMN icon tips

The icon of the BPMN elements must be defined in the mxGraph custom shapes and this currently must be done using `TypeScript`
code.

It is possible to adapt an SVG icon thanks to https://github.com/process-analytics/mxgraph-svg2shape[mxgraph-svg2shape],
a Java tool that will let you transform your SVG file into a set of `TypeScript` commands.

Please be aware that the tool is not able to support all SVG files, and you may need to adapt the SVG definition prior the
tool can transform it. See https://github.com/process-analytics/bpmn-visualization-js/pull/210[PR #210] for instance.
3 changes: 2 additions & 1 deletion docs/bpmn-support.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ The default rendering uses `white` as fill color and `black` as stroke color.

|User Task
|icon:flask[]
|Stroke color and icon are subject to change
|Icon will be subject to a large change +
*icon*: the task icon is currently derived from the https://github.com/primer/octicons/blob/638c6683c96ec4b357576c7897be8f19c933c052/icons/person.svg[primer octicons person svg]
|===


Expand Down
3 changes: 2 additions & 1 deletion src/component/mxgraph/ShapeConfigurator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { mxgraph } from 'ts-mxgraph';
import { ShapeBpmnElementKind } from '../../model/bpmn/shape/ShapeBpmnElementKind';
import { EndEventShape, StartEventShape } from './shape/event-shapes';
import { ExclusiveGatewayShape } from './shape/gateway-shapes';
import { ServiceTaskShape, TaskShape } from './shape/task-shapes';
import { ServiceTaskShape, TaskShape, UserTaskShape } from './shape/task-shapes';

export default class ShapeConfigurator {
private mxClient: typeof mxgraph.mxClient = MxGraphFactoryService.getMxGraphProperty('mxClient');
Expand All @@ -36,6 +36,7 @@ export default class ShapeConfigurator {
this.mxCellRenderer.registerShape(ShapeBpmnElementKind.GATEWAY_EXCLUSIVE, ExclusiveGatewayShape);
this.mxCellRenderer.registerShape(ShapeBpmnElementKind.TASK, TaskShape);
this.mxCellRenderer.registerShape(ShapeBpmnElementKind.TASK_SERVICE, ServiceTaskShape);
this.mxCellRenderer.registerShape(ShapeBpmnElementKind.TASK_USER, UserTaskShape);
}

private initMxShapePrototype(isFF: boolean): void {
Expand Down
13 changes: 0 additions & 13 deletions src/component/mxgraph/StyleConfigurator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ export default class StyleConfigurator {
this.configureEventsStyle();
// tasks
this.configureTasksStyle();
this.configureUserTaskStyle();
// gateways
this.configureGatewaysStyle();
this.configureParallelGatewayStyle();
Expand Down Expand Up @@ -118,18 +117,6 @@ export default class StyleConfigurator {
});
}

// TODO: to be removed as it will be configured in configureTasksStyle
// left just to not break current rendering
private configureUserTaskStyle(): void {
const style = this.cloneDefaultVertexStyle();
style[this.mxConstants.STYLE_SHAPE] = this.mxConstants.SHAPE_RECTANGLE;
style[this.mxConstants.STYLE_VERTICAL_ALIGN] = 'middle';
style[this.mxConstants.STYLE_STROKECOLOR] = '#2C6DA3';
style[this.mxConstants.STYLE_STROKEWIDTH] = 2;
style[this.mxConstants.STYLE_ROUNDED] = true;
this.putCellStyle(ShapeBpmnElementKind.TASK_USER, style);
}

// TODO: to be removed as it will be configured in configureGatewaysStyle
// left just to not break current rendering
private configureParallelGatewayStyle(): void {
Expand Down
10 changes: 9 additions & 1 deletion src/component/mxgraph/extension/MxScaleFactorCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { mxgraph } from 'ts-mxgraph';
* canvas.lineTo(12, 25);
*/
export default class MxScaleFactorCanvas {
constructor(readonly c: mxgraph.mxXmlCanvas2D, readonly scaleFactor: number) {}
constructor(private readonly c: mxgraph.mxXmlCanvas2D, readonly scaleFactor: number) {}

arcTo(rx: number, ry: number, angle: number, largeArcFlag: number, sweepFlag: number, x: number, y: number): void {
this.c.arcTo(rx * this.scaleFactor, ry * this.scaleFactor, angle, largeArcFlag, sweepFlag, x * this.scaleFactor, y * this.scaleFactor);
Expand All @@ -43,6 +43,14 @@ export default class MxScaleFactorCanvas {
this.c.close();
}

curveTo(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): void {
this.c.curveTo(x1 * this.scaleFactor, y1 * this.scaleFactor, x2 * this.scaleFactor, y2 * this.scaleFactor, x3 * this.scaleFactor, y3 * this.scaleFactor);
}

fill(): void {
this.c.fill();
}

fillAndStroke(): void {
this.c.fillAndStroke();
}
Expand Down
69 changes: 53 additions & 16 deletions src/component/mxgraph/shape/task-shapes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,23 @@ abstract class BaseTaskShape extends mxRectangleShape {
}

protected abstract paintTaskIcon(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void;

protected translateToStartingIconPosition(c: mxgraph.mxXmlCanvas2D, parentX: number, parentY: number, parentWidth: number, parentHeight: number): void {
const xTranslation = parentX + parentWidth / 20;
const yTranslation = parentY + parentHeight / 20;
c.translate(xTranslation, yTranslation);
}

protected configureCanvasForIcon(c: mxgraph.mxXmlCanvas2D, parentWidth: number, parentHeight: number, iconOriginalSize: number): MxScaleFactorCanvas {
// ensure we are not impacted by the configured shape stroke width
c.setStrokeWidth(1);

const parentSize = Math.min(parentWidth, parentHeight);
const ratioFromParent = 0.25;
const scaleFactor = (parentSize / iconOriginalSize) * ratioFromParent;

return new MxScaleFactorCanvas(c, scaleFactor);
}
}

export class TaskShape extends BaseTaskShape {
Expand All @@ -58,29 +75,17 @@ export class ServiceTaskShape extends BaseTaskShape {
// this implementation is adapted from the draw.io BPMN 'Service Task' stencil
// https://github.com/jgraph/drawio/blob/9394fb0f1430d2c869865827b2bbef5639f63478/src/main/webapp/stencils/bpmn.xml#L898
protected paintTaskIcon(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
const xTranslation = x + w / 20;
const yTranslation = y + h / 20;
c.translate(xTranslation, yTranslation);

// ensure we are not impacted by the configured shape stroke width
c.setStrokeWidth(1);
// icon coordinates fill a 100x100 rectangle (approximately: 90x90 + foreground translation)
const canvas = this.configureCanvasForIcon(c, w, h, 100);
this.translateToStartingIconPosition(c, x, y, w, h);

const parentSize = Math.min(w, h);
const ratioFromParent = 0.25;
// coordinates below fill a box of 100x100 (approximately: 90x90 + foreground translation)
const scaleFactor = (parentSize / 100) * ratioFromParent;

const canvas = new MxScaleFactorCanvas(c, scaleFactor);
// background
this.drawIconBackground(canvas);

// foreground
const foregroundTranslation = 14 * scaleFactor;
const foregroundTranslation = 14 * canvas.scaleFactor;
c.translate(foregroundTranslation, foregroundTranslation);
this.drawIconForeground(canvas);

// hack for translation that will be needed when managing task markers
// c.translate(-xTranslation, -yTranslation);
}

private drawIconBackground(canvas: MxScaleFactorCanvas): void {
Expand Down Expand Up @@ -178,3 +183,35 @@ export class ServiceTaskShape extends BaseTaskShape {
canvas.fillAndStroke();
}
}

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

// adapted from https://github.com/primer/octicons/blob/638c6683c96ec4b357576c7897be8f19c933c052/icons/person.svg
// use mxgraph svg2xml to generate the xml stencil and port it to code
protected paintTaskIcon(c: mxgraph.mxXmlCanvas2D, x: number, y: number, w: number, h: number): void {
// icon coordinates fill a 12x13 rectangle
const canvas = this.configureCanvasForIcon(c, w, h, 13);
this.translateToStartingIconPosition(c, x, y, w, h);

c.setFillColor(this.stroke);
canvas.begin();
canvas.moveTo(12, 13);
canvas.arcTo(1, 1, 0, 0, 1, 11, 14);
canvas.lineTo(1, 14);
canvas.arcTo(1, 1, 0, 0, 1, 0, 13);
canvas.lineTo(0, 12);
canvas.curveTo(0, 9.37, 4, 8, 4, 8);
canvas.curveTo(4, 8, 4.23, 8, 4, 8);
canvas.curveTo(3.16, 6.38, 3.06, 5.41, 3, 3);
canvas.curveTo(3.17, 0.59, 4.87, 0, 6, 0);
canvas.curveTo(7.13, 0, 8.83, 0.59, 9, 3);
canvas.curveTo(8.94, 5.41, 8.84, 6.38, 8, 8);
canvas.curveTo(8, 8, 12, 9.37, 12, 12);
canvas.lineTo(12, 13);
canvas.close();
canvas.fill();
}
}
11 changes: 6 additions & 5 deletions test/e2e/View.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,18 @@ describe('BPMN Visualization JS', () => {
<semantic:incoming>_d77dd5ec-e4e7-420e-bbe7-8ac9cd1df599</semantic:incoming>
<semantic:outgoing>_2aa47410-1b0e-4f8b-ad54-d6f798080cb4</semantic:outgoing>
</semantic:task>
<semantic:task completionQuantity="1" isForCompensation="false" startQuantity="1" name="Task 3" id="_e70a6fcb-913c-4a7b-a65d-e83adc73d69c">
<semantic:userTask completionQuantity="1" isForCompensation="false" startQuantity="1" name="Task 3" id="userTask_3">
<semantic:incoming>_2aa47410-1b0e-4f8b-ad54-d6f798080cb4</semantic:incoming>
<semantic:outgoing>_8e8fe679-eb3b-4c43-a4d6-891e7087ff80</semantic:outgoing>
</semantic:task>
</semantic:userTask>
<semantic:endEvent name="End Event" id="endEvent_1">
<semantic:incoming>_8e8fe679-eb3b-4c43-a4d6-891e7087ff80</semantic:incoming>
<semantic:terminateEventDefinition/>
</semantic:endEvent>
<semantic:sequenceFlow sourceRef="startEvent_1" targetRef="task_1" name="" id="_e16564d7-0c4c-413e-95f6-f668a3f851fb"/>
<semantic:sequenceFlow sourceRef="task_1" targetRef="serviceTask_2" name="" id="_d77dd5ec-e4e7-420e-bbe7-8ac9cd1df599"/>
<semantic:sequenceFlow sourceRef="serviceTask_2" targetRef="_e70a6fcb-913c-4a7b-a65d-e83adc73d69c" name="" id="_2aa47410-1b0e-4f8b-ad54-d6f798080cb4"/>
<semantic:sequenceFlow sourceRef="_e70a6fcb-913c-4a7b-a65d-e83adc73d69c" targetRef="endEvent_1" name="" id="_8e8fe679-eb3b-4c43-a4d6-891e7087ff80"/>
<semantic:sequenceFlow sourceRef="serviceTask_2" targetRef="userTask_3" name="" id="_2aa47410-1b0e-4f8b-ad54-d6f798080cb4"/>
<semantic:sequenceFlow sourceRef="userTask_3" targetRef="endEvent_1" name="" id="_8e8fe679-eb3b-4c43-a4d6-891e7087ff80"/>
</semantic:process>
<bpmndi:BPMNDiagram documentation="" id="Trisotech_Visio-_6" name="A.1.0" resolution="96.00000267028808">
<bpmndi:BPMNPlane bpmnElement="WFP-6-">
Expand All @@ -88,7 +88,7 @@ describe('BPMN Visualization JS', () => {
<dc:Bounds height="12.804751171875008" width="72.48293963254594" x="395.3333333333333" y="344.5818763825664"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_e70a6fcb-913c-4a7b-a65d-e83adc73d69c" id="S1373649849861__e70a6fcb-913c-4a7b-a65d-e83adc73d69c">
<bpmndi:BPMNShape bpmnElement="userTask_3" id="shape_userTask_3">
<dc:Bounds height="68.0" width="83.0" x="522.0" y="317.0"/>
<bpmndi:BPMNLabel labelStyle="LS1373649849858">
<dc:Bounds height="12.804751171875008" width="72.48293963254594" x="527.3333333333334" y="344.5818763825664"/>
Expand Down Expand Up @@ -171,6 +171,7 @@ describe('BPMN Visualization JS', () => {
expectModelContainsBpmnEvent('endEvent_1', ShapeBpmnElementKind.EVENT_END, ShapeBpmnEventKind.TERMINATE);
expectModelContainsCell('task_1', ShapeBpmnElementKind.TASK);
expectModelContainsCell('serviceTask_2', ShapeBpmnElementKind.TASK_SERVICE);
expectModelContainsCell('userTask_3', ShapeBpmnElementKind.TASK_USER);
});

function expectModelContainsCellWithGeometry(cellId: string, parentId: string, geometry: mxgraph.mxGeometry): void {
Expand Down