Skip to content

Commit

Permalink
[FEAT] Add a new API to zoom the BPMN diagram
Browse files Browse the repository at this point in the history
The API lets do zoom in and zoom out from the center of the bpmn-container.

Introduce the `Navigation` class that exposes all diagram navigation features. It also includes the existing `fit`
function. The former one that is defined in `BpmnVisualization` class is deprecated to not break compatibility. It will
be removed in a future release.

The demo and the diagram test page displays zoom buttons to demonstrate the usage of the new API.
In the demo page:
  - the zoom buttons replace the zoom debounce/throttle settings. They were only displayed for information and weren't
  useful for the end user.
  - some elements like labels could previously get focus. This is now fixed.

Refactor
  - In BpmnGraph, the `cumulativeZoomFactor` has been renamed into `currentZoomLevel` for clarification.
  Rationale: in mxGraph code, `factor` properties are not naming scale values, but a value used to multiply the scale.
  Here, we use something related to the scale, so we couldn't keep using the `factor` name.
  - In e2e tests, clarify the signature of the `pageTester.mouseZoom` utils method:
    - Use zoomType instead of `deltaX` values. Arbitrary `deltaX` values were used at several places. The value is too
    much tight to the mouse event implementation. The zoom type is now clearer and this reduces usage of duplicated
    constants.
    - Put the xTimes parameter at the end of the signature to allow default values. This also makes the signature
    consistent with the new `doZoomWithButton` function.
  • Loading branch information
tbouffard committed May 24, 2022
1 parent 6170330 commit b33cbd3
Show file tree
Hide file tree
Showing 19 changed files with 342 additions and 106 deletions.
14 changes: 14 additions & 0 deletions dev/public/diagram-navigation.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@

<!-- Zoom -->
<h5>Zoom</h5>
<div class="flex-column-container">
<button id="zoom-in" title="Zoom In">
<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M7,7 L5,7 L5,9 L7,9 L7,11 L9,11 L9,9 L11,9 L11,7 L9,7 L9,5 L7,5 L7,7 Z M12.9056439,14.3198574 C11.5509601,15.3729184 9.84871145,16 8,16 C3.581722,16 0,12.418278 0,8 C0,3.581722 3.581722,0 8,0 C12.418278,0 16,3.581722 16,8 C16,9.84871145 15.3729184,11.5509601 14.3198574,12.9056439 L19.6568542,18.2426407 L18.2426407,19.6568542 L12.9056439,14.3198574 Z M8,14 C11.3137085,14 14,11.3137085 14,8 C14,4.6862915 11.3137085,2 8,2 C4.6862915,2 2,4.6862915 2,8 C2,11.3137085 4.6862915,14 8,14 Z" fill="currentColor"/>
</svg>
</button>
</div>
<div class="flex-column-container">
<button id="zoom-out" title="Zoom Out">
<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M12.9056439,14.3198574 C11.5509601,15.3729184 9.84871145,16 8,16 C3.581722,16 0,12.418278 0,8 C0,3.581722 3.581722,0 8,0 C12.418278,0 16,3.581722 16,8 C16,9.84871145 15.3729184,11.5509601 14.3198574,12.9056439 L19.6568542,18.2426407 L18.2426407,19.6568542 L12.9056439,14.3198574 Z M8,14 C11.3137085,14 14,11.3137085 14,8 C14,4.6862915 11.3137085,2 8,2 C4.6862915,2 2,4.6862915 2,8 C2,11.3137085 4.6862915,14 8,14 Z M5,7 L11,7 L11,9 L5,9 L5,7 Z" fill="currentColor"/>
</svg>
</button>
</div>
<div class="flex-column-container">
<label for="zoom-throttle">throttle</label>
<input id="zoom-throttle" class="zoom-config" type="number" placeholder="thr" value="40" disabled
Expand Down
30 changes: 14 additions & 16 deletions dev/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<div class="bg-gray-800 shadow-xl w-full md:w-48">
<ul class="flex flex-row md:flex-col py-0 px-1 md:px-2">
<li class="md:mr-3">
<div class="py-1 md:pb-5 md:pt-3 pl-1 border-b-2 border-gray-800">
<div class="select-none py-1 md:pb-5 md:pt-3 pl-1 border-b-2 border-gray-800">
<div class="bg-gradient-to-b from-red-200 to-red-100 border-b-4 border-red-600 rounded-lg shadow-xl hover:border-red-900">
<div class="cursor-pointer text-red-500 hover:text-red-900">
<div class="text-center">
Expand All @@ -38,15 +38,15 @@
</div>
</div>
</div>
<a href="#" class="block pb-1 md:py-3 pl-1 text-white no-underline md:border-b-2 md:border-purple-500">
<div class="block pb-1 md:py-3 pl-1 text-white no-underline md:border-b-2 md:border-purple-500">
<label for="fitOnLoad">Fit on load:
<input type="checkbox" id="fitOnLoad" name="fitOnLoad" checked>
</label>
</a>
</div>
</li>
<li class="flex-1 md:mr-3">
<div class="py-1 md:py-3 pl-1 text-white md:border-b-2 md:border-red-500">
<a href="#" class="block no-underline md:pb-3 md:border-b-2 md:border-blue-600">
<div class="block no-underline md:pb-3 md:border-b-2 md:border-blue-600">
<label class="flex justify-evenly md:block">Fit type:
<select name="fitTypes" id="fitType-selected" class="w-1/2 md:w-full pl-1 md:pl-0 text-blue-900">
<option value="None">None</option>
Expand All @@ -56,25 +56,23 @@
<option value="Center" selected>Center</option>
</select>
</label>
</a>
<a href="#" class="block no-underline mt-3">
</div>
<div class="block no-underline mt-3">
<label for="fit-margin" class="flex justify-around">Fit margin:
<input type="number" id="fit-margin" class="w-1/3 pl-1 text-red-900 md:ml-auto" min="0" max="100" value="50" maxlength="3" oninput="validity.valid||(value='');">
</label>
</a>
</div>
</div>
</li>
<li class="flex-1 md:mr-3">
<div id="zoom-config-controls" class="py-1 md:py-3 pl-1 text-white md:border-b-2 md:border-yellow-500">
<label for="zoom-throttle" class="flex justify-center">
Throttle:
<input id="zoom-throttle" class="w-1/3 pl-1 ml-3 text-black md:ml-auto" placeholder="throttle" value="50" disabled
title="to play with throttle pass parameter in url like this: .../?zoomThrottle=40"/>
</label>
<label for="zoom-debounce" class="flex justify-center mt-3">Debounce:
<input id="zoom-debounce" class="w-1/3 pl-1 text-black md:ml-auto" placeholder="debounce" value="50" disabled
title="to play with debounce pass parameter in url like this: .../?zoomDebounce=30"/>
</label>
<div id="zoom" class="flex md:justify-between">
<span>Zoom:</span>
<span class="ml-4 md:ml-0">
<button id="zoom-in" title="Zoom In"><em class="fa-solid fa-magnifying-glass-plus"></em></button>
<button id="zoom-out" title="Zoom Out"><em class="ml-4 fa-solid fa-magnifying-glass-minus"></em></button>
</span>
</div>
</div>
</li>
</ul>
Expand Down
15 changes: 9 additions & 6 deletions dev/public/static/js/diagram-navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@
* limitations under the License.
*/

import { documentReady, startBpmnVisualization, fit, FitType } from '../../index.es.js';
import { documentReady, startBpmnVisualization, fit, FitType, zoom } from '../../index.es.js';
import { configureControlsPanel, configureMousePointer } from './helpers/controls.js';

function fitOnClick(fitType) {
document.getElementById(fitType).onclick = () => fit({ type: fitType });
function configureFitAndZoomButtons() {
Object.values(FitType).forEach(fitType => {
document.getElementById(fitType).onclick = () => fit({ type: fitType });
});
['in', 'out'].forEach(zoomType => {
document.getElementById(`zoom-${zoomType}`).onclick = () => zoom(zoomType);
});
}

function configureZoomThrottleInput(parameters) {
Expand Down Expand Up @@ -58,9 +63,7 @@ function start() {
},
});

for (let fitTypeElement in FitType) {
fitOnClick(fitTypeElement);
}
configureFitAndZoomButtons();
}

documentReady(start);
25 changes: 8 additions & 17 deletions dev/public/static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { documentReady, handleFileSelect, startBpmnVisualization, fit, log, updateLoadOptions, getCurrentLoadOptions, getVersion } from '../../index.es.js';
import { documentReady, handleFileSelect, startBpmnVisualization, fit, log, updateLoadOptions, getCurrentLoadOptions, getVersion, zoom } from '../../index.es.js';

let fitOnLoad = true;
let fitOptions = {};
Expand Down Expand Up @@ -70,6 +70,12 @@ function configureFitMarginInput() {
}
}

function configureZoomButtons() {
['in', 'out'].forEach(zoomType => {
document.getElementById(`zoom-${zoomType}`).onclick = () => zoom(zoomType);
});
}

