Skip to content

Commit 95a70dd

Browse files
committed
Add assignVertexProperties/assignEdgeProperties to allow user to modify some dot layout attributes
1 parent bd0f00a commit 95a70dd

File tree

1 file changed

+172
-32
lines changed

1 file changed

+172
-32
lines changed

gojs-dotlayout/src/DotLayout.js

Lines changed: 172 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ export class DotLayout extends go.Layout {
2929
if (!graphviz) throw new Error(`no Graphviz instance was provided to DotLayout`);
3030
const proto = Object.getPrototypeOf(graphviz);
3131
if (!(proto && typeof proto.dot === 'function')) throw new Error(`provided Graphviz instance must include dot layout function`);
32+
3233
super();
3334
this.isRouting = true;
34-
this.graphviz = graphviz;
35+
36+
this._graphviz = graphviz;
3537

3638
this._direction = 'TB';
3739
this._layerSpacing = 25;
@@ -45,7 +47,7 @@ export class DotLayout extends go.Layout {
4547
*
4648
* Valid values are "LR", "TB", "RL", "BT". Defaults to "TB".
4749
*
48-
* This corresponds with the dot layout rankdir attribute: https://graphviz.org/docs/attrs/rankdir/.
50+
* This corresponds to the dot layout rankdir attribute: https://graphviz.org/docs/attrs/rankdir/.
4951
*/
5052
get direction() { return this._direction; }
5153
set direction(value) {
@@ -61,7 +63,7 @@ export class DotLayout extends go.Layout {
6163
*
6264
* This value must be greater than 1.92 (to correspond to dot's minimum). Defaults to 25.
6365
*
64-
* This corresponds with the dot layout ranksep attribute: https://graphviz.org/docs/attrs/ranksep/.
66+
* This corresponds to the dot layout ranksep attribute: https://graphviz.org/docs/attrs/ranksep/.
6567
*/
6668
get layerSpacing() { return this._layerSpacing; }
6769
set layerSpacing(value) {
@@ -79,7 +81,7 @@ export class DotLayout extends go.Layout {
7981
*
8082
* This value must be greater than 1.92 (to correspond to dot's minimum). Defaults to 25.
8183
*
82-
* This corresponds with the dot layout nodesep attribute: https://graphviz.org/docs/attrs/nodesep/.
84+
* This corresponds to the dot layout nodesep attribute: https://graphviz.org/docs/attrs/nodesep/.
8385
*/
8486
get nodeSpacing() { return this._nodeSpacing; }
8587
set nodeSpacing(value) {
@@ -92,11 +94,11 @@ export class DotLayout extends go.Layout {
9294

9395
/**
9496
* Gets or sets whether the fromSpot and toSpot of each link should be used by the layout when routing links.
95-
* Link routes is typically more readable when this is left as false.
97+
* Link routes are typically more readable when this is left as false.
9698
*
9799
* Defaults to false.
98100
*
99-
* This corresponds with the Graphviz edge headport/tailport attributes:
101+
* This corresponds to the Graphviz edge headport/tailport attributes:
100102
* https://graphviz.org/docs/attrs/headport/
101103
* https://graphviz.org/docs/attrs/tailport/
102104
*/
@@ -114,7 +116,7 @@ export class DotLayout extends go.Layout {
114116
*
115117
* This value must be positive. Defaults to NaN, meaning no limit.
116118
*
117-
* This corresponds with the dot layout nslimit attribute: https://graphviz.org/docs/attrs/nslimit/.
119+
* This corresponds to the dot layout nslimit attribute: https://graphviz.org/docs/attrs/nslimit/.
118120
*/
119121
get iterations() { return this._iterations; }
120122
set iterations(value) {
@@ -125,11 +127,35 @@ export class DotLayout extends go.Layout {
125127
}
126128
}
127129

130+
/**
131+
* Creates a copy of this Layout and returns it.
132+
* @returns A copied DotLayout
133+
*/
134+
copy() {
135+
const copy = new (this.constructor)(this._graphviz);
136+
this.cloneProtected(copy);
137+
return copy;
138+
}
139+
140+
/**
141+
* Copies properties to a cloned Layout.
142+
*/
143+
cloneProtected(copy) {
144+
super.cloneProtected(copy);
145+
// don't copy .root
146+
copy._direction = this._direction;
147+
copy._layerSpacing = this._layerSpacing;
148+
copy._nodeSpacing = this._nodeSpacing;
149+
copy._usesLinkSpots = this._usesLinkSpots;
150+
copy._iterations = this._iterations;
151+
}
152+
128153
/**
129154
* Use a LayoutNetwork that always creates DotEdges.
130155
*/
131156
createNetwork() {
132157
const net = new go.LayoutNetwork(this);
158+
net.createVertex = () => new DotVertex(net);
133159
net.createEdge = () => new DotEdge(net);
134160
return net;
135161
}
@@ -156,7 +182,7 @@ export class DotLayout extends go.Layout {
156182
const dot = this.generateDot();
157183

158184
// perform Graphviz dot layout via Graphviz WASM
159-
const jsonStr = this.graphviz.dot(dot, 'json0');
185+
const jsonStr = this._graphviz.dot(dot, 'json0');
160186
const json = JSON.parse(jsonStr);
161187

162188
const h = parseFloat(json.bb.substring(json.bb.lastIndexOf(',') + 1)); // save height so we can convert y values
@@ -179,7 +205,7 @@ export class DotLayout extends go.Layout {
179205
const {x, y} = this.parsePos(ptStr, h);
180206
ptList.add(new go.Point(x, y));
181207
}
182-
edge.pts = ptList;
208+
edge._pts = ptList;
183209
}
184210
}
185211

@@ -206,16 +232,21 @@ export class DotLayout extends go.Layout {
206232
ranksep=${ranksep}
207233
nodesep=${nodesep}
208234
${nslimit}
209-
node [shape="box" fixedsize=true fontname="arial"]
235+
node [shape="box" fixedsize=true]
210236
edge [arrowhead="none"]\n`;
211237
const vit = this.network.vertexes.iterator;
212238
while (vit.next()) {
213239
const v = vit.value;
214240
const node = v.node;
215241
if (!node) continue;
216242
if (node instanceof go.Group) throw new Error('DotLayout does not currently support Groups.');
243+
this.assignVertexProperties(v);
244+
// prepare DOT language attributes for non-default vertex properties
245+
const width = !isNaN(v.width) ? `width=${v.width / 96}` : '';
246+
const height = !isNaN(v.height) ? ` height=${v.height / 96}` : '';
247+
const shape = v.shape !== 'box' ? ` shape="${v.shape}"` : '';
217248
// divide size by 96 since Graphviz expects inches
218-
dot += ` ${node.key} [width=${node.actualBounds.width / 96} height=${node.actualBounds.height / 96}]\n`;
249+
dot += ` ${node.key} [${width}${height}${shape}]\n`;
219250
}
220251
const eit = this.network.edges.iterator;
221252
while (eit.next()) {
@@ -225,31 +256,38 @@ export class DotLayout extends go.Layout {
225256
const fromNode = link.fromNode;
226257
const toNode = link.toNode;
227258
if (!(fromNode && toNode)) continue;
259+
this.assignEdgeProperties(e);
260+
// prepare DOT language attributes for non-default edge properties
228261
const constraint = !e.isConstraint ? ' constraint=false' : '';
229-
let tailport = '';
230-
let headport = '';
231-
if (this.usesLinkSpots) {
232-
tailport = this.getPortAttr(link.fromSpot, true);
233-
headport = this.getPortAttr(link.toSpot, false);
234-
}
262+
const tailport = e.tailport !== 'c' ? ` tailport=${e.tailport}` : '';
263+
const headport = e.headport !== 'c' ? ` headport=${e.headport}` : '';
264+
const weight = e.weight !== 1 ? ` weight=${e.weight}` : '';
235265
// include the Link key as an ID for quick lookup from the output
236-
dot += ` ${fromNode.key} -> ${toNode.key} [id=${link.key}${constraint}${tailport}${headport}]\n`;
266+
dot += ` ${fromNode.key} -> ${toNode.key} [id=${link.key}${constraint}${tailport}${headport}${weight}]\n`;
237267
}
238268
dot += '}';
239269
return dot;
240270
}
241271

242272
/**
243-
* Parse a pos string into an x-y pair, normalizing to a top-left origin and pixels.
244-
* @param {*} str A Graphviz "pos" property string
245-
* @param {*} h The height, in points, of the Graphviz output graph
246-
* @returns An x, y pair representing a point
273+
* Override this method in order to set vertex properties prior to the dot layout being performed.
274+
*
275+
* By default, this method does nothing.
276+
* @param {*} v The vertex for which properties can be assigned
247277
*/
248-
parsePos(str, h) {
249-
const idx = str.indexOf(',');
250-
const x = parseFloat(str.substring(0, idx)) * 96 / 72;
251-
const y = (h - parseFloat(str.substring(idx + 1)) + 1) * 96 / 72;
252-
return { x, y };
278+
assignVertexProperties(v) { }
279+
280+
/**
281+
* Override this method in order to set edge properties prior to the dot layout being performed.
282+
*
283+
* By default, this method sets the headport and tailport properties if usesLinkSpots is true.
284+
* @param {*} e The edge for which properties can be assigned
285+
*/
286+
assignEdgeProperties(e) {
287+
if (this.usesLinkSpots) {
288+
e.tailport = this.getPortAttr(e.link.fromSpot, true);
289+
e.headport = this.getPortAttr(e.link.toSpot, false);
290+
}
253291
}
254292

255293
/**
@@ -283,27 +321,78 @@ export class DotLayout extends go.Layout {
283321
case go.Spot.Default: port = 'c'; break;
284322
default: port = '_'; break;
285323
}
324+
return port;
325+
}
326+
327+
/**
328+
* Parse a pos string into an x-y pair, normalizing to a top-left origin and pixels.
329+
* @param {*} str A Graphviz "pos" property string
330+
* @param {*} h The height, in points, of the Graphviz output graph
331+
* @returns An x, y pair representing a point
332+
*/
333+
parsePos(str, h) {
334+
const idx = str.indexOf(',');
335+
const x = parseFloat(str.substring(0, idx)) * 96 / 72;
336+
const y = (h - parseFloat(str.substring(idx + 1)) + 1) * 96 / 72;
337+
return { x, y };
338+
}
339+
}
286340

287-
return isfrom ? ` tailport=${port}` : ` headport=${port}`;
341+
/**
342+
* DotVertex, a LayoutVertex that holds additional info specific to dot layouts.
343+
*/
344+
class DotVertex extends go.LayoutVertex {
345+
constructor(network) {
346+
super(network);
347+
348+
// default to NaN so we can ensure they're set
349+
this.width = NaN;
350+
this.height = NaN;
351+
352+
this._shape = 'box';
353+
}
354+
355+
/**
356+
* Gets or sets the shape of this vertex.
357+
*
358+
* Valid values can be found here: https://graphviz.org/doc/info/shapes.html#polygon. Defaults to "box".
359+
*
360+
* This property can be set during assignVertexProperties.
361+
*
362+
* This corresponds to the Graphviz node shape attribute: https://graphviz.org/docs/attrs/shape/.
363+
*/
364+
get shape() { return this._shape; }
365+
set shape(value) {
366+
if (this._shape !== value) {
367+
this._shape = value;
368+
}
288369
}
289370
}
290371

291372
/**
292-
* DotEdge, a LayoutEdge that holds additional info about link points.
373+
* DotEdge, a LayoutEdge that holds additional info specific to dot layouts.
293374
*/
294375
class DotEdge extends go.LayoutEdge {
376+
static validPorts = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'c', '_'];
377+
295378
constructor(network) {
296379
super(network);
297-
this.pts = null;
380+
this._pts = null; // internal
381+
298382
this._isConstraint = true;
383+
this._headport = 'c';
384+
this._tailport = 'c';
385+
this._weight = 1;
299386
}
300387

301388
/**
302389
* Gets or sets whether this edge is used in determining node layers.
303390
*
304391
* Defaults to true.
305392
*
306-
* This corresponds with the Graphviz edge constraint attribute: https://www.graphviz.org/docs/attrs/constraint/.
393+
* This property can be set during assignEdgeProperties.
394+
*
395+
* This corresponds to the Graphviz edge constraint attribute: https://www.graphviz.org/docs/attrs/constraint/.
307396
*/
308397
get isConstraint() { return this._isConstraint; }
309398
set isConstraint(value) {
@@ -313,7 +402,58 @@ class DotEdge extends go.LayoutEdge {
313402
}
314403
}
315404

405+
/**
406+
* Gets or sets the "compass point" where this edge connects to its to vertex.
407+
*
408+
* Valid values are "n", "ne", "e", "se", "s", "sw", "w", "nw", "c", "_". Defaults to "c".
409+
*
410+
* This property can be set during assignEdgeProperties.
411+
*
412+
* This corresponds to the Graphviz edge headport attribute: https://graphviz.org/docs/attrs/headport/.
413+
*/
414+
get headport() { return this._headport; }
415+
set headport(value) {
416+
if (!DotEdge.validPorts.includes(value)) throw new Error(`invalid value for DotEdge.headport: ${value}`);
417+
if (this._headport !== value) {
418+
this._headport = value;
419+
}
420+
}
421+
422+
/**
423+
* Gets or sets the "compass point" where this edge connects to its from vertex.
424+
*
425+
* Valid values are "n", "ne", "e", "se", "s", "sw", "w", "nw", "c", "_". Defaults to "c".
426+
*
427+
* This property can be set during assignEdgeProperties.
428+
*
429+
* This corresponds to the Graphviz edge tailport attribute: https://graphviz.org/docs/attrs/tailport/.
430+
*/
431+
get tailport() { return this._tailport; }
432+
set tailport(value) {
433+
if (!DotEdge.validPorts.includes(value)) throw new Error(`invalid value for DotEdge.tailport: ${value}`);
434+
if (this._tailport !== value) {
435+
this._tailport = value;
436+
}
437+
}
438+
439+
/**
440+
* Gets or sets the weight of this edge.
441+
*
442+
* This value must be an integer >= 0. Defaults to 1.
443+
*
444+
* This property can be set during assignEdgeProperties.
445+
*
446+
* This corresponds to the Graphviz edge constraint attribute: https://graphviz.org/docs/attrs/weight/.
447+
*/
448+
get weight() { return this._weight; }
449+
set weight(value) {
450+
if (typeof value !== 'number' || value < 0 || !Number.isInteger(value)) throw new Error(`new value for DotEdge.weight must be an integer >= 0, not: ${value}`);
451+
if (this._weight !== value) {
452+
this._weight = value;
453+
}
454+
}
455+
316456
commit() {
317-
if (this.network.layout.isRouting) this.link.points = this.pts;
457+
if (this.network.layout.isRouting) this.link.points = this._pts;
318458
}
319459
}

0 commit comments

Comments
 (0)