@eclipse-glsp/layout-elk 0.9.0-next.66aad6e.3 → 0.9.0-next.95961ba.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/di.config.d.ts +7 -13
- package/lib/di.config.d.ts.map +1 -1
- package/lib/di.config.js +20 -32
- package/lib/di.config.js.map +1 -1
- package/lib/di.config.spec.js +13 -16
- package/lib/di.config.spec.js.map +1 -1
- package/lib/element-filter.d.ts +45 -0
- package/lib/element-filter.d.ts.map +1 -0
- package/lib/element-filter.js +84 -0
- package/lib/element-filter.js.map +1 -0
- package/lib/glsp-elk-layout-engine.d.ts +34 -20
- package/lib/glsp-elk-layout-engine.d.ts.map +1 -1
- package/lib/glsp-elk-layout-engine.js +265 -42
- package/lib/glsp-elk-layout-engine.js.map +1 -1
- package/lib/index.d.ts +4 -6
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -7
- package/lib/index.js.map +1 -1
- package/lib/{glsp-layout-configurator.d.ts → layout-configurator.d.ts} +29 -15
- package/lib/layout-configurator.d.ts.map +1 -0
- package/lib/layout-configurator.js +99 -0
- package/lib/layout-configurator.js.map +1 -0
- package/lib/{basic-type-mapper.d.ts → sprotty-elk.d.ts} +3 -11
- package/lib/sprotty-elk.d.ts.map +1 -0
- package/lib/sprotty-elk.js +5 -0
- package/lib/sprotty-elk.js.map +1 -0
- package/package.json +3 -3
- package/src/di.config.spec.ts +15 -18
- package/src/di.config.ts +29 -42
- package/src/element-filter.ts +84 -0
- package/src/glsp-elk-layout-engine.ts +305 -30
- package/src/index.ts +4 -6
- package/src/layout-configurator.ts +101 -0
- package/src/sprotty-elk.ts +18 -0
- package/lib/basic-type-mapper.d.ts.map +0 -1
- package/lib/basic-type-mapper.js +0 -97
- package/lib/basic-type-mapper.js.map +0 -1
- package/lib/glsp-element-filter.d.ts +0 -17
- package/lib/glsp-element-filter.d.ts.map +0 -1
- package/lib/glsp-element-filter.js +0 -53
- package/lib/glsp-element-filter.js.map +0 -1
- package/lib/glsp-layout-configurator.d.ts.map +0 -1
- package/lib/glsp-layout-configurator.js +0 -57
- package/lib/glsp-layout-configurator.js.map +0 -1
- package/src/basic-type-mapper.ts +0 -73
- package/src/glsp-element-filter.ts +0 -38
- package/src/glsp-layout-configurator.ts +0 -59
package/src/di.config.spec.ts
CHANGED
|
@@ -13,23 +13,21 @@
|
|
|
13
13
|
*
|
|
14
14
|
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
15
|
********************************************************************************/
|
|
16
|
-
import { DefaultModelState,
|
|
16
|
+
import { DefaultModelState, GGraph, GModelElementConstructor, ModelState } from '@eclipse-glsp/server-node';
|
|
17
17
|
import { StubDiagramConfiguration } from '@eclipse-glsp/server-node/lib/test/mock-util';
|
|
18
18
|
import { expect } from 'chai';
|
|
19
19
|
import { Container, ContainerModule, injectable } from 'inversify';
|
|
20
20
|
import * as sinon from 'sinon';
|
|
21
|
-
import {
|
|
22
|
-
import { SModelIndex } from 'sprotty-protocol';
|
|
23
|
-
import { GlspLayoutConfigurator } from '.';
|
|
21
|
+
import { AbstractLayoutConfigurator } from '.';
|
|
24
22
|
import { configureELKLayoutModule } from './di.config';
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
23
|
+
import { DefaultElementFilter, ElementFilter } from './element-filter';
|
|
24
|
+
import { FallbackLayoutConfigurator, LayoutConfigurator } from './layout-configurator';
|
|
27
25
|
|
|
28
26
|
@injectable()
|
|
29
|
-
class CustomLayoutConfigurator extends
|
|
27
|
+
class CustomLayoutConfigurator extends AbstractLayoutConfigurator {}
|
|
30
28
|
|
|
31
29
|
@injectable()
|
|
32
|
-
class CustomElementFilter extends
|
|
30
|
+
class CustomElementFilter extends DefaultElementFilter {}
|
|
33
31
|
|
|
34
32
|
describe('test configureELKLayoutModule', () => {
|
|
35
33
|
const sandbox = sinon.createSandbox();
|
|
@@ -40,7 +38,6 @@ describe('test configureELKLayoutModule', () => {
|
|
|
40
38
|
sandbox.stub(mockDiagramConfiguration, 'typeMapping').value(typeMappings);
|
|
41
39
|
const modelState = new DefaultModelState();
|
|
42
40
|
const baseModule = new ContainerModule(bind => {
|
|
43
|
-
bind(DiagramConfiguration).toConstantValue(mockDiagramConfiguration);
|
|
44
41
|
bind(ModelState).toConstantValue(modelState);
|
|
45
42
|
});
|
|
46
43
|
it('configure with minimal options', () => {
|
|
@@ -48,11 +45,11 @@ describe('test configureELKLayoutModule', () => {
|
|
|
48
45
|
const elkModule = configureELKLayoutModule({ algorithms: [algorithm] });
|
|
49
46
|
const container = new Container();
|
|
50
47
|
container.load(baseModule, elkModule);
|
|
51
|
-
const filter = container.get<
|
|
52
|
-
expect(filter).to.be.instanceOf(
|
|
53
|
-
const configurator = container.get<
|
|
54
|
-
expect(configurator).to.be.instanceOf(
|
|
55
|
-
const graphOptions = configurator.apply(new GGraph()
|
|
48
|
+
const filter = container.get<ElementFilter>(ElementFilter);
|
|
49
|
+
expect(filter).to.be.instanceOf(DefaultElementFilter);
|
|
50
|
+
const configurator = container.get<LayoutConfigurator>(LayoutConfigurator);
|
|
51
|
+
expect(configurator).to.be.instanceOf(FallbackLayoutConfigurator);
|
|
52
|
+
const graphOptions = configurator.apply(new GGraph());
|
|
56
53
|
expect(graphOptions).not.to.be.undefined;
|
|
57
54
|
expect(graphOptions!['elk.algorithm']).to.equal(algorithm);
|
|
58
55
|
});
|
|
@@ -66,8 +63,8 @@ describe('test configureELKLayoutModule', () => {
|
|
|
66
63
|
const elkModule = configureELKLayoutModule({ algorithms: [algorithm], defaultLayoutOptions });
|
|
67
64
|
const container = new Container();
|
|
68
65
|
container.load(baseModule, elkModule);
|
|
69
|
-
const configurator = container.get<
|
|
70
|
-
const graphOptions = configurator.apply(new GGraph()
|
|
66
|
+
const configurator = container.get<LayoutConfigurator>(LayoutConfigurator);
|
|
67
|
+
const graphOptions = configurator.apply(new GGraph());
|
|
71
68
|
expect(graphOptions).not.to.be.undefined;
|
|
72
69
|
expect(graphOptions).to.include(defaultLayoutOptions);
|
|
73
70
|
});
|
|
@@ -77,7 +74,7 @@ describe('test configureELKLayoutModule', () => {
|
|
|
77
74
|
const elkModule = configureELKLayoutModule({ algorithms: [algorithm], layoutConfigurator: CustomLayoutConfigurator });
|
|
78
75
|
const container = new Container();
|
|
79
76
|
container.load(baseModule, elkModule);
|
|
80
|
-
const configurator = container.get<
|
|
77
|
+
const configurator = container.get<LayoutConfigurator>(LayoutConfigurator);
|
|
81
78
|
expect(configurator).to.be.instanceOf(CustomLayoutConfigurator);
|
|
82
79
|
});
|
|
83
80
|
|
|
@@ -86,7 +83,7 @@ describe('test configureELKLayoutModule', () => {
|
|
|
86
83
|
const elkModule = configureELKLayoutModule({ algorithms: [algorithm], elementFilter: CustomElementFilter });
|
|
87
84
|
const container = new Container();
|
|
88
85
|
container.load(baseModule, elkModule);
|
|
89
|
-
const filter = container.get<
|
|
86
|
+
const filter = container.get<ElementFilter>(ElementFilter);
|
|
90
87
|
expect(filter).to.be.instanceOf(CustomElementFilter);
|
|
91
88
|
});
|
|
92
89
|
});
|
package/src/di.config.ts
CHANGED
|
@@ -13,15 +13,14 @@
|
|
|
13
13
|
*
|
|
14
14
|
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
15
|
********************************************************************************/
|
|
16
|
-
import { LayoutEngine } from '@eclipse-glsp/server-node
|
|
16
|
+
import { LayoutEngine, ModelState } from '@eclipse-glsp/server-node';
|
|
17
17
|
import ElkConstructor, { LayoutOptions } from 'elkjs/lib/elk.bundled';
|
|
18
18
|
import { ContainerModule } from 'inversify';
|
|
19
|
-
import { ElkFactory
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { FallbackGlspLayoutConfigurator, GlspLayoutConfigurator } from './glsp-layout-configurator';
|
|
19
|
+
import { ElkFactory } from 'sprotty-elk';
|
|
20
|
+
import { ElementFilter, LayoutConfigurator } from '.';
|
|
21
|
+
import { DefaultElementFilter } from './element-filter';
|
|
22
|
+
import { GlspElkLayoutEngine } from './glsp-elk-layout-engine';
|
|
23
|
+
import { FallbackLayoutConfigurator } from './layout-configurator';
|
|
25
24
|
|
|
26
25
|
type Constructor<T> = new (...args: any[]) => T;
|
|
27
26
|
|
|
@@ -39,15 +38,15 @@ export interface ElkModuleOptions {
|
|
|
39
38
|
*/
|
|
40
39
|
defaultLayoutOptions?: LayoutOptions;
|
|
41
40
|
/**
|
|
42
|
-
* The custom {@link
|
|
43
|
-
* the {@link
|
|
41
|
+
* The custom {@link LayoutConfigurator} class that should be bound. If this option is not set
|
|
42
|
+
* the {@link FallbackLayoutConfigurator} is bound instead.
|
|
44
43
|
*/
|
|
45
|
-
layoutConfigurator?: Constructor<
|
|
44
|
+
layoutConfigurator?: Constructor<LayoutConfigurator>;
|
|
46
45
|
/**
|
|
47
|
-
* The custom {@link
|
|
46
|
+
* The custom {@link ElementFilter} class that should be bound. If this option is not set, the default implementation
|
|
48
47
|
* is bound.
|
|
49
48
|
*/
|
|
50
|
-
elementFilter?: Constructor<
|
|
49
|
+
elementFilter?: Constructor<ElementFilter>;
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
/**
|
|
@@ -58,13 +57,8 @@ export interface ElkModuleOptions {
|
|
|
58
57
|
* The constructed module is not intended for standalone use cases and only works in combination with a GLSPDiagramModule.
|
|
59
58
|
*
|
|
60
59
|
* * The following bindings are provided:
|
|
61
|
-
* - {@link BasicTypeMapper}
|
|
62
|
-
* - {@link BaseElkLayoutEngine}
|
|
63
|
-
* - {@link ElkLayoutEngine}
|
|
64
|
-
* - {@link GlspElementFilter}
|
|
65
|
-
* - {@link IElementFilter}
|
|
66
|
-
* - {@link GlspLayoutConfigurator}
|
|
67
60
|
* - {@link ILayoutConfigurator}
|
|
61
|
+
* - {@link IElementFilter}
|
|
68
62
|
* - {@link LayoutEngine}
|
|
69
63
|
* - {@link ElkFactory}
|
|
70
64
|
*
|
|
@@ -73,38 +67,19 @@ export interface ElkModuleOptions {
|
|
|
73
67
|
*/
|
|
74
68
|
export function configureELKLayoutModule(options: ElkModuleOptions): ContainerModule {
|
|
75
69
|
return new ContainerModule(bind => {
|
|
76
|
-
bind(BasicTypeMapper).toSelf().inSingletonScope();
|
|
77
|
-
bind(BaseElkLayoutEngine)
|
|
78
|
-
.toDynamicValue(context => {
|
|
79
|
-
const elkFactory = context.container.get<ElkFactory>(ElkFactory);
|
|
80
|
-
const elementFilter = context.container.get<IElementFilter>(IElementFilter);
|
|
81
|
-
const layoutConfigurator = context.container.get<ILayoutConfigurator>(ILayoutConfigurator);
|
|
82
|
-
const typeMapper = context.container.get<BasicTypeMapper>(BasicTypeMapper);
|
|
83
|
-
return new BaseElkLayoutEngine(elkFactory, elementFilter, layoutConfigurator, typeMapper);
|
|
84
|
-
})
|
|
85
|
-
.inSingletonScope();
|
|
86
|
-
|
|
87
|
-
bind(ElkLayoutEngine).to(BaseElkLayoutEngine);
|
|
88
|
-
|
|
89
70
|
if (options.elementFilter) {
|
|
90
|
-
bind(
|
|
71
|
+
bind(ElementFilter).to(options.elementFilter).inSingletonScope();
|
|
91
72
|
} else {
|
|
92
|
-
bind(
|
|
73
|
+
bind(ElementFilter).to(DefaultElementFilter).inSingletonScope();
|
|
93
74
|
}
|
|
94
|
-
bind(IElementFilter).toService(GlspElementFilter);
|
|
95
75
|
|
|
96
76
|
if (options.layoutConfigurator) {
|
|
97
|
-
bind(
|
|
77
|
+
bind(LayoutConfigurator).to(options.layoutConfigurator);
|
|
98
78
|
} else {
|
|
99
|
-
bind(
|
|
100
|
-
.toDynamicValue(context =>
|
|
101
|
-
const typeMapper = context.container.get<BasicTypeMapper>(BasicTypeMapper);
|
|
102
|
-
return new FallbackGlspLayoutConfigurator(typeMapper, options.algorithms, options.defaultLayoutOptions);
|
|
103
|
-
})
|
|
79
|
+
bind(LayoutConfigurator)
|
|
80
|
+
.toDynamicValue(context => new FallbackLayoutConfigurator(options.algorithms, options.defaultLayoutOptions))
|
|
104
81
|
.inSingletonScope();
|
|
105
82
|
}
|
|
106
|
-
bind(ILayoutConfigurator).toService(GlspLayoutConfigurator);
|
|
107
|
-
bind(LayoutEngine).to(GlspElkLayoutEngine).inSingletonScope();
|
|
108
83
|
|
|
109
84
|
const elkFactory: ElkFactory = () =>
|
|
110
85
|
new ElkConstructor({
|
|
@@ -113,5 +88,17 @@ export function configureELKLayoutModule(options: ElkModuleOptions): ContainerMo
|
|
|
113
88
|
});
|
|
114
89
|
|
|
115
90
|
bind(ElkFactory).toConstantValue(elkFactory);
|
|
91
|
+
|
|
92
|
+
bind(GlspElkLayoutEngine)
|
|
93
|
+
.toDynamicValue(context => {
|
|
94
|
+
const container = context.container;
|
|
95
|
+
const factory = container.get<ElkFactory>(ElkFactory);
|
|
96
|
+
const filter = container.get<ElementFilter>(ElementFilter);
|
|
97
|
+
const configurator = container.get<LayoutConfigurator>(LayoutConfigurator);
|
|
98
|
+
const modelState = container.get<ModelState>(ModelState);
|
|
99
|
+
return new GlspElkLayoutEngine(factory, filter, configurator, modelState);
|
|
100
|
+
})
|
|
101
|
+
.inSingletonScope();
|
|
102
|
+
bind(LayoutEngine).toService(GlspElkLayoutEngine);
|
|
116
103
|
});
|
|
117
104
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2018-2022 TypeFox and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
* http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
*
|
|
8
|
+
* This Source Code may also be made available under the following Secondary
|
|
9
|
+
* Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
* with the GNU Classpath Exception which is available at
|
|
12
|
+
* https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
*
|
|
14
|
+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
|
+
********************************************************************************/
|
|
16
|
+
import { GEdge, GLabel, GModelElement, GNode, GPort, ModelState } from '@eclipse-glsp/server-node';
|
|
17
|
+
import { inject, injectable } from 'inversify';
|
|
18
|
+
|
|
19
|
+
export const ElementFilter = Symbol('ElementFilter');
|
|
20
|
+
/**
|
|
21
|
+
* Filter used to determine which model elements should be included in the automatic layout.
|
|
22
|
+
*/
|
|
23
|
+
export interface ElementFilter {
|
|
24
|
+
/**
|
|
25
|
+
* Applies the element filter on the given element,
|
|
26
|
+
* @param element The element on which the filter should be applied.
|
|
27
|
+
* @returns `true` if the element should be included in the automatic layout, `false` otherwise.
|
|
28
|
+
*/
|
|
29
|
+
apply(element: GModelElement): boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Default implementation of {@link ElementFilter}.
|
|
34
|
+
* Without further configuration this filter includes all basic model elements (nodes,edges,labels,ports) that are
|
|
35
|
+
* part of the graphical model. For each basic type a custom filter behavior is in place.
|
|
36
|
+
* Edges that have no source or target are filtered out. In addition, edges that are connected to a filtered element are filtered out
|
|
37
|
+
* as well. The filter behavior for each of the basic types can be customized by overriding the corresponding `filter` method.
|
|
38
|
+
* (e.g. {@link DefaultElementFilter.filterNode})
|
|
39
|
+
*/
|
|
40
|
+
@injectable()
|
|
41
|
+
export class DefaultElementFilter implements ElementFilter {
|
|
42
|
+
@inject(ModelState)
|
|
43
|
+
protected modelState: ModelState;
|
|
44
|
+
|
|
45
|
+
apply(element: GModelElement): boolean {
|
|
46
|
+
if (element instanceof GNode) {
|
|
47
|
+
return this.filterNode(element);
|
|
48
|
+
} else if (element instanceof GEdge) {
|
|
49
|
+
return this.filterEdge(element);
|
|
50
|
+
} else if (element instanceof GLabel) {
|
|
51
|
+
this.filterLabel(element);
|
|
52
|
+
} else if (element instanceof GPort) {
|
|
53
|
+
this.filterPort(element);
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
protected filterNode(node: GNode): boolean {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
protected filterEdge(edge: GEdge): boolean {
|
|
63
|
+
const source = this.modelState.index.get(edge.sourceId);
|
|
64
|
+
|
|
65
|
+
if (!source || (source instanceof GNode && !this.filterNode(source)) || (source instanceof GPort && !this.filterPort(source))) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const target = this.modelState.index.get(edge.targetId);
|
|
70
|
+
|
|
71
|
+
if (!target || (target instanceof GNode && !this.filterNode(target)) || (target instanceof GPort && !this.filterPort(target))) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
protected filterLabel(label: GLabel): boolean {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
protected filterPort(port: GPort): boolean {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2022
|
|
3
|
-
*
|
|
2
|
+
* Copyright (c) 2018-2022 TypeFox and others.
|
|
4
3
|
* This program and the accompanying materials are made available under the
|
|
5
4
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
5
|
* http://www.eclipse.org/legal/epl-2.0.
|
|
@@ -13,45 +12,321 @@
|
|
|
13
12
|
*
|
|
14
13
|
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
14
|
********************************************************************************/
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
import {
|
|
16
|
+
findParent,
|
|
17
|
+
findParentByClass,
|
|
18
|
+
GCompartment,
|
|
19
|
+
GEdge,
|
|
20
|
+
GGraph,
|
|
21
|
+
GLabel,
|
|
22
|
+
GModelElement,
|
|
23
|
+
GModelElementConstructor,
|
|
24
|
+
GModelRoot,
|
|
25
|
+
GNode,
|
|
26
|
+
GPort,
|
|
27
|
+
GShapeElement,
|
|
28
|
+
LayoutEngine,
|
|
29
|
+
MaybePromise,
|
|
30
|
+
ModelState,
|
|
31
|
+
Point
|
|
32
|
+
} from '@eclipse-glsp/server-node';
|
|
33
|
+
import { ELK, ElkEdge, ElkExtendedEdge, ElkGraphElement, ElkLabel, ElkNode, ElkPort, ElkPrimitiveEdge, ElkShape } from 'elkjs/lib/elk-api';
|
|
34
|
+
import { injectable } from 'inversify';
|
|
35
|
+
import { ElkFactory } from 'sprotty-elk';
|
|
36
|
+
import { ElementFilter } from './element-filter';
|
|
37
|
+
import { LayoutConfigurator } from './layout-configurator';
|
|
21
38
|
|
|
22
39
|
/**
|
|
23
|
-
*
|
|
40
|
+
* An implementation of GLSP's {@link LayoutEngine} interface that retrieves the graphical model from the {@link ModelState},
|
|
41
|
+
* transforms this model into an ELK graph and then invokes the underlying ELK instance for layout computation.
|
|
24
42
|
*/
|
|
25
43
|
@injectable()
|
|
26
|
-
export class
|
|
44
|
+
export class GlspElkLayoutEngine implements LayoutEngine {
|
|
45
|
+
protected readonly elk: ELK;
|
|
46
|
+
|
|
47
|
+
protected elkEdges: ElkEdge[];
|
|
48
|
+
protected idToElkElement: Map<string, ElkGraphElement>;
|
|
49
|
+
|
|
27
50
|
constructor(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
51
|
+
elkFactory: ElkFactory,
|
|
52
|
+
protected readonly filter: ElementFilter,
|
|
53
|
+
protected readonly configurator: LayoutConfigurator,
|
|
54
|
+
protected modelState: ModelState
|
|
32
55
|
) {
|
|
33
|
-
|
|
56
|
+
this.elk = elkFactory();
|
|
34
57
|
}
|
|
58
|
+
layout(): MaybePromise<GModelRoot> {
|
|
59
|
+
const root = this.modelState.root;
|
|
60
|
+
if (!(root instanceof GGraph)) {
|
|
61
|
+
return root;
|
|
62
|
+
}
|
|
35
63
|
|
|
36
|
-
|
|
37
|
-
|
|
64
|
+
this.elkEdges = [];
|
|
65
|
+
this.idToElkElement = new Map();
|
|
66
|
+
const elkGraph = this.transformToElk(root) as ElkNode;
|
|
67
|
+
return this.elk.layout(elkGraph).then(result => {
|
|
68
|
+
this.applyLayout(result);
|
|
69
|
+
return root;
|
|
70
|
+
});
|
|
38
71
|
}
|
|
39
|
-
}
|
|
40
72
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
73
|
+
protected transformToElk(model: GModelElement): ElkGraphElement {
|
|
74
|
+
if (model instanceof GGraph) {
|
|
75
|
+
const graph = this.transformGraph(model);
|
|
76
|
+
this.elkEdges.forEach(elkEdge => {
|
|
77
|
+
const parent = this.findCommonAncestor(elkEdge as ElkPrimitiveEdge);
|
|
78
|
+
if (parent) {
|
|
79
|
+
parent.edges!.push(elkEdge);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
return graph;
|
|
83
|
+
} else if (model instanceof GNode) {
|
|
84
|
+
return this.transformNode(model);
|
|
85
|
+
} else if (model instanceof GEdge) {
|
|
86
|
+
return this.transformEdge(model);
|
|
87
|
+
} else if (model instanceof GLabel) {
|
|
88
|
+
return this.transformPort(model);
|
|
89
|
+
} else if (model instanceof GPort) {
|
|
90
|
+
return this.transformPort(model);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
throw new Error('Type not supported: ' + model.type);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Searches for all children of the given element that are an instance of the given {@link GModelElementConstructor}
|
|
98
|
+
* and are included by the {@link ElementFilter}. Also considers children that are nested inside of {@link GCompartment}s.
|
|
99
|
+
* @param element The element whose children should be queried.
|
|
100
|
+
* @param constructor The class instance that should be matched
|
|
101
|
+
* @returns A list of all matching children.
|
|
102
|
+
*/
|
|
103
|
+
protected findChildren<G extends GModelElement>(element: GModelElement, constructor: GModelElementConstructor<G>): G[] {
|
|
104
|
+
const result: G[] = [];
|
|
105
|
+
element.children.forEach(child => {
|
|
106
|
+
if (child instanceof constructor && this.filter.apply(element)) {
|
|
107
|
+
result.push(child);
|
|
108
|
+
} else if (child instanceof GCompartment) {
|
|
109
|
+
result.push(...this.findChildren(child, constructor));
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
protected findCommonAncestor(elkEdge: ElkPrimitiveEdge): ElkNode | undefined {
|
|
117
|
+
const source = this.modelState.index.get(elkEdge.source);
|
|
118
|
+
const target = this.modelState.index.get(elkEdge.target);
|
|
119
|
+
if (!source || !target) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const sourceParent = findParent(source.parent, parent => parent instanceof GNode || parent instanceof GGraph);
|
|
124
|
+
const targetParent = findParent(target.parent, parent => parent instanceof GNode || parent instanceof GGraph);
|
|
125
|
+
|
|
126
|
+
if (!sourceParent || !targetParent) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (sourceParent === targetParent) {
|
|
131
|
+
return this.idToElkElement.get(sourceParent.id);
|
|
132
|
+
} else if (source === targetParent) {
|
|
133
|
+
return this.idToElkElement.get(source.id);
|
|
134
|
+
} else if (target === sourceParent) {
|
|
135
|
+
return this.idToElkElement.get(target.id);
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
protected transformGraph(graph: GGraph): ElkGraphElement {
|
|
141
|
+
const elkGraph: ElkNode = {
|
|
142
|
+
id: graph.id,
|
|
143
|
+
layoutOptions: this.configurator.apply(graph)
|
|
144
|
+
};
|
|
145
|
+
if (graph.children) {
|
|
146
|
+
elkGraph.children = this.findChildren(graph, GNode).map(child => this.transformToElk(child)) as ElkNode[];
|
|
147
|
+
elkGraph.edges = [];
|
|
148
|
+
this.elkEdges.push(...(this.findChildren(graph, GEdge).map(child => this.transformToElk(child)) as ElkEdge[]));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this.idToElkElement.set(graph.id, elkGraph);
|
|
152
|
+
return elkGraph;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
protected transformNode(node: GNode): ElkNode {
|
|
156
|
+
const elkNode: ElkNode = {
|
|
157
|
+
id: node.id,
|
|
158
|
+
layoutOptions: this.configurator.apply(node)
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (node.children) {
|
|
162
|
+
elkNode.children = this.findChildren(node, GNode).map(child => this.transformToElk(child)) as ElkNode[];
|
|
163
|
+
elkNode.edges = [];
|
|
164
|
+
this.elkEdges.push(...(this.findChildren(node, GEdge).map(child => this.transformToElk(child)) as ElkEdge[]));
|
|
165
|
+
|
|
166
|
+
elkNode.labels = this.findChildren(node, GLabel).map(child => this.transformToElk(child)) as ElkLabel[];
|
|
167
|
+
elkNode.ports = this.findChildren(node, GPort).map(child => this.transformToElk(child)) as ElkPort[];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.transformShape(elkNode, node);
|
|
171
|
+
this.idToElkElement.set(node.id, elkNode);
|
|
172
|
+
|
|
173
|
+
return elkNode;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
protected transformShape(elkShape: ElkShape, shape: GShapeElement): void {
|
|
177
|
+
if (shape.position) {
|
|
178
|
+
elkShape.x = shape.position.x;
|
|
179
|
+
elkShape.y = shape.position.y;
|
|
180
|
+
}
|
|
181
|
+
if (shape.size) {
|
|
182
|
+
elkShape.width = shape.size.width;
|
|
183
|
+
elkShape.height = shape.size.height;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
protected transformEdge(edge: GEdge): ElkEdge {
|
|
188
|
+
const elkEdge: ElkPrimitiveEdge = {
|
|
189
|
+
id: edge.id,
|
|
190
|
+
source: edge.sourceId,
|
|
191
|
+
target: edge.targetId,
|
|
192
|
+
layoutOptions: this.configurator.apply(edge)
|
|
193
|
+
};
|
|
194
|
+
const sourceElement = this.modelState.index.get(edge.sourceId);
|
|
195
|
+
if (sourceElement instanceof GPort) {
|
|
196
|
+
const parentNode = findParentByClass(sourceElement, GNode);
|
|
197
|
+
if (parentNode) {
|
|
198
|
+
elkEdge.source = parentNode.id;
|
|
199
|
+
elkEdge.sourcePort = sourceElement.id;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const targetElement = this.modelState.index.get(edge.targetId);
|
|
204
|
+
if (sourceElement instanceof GPort) {
|
|
205
|
+
const parentNode = findParentByClass(targetElement, GNode);
|
|
206
|
+
if (parentNode) {
|
|
207
|
+
elkEdge.target = parentNode.id;
|
|
208
|
+
elkEdge.targetPort = targetElement.id;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (edge.children) {
|
|
213
|
+
elkEdge.labels = this.findChildren(edge, GLabel).map(child => this.transformToElk(child)) as ElkLabel[];
|
|
214
|
+
}
|
|
215
|
+
const points = edge.routingPoints;
|
|
216
|
+
if (points && points.length >= 2) {
|
|
217
|
+
elkEdge.sourcePoint = points[0];
|
|
218
|
+
elkEdge.bendPoints = points.slice(1, points.length - 1);
|
|
219
|
+
elkEdge.targetPoint = points[points.length - 1];
|
|
220
|
+
}
|
|
221
|
+
this.idToElkElement.set(edge.id, elkEdge);
|
|
222
|
+
return elkEdge;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
protected transformLabel(label: GLabel): ElkLabel {
|
|
226
|
+
const elkLabel: ElkLabel = {
|
|
227
|
+
id: label.id,
|
|
228
|
+
text: label.text,
|
|
229
|
+
layoutOptions: this.configurator.apply(label)
|
|
230
|
+
};
|
|
231
|
+
this.transformShape(elkLabel, label);
|
|
232
|
+
this.idToElkElement.set(label.id, elkLabel);
|
|
233
|
+
return elkLabel;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
protected transformPort(port: GPort): ElkPort {
|
|
237
|
+
const elkPort: ElkPort = {
|
|
238
|
+
id: port.id,
|
|
239
|
+
layoutOptions: this.configurator.apply(port)
|
|
240
|
+
};
|
|
241
|
+
if (port.children) {
|
|
242
|
+
elkPort.labels = this.findChildren(port, GLabel).map(child => this.transformToElk(child)) as ElkLabel[];
|
|
243
|
+
}
|
|
244
|
+
this.transformShape(elkPort, port);
|
|
245
|
+
this.idToElkElement.set(port.id, elkPort);
|
|
246
|
+
return elkPort;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
protected applyLayout(elkNode: ElkNode): void {
|
|
250
|
+
const element = this.modelState.index.get(elkNode.id);
|
|
251
|
+
if (element instanceof GNode) {
|
|
252
|
+
this.applyShape(element, elkNode);
|
|
253
|
+
}
|
|
254
|
+
if (elkNode.children) {
|
|
255
|
+
for (const child of elkNode.children) {
|
|
256
|
+
this.applyLayout(child);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (elkNode.edges) {
|
|
260
|
+
for (const elkEdge of elkNode.edges) {
|
|
261
|
+
const edge = this.modelState.index.get(elkEdge.id);
|
|
262
|
+
if (edge instanceof GEdge) {
|
|
263
|
+
this.applyEdge(edge, elkEdge);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (elkNode.ports) {
|
|
269
|
+
for (const elkPort of elkNode.ports) {
|
|
270
|
+
const port = this.modelState.index.findByClass(elkPort.id, GPort);
|
|
271
|
+
if (port) {
|
|
272
|
+
this.applyShape(port, elkPort);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
protected applyShape(shape: GShapeElement, elkShape: ElkShape): void {
|
|
279
|
+
if (elkShape.x !== undefined && elkShape.y !== undefined) {
|
|
280
|
+
shape.position = { x: elkShape.x, y: elkShape.y };
|
|
281
|
+
}
|
|
282
|
+
if (elkShape.width !== undefined && elkShape.height !== undefined) {
|
|
283
|
+
shape.size = { width: elkShape.width, height: elkShape.height };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (elkShape.labels) {
|
|
287
|
+
for (const elkLabel of elkShape.labels) {
|
|
288
|
+
const label = this.modelState.index.findByClass(elkLabel.id, GLabel);
|
|
289
|
+
if (label) {
|
|
290
|
+
this.applyShape(label, elkLabel);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
49
295
|
|
|
50
|
-
|
|
51
|
-
|
|
296
|
+
protected applyEdge(edge: GEdge, elkEdge: ElkEdge): void {
|
|
297
|
+
const points: Point[] = [];
|
|
298
|
+
if ((elkEdge as any).sections && (elkEdge as any).sections.length > 0) {
|
|
299
|
+
const section = (elkEdge as ElkExtendedEdge).sections[0];
|
|
300
|
+
if (section.startPoint) {
|
|
301
|
+
points.push(section.startPoint);
|
|
302
|
+
}
|
|
303
|
+
if (section.bendPoints) {
|
|
304
|
+
points.push(...section.bendPoints);
|
|
305
|
+
}
|
|
306
|
+
if (section.endPoint) {
|
|
307
|
+
points.push(section.endPoint);
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
const section = elkEdge as ElkPrimitiveEdge;
|
|
311
|
+
if (section.sourcePoint) {
|
|
312
|
+
points.push(section.sourcePoint);
|
|
313
|
+
}
|
|
314
|
+
if (section.bendPoints) {
|
|
315
|
+
points.push(...section.bendPoints);
|
|
316
|
+
}
|
|
317
|
+
if (section.targetPoint) {
|
|
318
|
+
points.push(section.targetPoint);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
edge.routingPoints = points;
|
|
52
322
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
323
|
+
if (elkEdge.labels) {
|
|
324
|
+
elkEdge.labels.forEach(elkLabel => {
|
|
325
|
+
const label = this.modelState.index.findByClass(elkLabel.id, GLabel);
|
|
326
|
+
if (label) {
|
|
327
|
+
this.applyShape(label, elkLabel);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
56
331
|
}
|
|
57
332
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2022
|
|
2
|
+
* Copyright (c) 2022 STMicroelectronics and others.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials are made available under the
|
|
5
5
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
@@ -13,10 +13,8 @@
|
|
|
13
13
|
*
|
|
14
14
|
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
15
|
********************************************************************************/
|
|
16
|
-
export * from 'sprotty-elk';
|
|
17
|
-
export { SModelIndex } from 'sprotty-protocol/lib/utils/model-utils';
|
|
18
|
-
export * from './basic-type-mapper';
|
|
19
16
|
export * from './di.config';
|
|
20
|
-
export * from './
|
|
17
|
+
export * from './element-filter';
|
|
21
18
|
export * from './glsp-elk-layout-engine';
|
|
22
|
-
export * from './
|
|
19
|
+
export * from './layout-configurator';
|
|
20
|
+
export * from './sprotty-elk';
|