function configureDisplayedFooterContent() {
const version = getVersion();
const versionAsString = `bpmn-visualization@${version.lib}`;
Expand All @@ -95,27 +101,11 @@ function startDemo() {
preventZoomingPage();
const bpmnContainerId = 'bpmn-container';

const parameters = new URLSearchParams(window.location.search);
const zoomThrottleElt = document.getElementById('zoom-throttle'),
zoomDebounceElt = document.getElementById('zoom-debounce'),
zoomControlsElt = document.getElementById('zoom-config-controls');
if (parameters.get('zoomThrottle')) {
zoomControlsElt.style = 'visibility: visible';
zoomThrottleElt.value = parameters.get('zoomThrottle');
}
if (parameters.get('zoomDebounce')) {
zoomControlsElt.style = 'visibility: visible';
zoomDebounceElt.value = parameters.get('zoomDebounce');
}
startBpmnVisualization({
globalOptions: {
container: bpmnContainerId,
navigation: {
enabled: true,
zoom: {
throttleDelay: zoomThrottleElt.value,
debounceDelay: zoomDebounceElt.value,
},
},
},
});
Expand All @@ -127,6 +117,7 @@ function startDemo() {
configureFitTypeSelect();
configureFitMarginInput();
configureFitOnLoadCheckBox();
configureZoomButtons();
configureDisplayedFooterContent();
}

Expand Down
14 changes: 10 additions & 4 deletions dev/ts/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import type { BpmnElement, BpmnElementKind, FitOptions, FitType, GlobalOptions, LoadOptions, Overlay, Version } from '../../src/bpmn-visualization';
import type { BpmnElement, BpmnElementKind, FitOptions, FitType, GlobalOptions, LoadOptions, Overlay, Version, ZoomType } from '../../src/bpmn-visualization';
import { log, logDownload, logErrorAndOpenAlert, logStartup } from './helper';
import { DropFileUserInterface } from './component/DropFileUserInterface';
import { SvgExporter } from './component/SvgExporter';
Expand Down Expand Up @@ -42,7 +42,7 @@ function stringify(value: unknown): string {
}

function loadBpmn(bpmn: string): void {
log('Loading bpmn....');
log('Loading bpmn...');
try {
bpmnVisualization.load(bpmn, loadOptions);
log('BPMN loaded with configuration', stringify(loadOptions));
Expand All @@ -54,11 +54,17 @@ function loadBpmn(bpmn: string): void {
}

export function fit(fitOptions: FitOptions): void {
log('Fitting....');
bpmnVisualization.fit(fitOptions);
log('Fitting...');
bpmnVisualization.navigation.fit(fitOptions);
log('Fit done with configuration', stringify(fitOptions));
}

export function zoom(zoomType: ZoomType): void {
log(`Zooming '${zoomType}'...`);
bpmnVisualization.navigation.zoom(zoomType);
log('Zoom done');
}

export function getElementsByKinds(bpmnKinds: BpmnElementKind | BpmnElementKind[]): BpmnElement[] {
return bpmnVisualization.bpmnElementsRegistry.getElementsByKinds(bpmnKinds);
}
Expand Down
1 change: 1 addition & 0 deletions src/bpmn-visualization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export * from './component/options';
export { BpmnVisualization } from './component/BpmnVisualization';
export * from './component/registry';
export type { Version } from './component/version';
export type { Navigation } from './component/navigation';
export * from './model/bpmn/internal';

// not part of the public API but needed for the custom theme examples
Expand Down
13 changes: 12 additions & 1 deletion src/component/BpmnVisualization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type { BpmnElementsRegistry } from './registry';
import { newBpmnElementsRegistry } from './registry/bpmn-elements-registry';
import { BpmnModelRegistry } from './registry/bpmn-model-registry';
import { htmlElement } from './helpers/dom-utils';
import { Navigation } from './navigation';
import { version, type Version } from './version';

/**
Expand All @@ -42,6 +43,12 @@ export class BpmnVisualization {
*/
readonly graph: BpmnGraph;

/**
* Perform BPMN diagram navigation.
* @experimental subject to change, feedback welcome.
*/
readonly navigation: Navigation;

/**
* Interact with BPMN diagram elements rendered in the page.
* @experimental subject to change, feedback welcome.
Expand All @@ -55,6 +62,7 @@ export class BpmnVisualization {
const configurator = new GraphConfigurator(htmlElement(options?.container));
this.graph = configurator.configure(options);
// other configurations
this.navigation = new Navigation(this.graph);
this.bpmnModelRegistry = new BpmnModelRegistry();
this.bpmnElementsRegistry = newBpmnElementsRegistry(this.bpmnModelRegistry, this.graph);
}
Expand All @@ -71,8 +79,11 @@ export class BpmnVisualization {
newBpmnRenderer(this.graph).render(renderedModel, options);
}

/**
* @deprecated Starting from version `0.24.0`, use `navigation.fit` instead. This method may be removed in version `0.27.0`.
*/
fit(options?: FitOptions): void {
this.graph.customFit(options);
this.navigation.fit(options);
}

getVersion(): Version {
Expand Down
42 changes: 34 additions & 8 deletions src/component/mxgraph/BpmnGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,18 @@ import throttle from 'lodash.throttle';
import { mxgraph } from './initializer';
import type { mxCellState, mxGraphView, mxPoint } from 'mxgraph';

const zoomFactorIn = 1.25;
const zoomFactorOut = 1 / zoomFactorIn;

export class BpmnGraph extends mxgraph.mxGraph {
private cumulativeZoomFactor = 1;
private currentZoomLevel = 1;

/**
* @internal
*/
constructor(container: HTMLElement) {
super(container);
this.zoomFactor = zoomFactorIn;
if (this.container) {
// ensure we don't have a select text cursor on label hover, see #294
this.container.style.cursor = 'default';
Expand All @@ -44,22 +48,44 @@ export class BpmnGraph extends mxgraph.mxGraph {
}

/**
* Overridden to set initial cumulativeZoomFactor
* Overridden to manage `currentZoomLevel`
* @internal
*/
override fit(border: number, keepOrigin?: boolean, margin?: number, enabled?: boolean, ignoreWidth?: boolean, ignoreHeight?: boolean, maxHeight?: number): number {
const scale = super.fit(border, keepOrigin, margin, enabled, ignoreWidth, ignoreHeight, maxHeight);
this.cumulativeZoomFactor = scale;
this.setCurrentZoomLevel(scale);
return scale;
}

private setCurrentZoomLevel(scale?: number): void {
this.currentZoomLevel = scale ?? this.view.scale;
}

/**
* Overridden to set initial cumulativeZoomFactor
* Overridden to manage `currentZoomLevel`
* @internal
*/
override zoomActual(): void {
super.zoomActual();
this.cumulativeZoomFactor = this.view.scale;
this.setCurrentZoomLevel();
}

/**
* Overridden to manage `currentZoomLevel`
* @internal
*/
override zoomIn(): void {
super.zoomIn();
this.setCurrentZoomLevel();
}

/**
* Overridden to manage `currentZoomLevel`
* @internal
*/
override zoomOut(): void {
super.zoomOut();
this.setCurrentZoomLevel();
}

/**
Expand Down Expand Up @@ -99,7 +125,7 @@ export class BpmnGraph extends mxgraph.mxGraph {
const width = bounds.width / this.view.scale;
const height = bounds.height / this.view.scale;
const scale = Math.min(maxScale, Math.min(clientWidth / width, clientHeight / height));
this.cumulativeZoomFactor = scale;
this.setCurrentZoomLevel(scale);

this.view.scaleAndTranslate(
scale,
Expand Down Expand Up @@ -185,8 +211,8 @@ export class BpmnGraph extends mxgraph.mxGraph {

private calculateFactorAndScale(up: boolean): [number, number] {
// as with new zoom scaling is invoked 2x the factor's square root is taken
this.cumulativeZoomFactor *= up ? Math.sqrt(1.25) : Math.sqrt(0.8);
let factor = this.cumulativeZoomFactor / this.view.scale;
this.currentZoomLevel *= Math.sqrt(up ? zoomFactorIn : zoomFactorOut);
let factor = this.currentZoomLevel / this.view.scale;
const scale = Math.round(this.view.scale * factor * 100) / 100;
factor = scale / this.view.scale;
return [factor, scale];
Expand Down
35 changes: 35 additions & 0 deletions src/component/navigation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Copyright 2022 Bonitasoft S.A.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { BpmnGraph } from './mxgraph/BpmnGraph';
import type { FitOptions, ZoomType } from './options';

/**
* Perform BPMN diagram navigation.
* @category Navigation
* @experimental subject to change, feedback welcome.
*/
export class Navigation {
constructor(private readonly graph: BpmnGraph) {}

fit(options?: FitOptions): void {
this.graph.customFit(options);
}

zoom(type: ZoomType): void {
type == 'in' ? this.graph.zoomIn() : this.graph.zoomOut();
}
}
Loading

0 comments on commit b33cbd3

Please sign in to comment.