Skip to content

Commit

Permalink
feat(canvas): support multiple planes for rendering
Browse files Browse the repository at this point in the history
closes #560
  • Loading branch information
marstamm authored and philippfromme committed Sep 7, 2021
1 parent 44fd91e commit 91fde25
Show file tree
Hide file tree
Showing 4 changed files with 421 additions and 15 deletions.
172 changes: 160 additions & 12 deletions lib/core/Canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
every,
debounce,
bind,
reduce
reduce,
find
} from 'min-dash';

import {
Expand Down Expand Up @@ -39,6 +40,14 @@ function ensurePx(number) {
return isNumber(number) ? number + 'px' : number;
}

function findRoot(element) {
while (element.parent && element.parent !== element) {
element = element.parent;
}

return element;
}

/**
* Creates a HTML container element for a SVG element with
* the given configuration
Expand Down Expand Up @@ -84,6 +93,7 @@ function createGroup(parent, cls, childIndex) {
}

var BASE_LAYER = 'base';
var HIDDEN_MARKER = 'djs-element-hidden';


var REQUIRED_MODEL_ATTRS = {
Expand Down Expand Up @@ -148,6 +158,7 @@ Canvas.prototype._init = function(config) {
var viewport = this._viewport = createGroup(svg, 'viewport');

this._layers = {};
this._planes = {};

// debounce canvas.viewbox.changed events
// for smoother diagram interaction
Expand Down Expand Up @@ -206,7 +217,8 @@ Canvas.prototype._destroy = function(emit) {
delete this._svg;
delete this._container;
delete this._layers;
delete this._rootElement;
delete this._planes;
delete this._activePlane;
delete this._viewport;
};

Expand Down Expand Up @@ -238,7 +250,7 @@ Canvas.prototype._clear = function() {
* @returns {SVGElement}
*/
Canvas.prototype.getDefaultLayer = function() {
return this.getLayer(BASE_LAYER, 0);
return this.getPlane(BASE_LAYER).layer;
};

/**
Expand Down Expand Up @@ -306,6 +318,110 @@ Canvas.prototype._createLayer = function(name, index) {

};

/**
* Returns a plane that is used to draw elements on it.
*
* Non-existing planes retrieved through this method
* will be created.
*
* @param {string} name
*
* @return {Object} plane descriptor with { layer, rootElement, name }
*/
Canvas.prototype.getPlane = function(name) {
if (!name) {
throw new Error('must specify a name');
}

var plane = this._planes[name];

if (!plane) {
plane = this._planes[name] = this._createPlane(name);
}

return plane;
};

Canvas.prototype._createPlane = function(name) {
var svgLayer = this.getLayer(name);

svgClasses(svgLayer).add(HIDDEN_MARKER);

return {
layer: svgLayer,
rootElement: null,
name: name
};
};

/**
* Sets the active plane and hides the previously active plane.
*
* @param {string|Object} plane
*
* @return {Object} plane descriptor with { layer, rootElement, name }
*/
Canvas.prototype.setPlane = function(plane) {
if (!plane) {
throw new Error('must specify a plane');
}

if (typeof plane === 'string') {
plane = this.getPlane(plane);
}

// Hide previous Plane
if (this._activePlane) {
svgClasses(this._activePlane.layer).add(HIDDEN_MARKER);
}

this._activePlane = plane;

// Show current Plane
svgClasses(plane.layer).remove(HIDDEN_MARKER);

if (plane.rootElement) {
this._elementRegistry.updateGraphics(plane.rootElement, this._svg, true);
}

this._eventBus.fire('plane.set', { plane: plane });

return plane;
};

/**
* Returns the currently active plane.
*
* @return {Object} plane descriptor with { layer, rootElement, name }
*/
Canvas.prototype.getActivePlane = function() {
if (!this._activePlane) {
this._activePlane = this.getPlane(BASE_LAYER);
this.setPlane(BASE_LAYER);
}

return this._activePlane;
};

/**
* Returns the plane which contains the given element.
*
* @param {string|djs.model.Base} element
*
* @return {Object} plane descriptor with { layer, rootElement, name }
*/
Canvas.prototype.findPlane = function(element) {
if (typeof element === 'string') {
element = this._elementRegistry.get(element);
}

var root = findRoot(element);

return find(this._planes, function(plane) {
return plane.rootElement === root;
});
};

/**
* Returns the html element that encloses the
* drawing canvas.
Expand Down Expand Up @@ -427,11 +543,12 @@ Canvas.prototype.toggleMarker = function(element, marker) {
};

Canvas.prototype.getRootElement = function() {
if (!this._rootElement) {
this.setRootElement({ id: '__implicitroot', children: [] });
var plane = this.getActivePlane();
if (!plane.rootElement) {
this.setRootElement({ id: '__implicitroot' + plane.name, children: [] });
}

return this._rootElement;
return plane.rootElement;
};


Expand All @@ -448,12 +565,33 @@ Canvas.prototype.getRootElement = function() {
* @return {Object|djs.model.Root} new root element
*/
Canvas.prototype.setRootElement = function(element, override) {
var activePlane = this.getActivePlane();

return this.setRootElementForPlane(element, activePlane, override);
};


/**
* Sets a given element as the new root element for the canvas
* and returns the new root element.
*
* @param {Object|djs.model.Root} element
* @param {Object|djs.model.Root} plane
* @param {boolean} [override] whether to override the current root element, if any
*
* @return {Object|djs.model.Root} new root element
*/
Canvas.prototype.setRootElementForPlane = function(element, plane, override) {

if (typeof plane === 'string') {
plane = this.getPlane(plane);
}

if (element) {
this._ensureValid('root', element);
}

var currentRoot = this._rootElement,
var currentRoot = plane.rootElement,
elementRegistry = this._elementRegistry,
eventBus = this._eventBus;

Expand All @@ -470,23 +608,26 @@ Canvas.prototype.setRootElement = function(element, override) {
}

if (element) {
var gfx = this.getDefaultLayer();
var gfx = plane.layer;

// resemble element add event sequence
eventBus.fire('root.add', { element: element });

elementRegistry.add(element, gfx, this._svg);
elementRegistry.add(element, gfx);

eventBus.fire('root.added', { element: element, gfx: gfx });

// Associate SVG with root element when active
if (plane === this._activePlane) {
this._elementRegistry.updateGraphics(element, this._svg, true);
}
}

this._rootElement = element;
plane.rootElement = element;

return element;
};



// add functionality //////////////////////

Canvas.prototype._ensureValid = function(type, element) {
Expand Down Expand Up @@ -872,6 +1013,13 @@ Canvas.prototype.scroll = function(delta) {
*/
Canvas.prototype.scrollToElement = function(element, padding) {
var defaultPadding = 100;

// switch to correct Plane
var targetPlane = this.findPlane(element);
if (targetPlane !== this._activePlane) {
this.setPlane(targetPlane);
}

if (!padding) {
padding = {};
}
Expand Down
21 changes: 21 additions & 0 deletions lib/core/ElementRegistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,27 @@ ElementRegistry.prototype.updateId = function(element, newId) {
this.add(element, gfx, secondaryGfx);
};

/**
* Update the graphics of an element
*
* @param {djs.model.Base} element
* @param {SVGElement} gfx
* @param {boolean} [secondary=false] whether to update the secondary connected element
*/
ElementRegistry.prototype.updateGraphics = function(filter, gfx, secondary) {
var id = filter.id || filter;

var container = this._elements[id];

if (secondary) {
container.secondaryGfx = gfx;
} else {
container.gfx = gfx;
}

svgAttr(gfx, ELEMENT_ID, id);
};

/**
* Return the model element for a given id or graphics.
*
Expand Down
Loading

0 comments on commit 91fde25

Please sign in to comment.