1 | |
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | import * as go from '../release/go-module.js';
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | export class RadialLayout extends go.Layout {
|
23 | private _root: go.Node | null = null;
|
24 | private _layerThickness: number = 100;
|
25 | private _maxLayers: number = Infinity;
|
26 |
|
27 | |
28 |
|
29 |
|
30 | get root(): go.Node | null { return this._root; }
|
31 | set root(value: go.Node | null) {
|
32 | if (this._root !== value) {
|
33 | this._root = value;
|
34 | this.invalidateLayout();
|
35 | }
|
36 | }
|
37 |
|
38 | |
39 |
|
40 |
|
41 |
|
42 |
|
43 | get layerThickness(): number { return this._layerThickness; }
|
44 | set layerThickness(value: number) {
|
45 | if (this._layerThickness !== value) {
|
46 | this._layerThickness = value;
|
47 | this.invalidateLayout();
|
48 | }
|
49 | }
|
50 |
|
51 | |
52 |
|
53 |
|
54 |
|
55 |
|
56 | get maxLayers(): number { return this._maxLayers; }
|
57 | set maxLayers(value: number) {
|
58 | if (this._maxLayers !== value) {
|
59 | this._maxLayers = value;
|
60 | this.invalidateLayout();
|
61 | }
|
62 | }
|
63 |
|
64 | |
65 |
|
66 |
|
67 | public cloneProtected(copy: this): void {
|
68 | super.cloneProtected(copy);
|
69 |
|
70 | copy._layerThickness = this._layerThickness;
|
71 | copy._maxLayers = this._maxLayers;
|
72 | }
|
73 |
|
74 | |
75 |
|
76 |
|
77 | public createNetwork(): go.LayoutNetwork {
|
78 | const net = new go.LayoutNetwork(this);
|
79 | net.createVertex = () => new RadialVertex(net);
|
80 | return net;
|
81 | }
|
82 |
|
83 | |
84 |
|
85 |
|
86 |
|
87 | public doLayout(coll: go.Diagram | go.Group | go.Iterable<go.Part>): void {
|
88 | if (this.network === null) {
|
89 | this.network = this.makeNetwork(coll);
|
90 | }
|
91 | if (this.network.vertexes.count === 0) {
|
92 | this.network = null;
|
93 | return;
|
94 | }
|
95 |
|
96 | if (this.root === null) {
|
97 |
|
98 | const rit = this.network.vertexes.iterator;
|
99 | while (rit.next()) {
|
100 | const v = rit.value;
|
101 | if (v.node !== null && v.sourceEdges.count === 0) {
|
102 | this.root = v.node;
|
103 | break;
|
104 | }
|
105 | }
|
106 | }
|
107 | if (this.root === null && this.network !== null) {
|
108 |
|
109 | const first = this.network.vertexes.first();
|
110 | this.root = first === null ? null : first.node;
|
111 | }
|
112 | if (this.root === null) {
|
113 | this.network = null;
|
114 | return;
|
115 | }
|
116 |
|
117 | const rootvert = this.network.findVertex(this.root) as RadialVertex;
|
118 | if (rootvert === null) throw new Error('RadialLayout.root must be a Node in the LayoutNetwork that the RadialLayout is operating on');
|
119 |
|
120 | this.arrangementOrigin = this.initialOrigin(this.arrangementOrigin);
|
121 | this.findDistances(rootvert);
|
122 |
|
123 |
|
124 | const verts = [];
|
125 | let maxlayer = 0;
|
126 | const it = this.network.vertexes.iterator;
|
127 | while (it.next()) {
|
128 | const vv = it.value as RadialVertex;
|
129 | vv.laid = false;
|
130 | const layer = vv.distance;
|
131 | if (layer === Infinity) continue;
|
132 | if (layer > maxlayer) maxlayer = layer;
|
133 | let layerverts: Array<go.LayoutVertex> = verts[layer];
|
134 | if (layerverts === undefined) {
|
135 | layerverts = [];
|
136 | verts[layer] = layerverts;
|
137 | }
|
138 | layerverts.push(vv);
|
139 | }
|
140 |
|
141 |
|
142 | rootvert.centerX = this.arrangementOrigin.x;
|
143 | rootvert.centerY = this.arrangementOrigin.y;
|
144 | this.radlay1(rootvert, 1, 0, 360);
|
145 |
|
146 |
|
147 | this.updateParts();
|
148 | this.network = null;
|
149 | }
|
150 |
|
151 | |
152 |
|
153 |
|
154 | private radlay1(vert: RadialVertex, layer: number, angle: number, sweep: number): void {
|
155 | if (layer > this.maxLayers) return;
|
156 | const verts: Array<RadialVertex> = [];
|
157 | const vit = vert.vertexes.iterator;
|
158 | while (vit.next()) {
|
159 | const v = vit.value as RadialVertex;
|
160 | if (v.laid) continue;
|
161 | if (v.distance === layer) verts.push(v);
|
162 | }
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | const found = verts.length;
|
169 | if (found === 0) return;
|
170 |
|
171 | const radius = layer * this.layerThickness;
|
172 | const separator = sweep / found;
|
173 | const start = angle - sweep / 2 + separator / 2;
|
174 |
|
175 | for (let i = 0; i < found; i++) {
|
176 | const v = verts[i];
|
177 | let a = start + i * separator;
|
178 | if (a < 0) a += 360; else if (a > 360) a -= 360;
|
179 |
|
180 |
|
181 | const p = new go.Point(radius, 0);
|
182 | p.rotate(a);
|
183 | v.centerX = p.x + this.arrangementOrigin.x;
|
184 | v.centerY = p.y + this.arrangementOrigin.y;
|
185 | v.laid = true;
|
186 | v.angle = a;
|
187 | v.sweep = separator;
|
188 | v.radius = radius;
|
189 |
|
190 | this.radlay1(v, layer + 1, a, sweep / found);
|
191 | }
|
192 | }
|
193 |
|
194 | |
195 |
|
196 |
|
197 | private findDistances(source: RadialVertex): void {
|
198 | if (this.network === null) return;
|
199 |
|
200 |
|
201 | const vit = this.network.vertexes.iterator;
|
202 | while (vit.next()) {
|
203 | const v = vit.value as RadialVertex;
|
204 | v.distance = Infinity;
|
205 | }
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | source.distance = 0;
|
212 |
|
213 |
|
214 | const seen = new go.Set<RadialVertex>();
|
215 | seen.add(source);
|
216 |
|
217 |
|
218 | function leastVertex(coll: go.Set<RadialVertex>): RadialVertex | null {
|
219 | let bestdist = Infinity;
|
220 | let bestvert = null;
|
221 | const it = coll.iterator;
|
222 | while (it.next()) {
|
223 | const v = it.value;
|
224 | const dist = v.distance;
|
225 | if (dist < bestdist) {
|
226 | bestdist = dist;
|
227 | bestvert = v;
|
228 | }
|
229 | }
|
230 | return bestvert;
|
231 | }
|
232 |
|
233 |
|
234 |
|
235 | const finished = new go.Set<RadialVertex>();
|
236 | while (seen.count > 0) {
|
237 |
|
238 | const least = leastVertex(seen);
|
239 | if (least === null) return;
|
240 | const leastdist = least.distance;
|
241 |
|
242 | seen.remove(least);
|
243 | finished.add(least);
|
244 |
|
245 | least.edges.each(function(e) {
|
246 | if (least === null) return;
|
247 | const neighbor = e.getOtherVertex(least);
|
248 |
|
249 | if (finished.contains(neighbor as any)) return;
|
250 | const neighbordist = (neighbor as any).distance;
|
251 |
|
252 | const dist = leastdist + 1;
|
253 | if (dist < neighbordist) {
|
254 |
|
255 | if (neighbordist === Infinity) {
|
256 | seen.add(neighbor as any);
|
257 | }
|
258 |
|
259 | (neighbor as any).distance = dist;
|
260 | }
|
261 | });
|
262 | }
|
263 | }
|
264 |
|
265 | |
266 |
|
267 |
|
268 | public commitLayout(): void {
|
269 | super.commitLayout();
|
270 | if (this.network !== null) {
|
271 | const it = this.network.vertexes.iterator;
|
272 | while (it.next()) {
|
273 | const v = it.value as RadialVertex;
|
274 | const n = v.node;
|
275 | if (n !== null) {
|
276 | n.visible = (v.distance <= this.maxLayers);
|
277 | this.rotateNode(n, v.angle, v.sweep, v.radius);
|
278 | }
|
279 | }
|
280 | }
|
281 | this.commitLayers();
|
282 | }
|
283 |
|
284 | |
285 |
|
286 |
|
287 |
|
288 |
|
289 | public rotateNode(node: go.Node, angle: number, sweep: number, radius: number): void { }
|
290 |
|
291 | |
292 |
|
293 |
|
294 |
|
295 |
|
296 | public commitLayers(): void { }
|
297 | }
|
298 |
|
299 |
|
300 |
|
301 |
|
302 |
|
303 | class RadialVertex extends go.LayoutVertex {
|
304 | constructor(network: go.LayoutNetwork) {
|
305 | super(network);
|
306 | }
|
307 | public distance: number = Infinity;
|
308 | public laid: boolean = false;
|
309 | public angle: number = 0;
|
310 | public sweep: number = 0;
|
311 | public radius: number = 0;
|
312 | }
|