Skip to content

feat(dia.Graph, dia.Paper): cell layers #3025

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 72 commits into
base: master
Choose a base branch
from

Conversation

Geliogabalus
Copy link
Contributor

No description provided.

@kumilingus kumilingus requested a review from Copilot August 8, 2025 15:03
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a cell layers feature to the JointJS diagramming library that allows organizing cells into separate layers within the graph. The key changes include replacing the paper layer system with a more structured layer view architecture and adding cell layer management to the graph model.

  • Introduces CellLayer and CellLayerView classes to manage and visualize cell organization
  • Replaces the PaperLayer class with a generic LayerView class for better extensibility
  • Adds a CellLayersController to the Graph class for managing cell layers and their relationships

Reviewed Changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/joint-core/src/dia/Graph.mjs Adds cell layers controller and refactors cell collection management
packages/joint-core/src/dia/Paper.mjs Refactors layer management from paper layers to layer views with better organization
packages/joint-core/src/dia/Cell.mjs Adds layer() method for cell layer assignment and management
packages/joint-core/src/dia/layers/LayerView.mjs Replaces PaperLayer with generic LayerView class
packages/joint-core/src/dia/layers/CellLayerView.mjs New specialized layer view for managing cell collections
packages/joint-core/src/dia/groups/CellLayer.mjs New cell layer model for organizing cells
packages/joint-core/src/dia/controllers/CellLayersController.mjs New controller for managing cell layers in the graph
packages/joint-core/test/jointjs/layers/basic.js Test suite for new cell layers functionality

},

getCells: function() {

return this.get('cells').toArray();
// Preserve old order without layers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does old order mean? The reader would not know.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did comment it properly

getFirstCell: function(layerId) {
let cells;
if (!layerId) {
cells = this.getCells();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do this more efficiently?

this.cellLayersController.getCellLayer(firstLayerId).cells;

import { GridLayer } from './layers/GridLayer.mjs';
import { GridLayerView } from './layers/GridLayerView.mjs';

export const LAYERS = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we name enums with all upper-case letter anywhere?

@@ -437,7 +451,7 @@ export const Paper = View.extend({
};

// Render existing cells in the graph
this.resetViews(model.attributes.cells.models);
this.resetViews(model.cellCollection.models);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a breaking change? cellCollection.models is not sorted, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, indeed. Changed to model.getCells(), this one returns ordered models

} else {
layerView = this.getLayerView(cellLayer.id);
}
this.insertLayerView(layerView, LAYERS.LABELS);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Layers.LABELS? Can you add a comment to the code here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the comment. I am putting cell layers between back and labels layers, where cells layer is currently located

},

updateCellLayers: function(cellLayers) {
const removedCellLayerViewIds = this._cellLayers.filter(cellLayerView => !cellLayers.some(l => l.id === cellLayerView.id)).map(cellLayerView => cellLayerView.id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach to removing leftovers isn’t the most optimal, but given the expected number of layers, it should be sufficient. Please add a comment to the code to indicate that while this can be improved, it’s acceptable for our current use case.

getLayers() {
// Returns a sorted array of layer views.
return this.getLayerNames().map(name => this.getLayerView(name));
getOrderedLayerViews() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have getLayerViews() method that is unordered?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added getLayerViews() and getCellLayerViews methods.

if (!insertBefore) {
this._registerLayer(layerName, layerView, null);
// remove from order in case of a move command
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what is the move command?
It will be better to refer to it as in case we are changing position of an existing layer

},

resetLayers: function() {
resetLayerViews: function() {
const { _layers: { viewsMap }} = this;
Object.values(viewsMap).forEach(layerView => layerView.removePivots());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nice to have reset() method on LayerView (which will call removePivots() internally)

@@ -1791,7 +1854,16 @@ export const Paper = View.extend({
return this.model.getBBox() || new Rect();
}

return V(this.cells).getBBox();
const cellLayerViews = Object.values(this._cellLayerViews);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use Rect.fromRectUnion() for simplicity here?
Also, please add a comment what is being done here.

var zB = cellB.attributes.z || 0;
return (zA === zB) ? 0 : (zA < zB) ? -1 : 1;
});
Object.values(viewsMap).filter(view => view instanceof CellLayerView).forEach(view => view.sortExact());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have a higher-level API to get all CellLayerViews?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added getCellLayerViews() method

}
layerView.insertCellView(view);

this.trigger('cell:inserted', view, isInitialInsert);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should call it 'cell:insert' (we don't use added, changed either).

Or better 'cell:mount'. Analogous to the onMount() method.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or remove it completely, if it is not used in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in this PR

});
}

groupSet(key, val, opt) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the method if it is not used. Let's not commit to an API we are not sure about yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is used when I need to change layer attribute for all cells, when the default layer has changed

this.cells.remove(cell, opt);
}

reset() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method removes cell by cell and does not allow replacing cells with a new set.
A better name could be clear(). Similarly to graph.clear().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a clear method on mvc.Model, so I resorted to this naming


_onModelEvent(event, model, collection, options) {
if (model) {
if (event === 'changeId') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can refactor Collection.mjs if you need to partially preserved its behavious.

Why do we omit this line?

if ((event === 'add' || event === 'remove') && collection !== this) return;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment. It is done, so the CellGroup can emit add and remove events, because the collection is normally graph.cellCollection origin collection


_prepareModel(attrs, _options) {
if (this._isModel(attrs)) {
// do not change collection attribute of a cell model
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you omit this line?

if (!attrs.collection) attrs.collection = this;

These are low-level features and changing them must be properly explained in the code.

defaults() {
return {
type: 'CellLayer',
collectionConstructor: CellLayerCollection,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to store a JS class inside the model attributes (CellGroup is a model)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this attribute and refactored cellLayers attribute in a way that it stores all layer attributes similar to cells.

init() {
LayerView.prototype.init.apply(this, arguments);

// applying it here instead of using property
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we update the snapshot instead?

}
},

_requestViewUpdate(cell, opt) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not just an update request. This is a request for inserting / re-inserting.
This should be called _requestCellViewInsertion().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants