Skip to content

Commit 565bdbd

Browse files
authored
fix(rendering): render external node labels above arrows (#932)
1 parent d655613 commit 565bdbd

File tree

13 files changed

+227
-103
lines changed

13 files changed

+227
-103
lines changed
Lines changed: 110 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,118 @@
1+
const EXTERNAL_LABEL_SHAPES = new Set<string>([
2+
"diamond",
3+
"dot",
4+
"hexagon",
5+
"square",
6+
"star",
7+
"triangle",
8+
"triangleDown",
9+
]);
10+
const INTERNAL_LABEL_SHAPES = new Set<string>([
11+
"box",
12+
"circle",
13+
"database",
14+
"ellipse",
15+
"text",
16+
]);
17+
18+
const EXTERNAL_LABEL = new Array(15)
19+
.fill(null)
20+
.map((): string => "This label should be above edge labels and arrows.")
21+
.join("\n");
22+
const INTERNAL_LABEL = [
23+
"This",
24+
"label",
25+
"should",
26+
"be",
27+
"above",
28+
"edge",
29+
"labels",
30+
"but",
31+
"bellow",
32+
"arrows.",
33+
].join("\n");
34+
135
context("Z-index", (): void => {
2-
it("node labels > nodes > edge labels > edge arrows > edges", (): void => {
3-
cy.visSimpleCanvasSnapshot(
4-
"node-labels_nodes_edge-labels_edge-arrows_edges",
5-
{
6-
nodes: [
36+
describe("external node labels > edge arrows > internal node labels > nodes > edge labels > edges", (): void => {
37+
for (const shape of [...EXTERNAL_LABEL_SHAPES, ...INTERNAL_LABEL_SHAPES]) {
38+
it(shape, function (): void {
39+
cy.visSimpleCanvasSnapshot(
40+
`z-index_${shape}`,
741
{
8-
id: 1,
9-
fixed: true,
10-
x: 0,
11-
y: -300,
12-
shape: "dot",
13-
label: new Array(15)
14-
.fill(null)
15-
.map(
16-
(): string =>
17-
"This label should be above edge labels and arrows."
18-
)
19-
.join("\n"),
20-
font: {
21-
color: "green",
22-
size: 40,
23-
},
24-
},
25-
{
26-
id: 3,
27-
fixed: true,
28-
x: 100,
29-
y: 300,
30-
shape: "box",
31-
label: new Array(5)
32-
.fill(null)
33-
.map(
34-
(): string =>
35-
"This label should be above edge labels but bellow arrows."
36-
)
37-
.join("\n"),
38-
font: {
39-
color: "black",
40-
size: 20,
41-
},
42-
},
43-
],
44-
edges: [
45-
{
46-
id: 2,
47-
from: 1,
48-
to: 3,
49-
label: new Array(80)
50-
.fill(null)
51-
.map(
52-
(): string =>
53-
"This label should be bellow nodes and their labels but above edge arrows."
54-
)
55-
.join("\n"),
56-
font: {
57-
color: "red",
58-
size: 10,
59-
},
60-
arrows: {
61-
from: {
62-
enabled: true,
63-
scaleFactor: 6,
42+
nodes: [
43+
{
44+
id: 1,
45+
fixed: true,
46+
x: 0,
47+
y: -300,
48+
shape,
49+
label: EXTERNAL_LABEL_SHAPES.has(shape)
50+
? EXTERNAL_LABEL
51+
: INTERNAL_LABEL,
52+
font: {
53+
color: "green",
54+
size: 40,
55+
},
6456
},
65-
middle: {
66-
enabled: true,
67-
scaleFactor: 6,
57+
{
58+
id: 3,
59+
fixed: true,
60+
x: 100,
61+
y: 300,
62+
shape: "box",
63+
label: new Array(5)
64+
.fill(null)
65+
.map(
66+
(): string =>
67+
"This label should be above edge labels but bellow arrows."
68+
)
69+
.join("\n"),
70+
font: {
71+
color: "black",
72+
size: 20,
73+
},
6874
},
69-
to: {
70-
enabled: true,
71-
scaleFactor: 6,
75+
],
76+
edges: [
77+
{
78+
id: 2,
79+
from: 1,
80+
to: 3,
81+
label: new Array(80)
82+
.fill(null)
83+
.map(
84+
(): string =>
85+
"This label should be bellow nodes and their labels but above edge arrows."
86+
)
87+
.join("\n"),
88+
font: {
89+
color: "red",
90+
size: 10,
91+
},
92+
arrows: {
93+
from: {
94+
enabled: true,
95+
scaleFactor: 6,
96+
},
97+
middle: {
98+
enabled: true,
99+
scaleFactor: 6,
100+
},
101+
to: {
102+
enabled: true,
103+
scaleFactor: 6,
104+
},
105+
},
106+
endPointOffset: {
107+
from: -50,
108+
to: -50,
109+
},
72110
},
73-
},
74-
endPointOffset: {
75-
from: -50,
76-
to: -50,
77-
},
111+
],
78112
},
79-
],
80-
}
81-
);
113+
{ requireNewerVersionThan: "8.0.1" }
114+
);
115+
});
116+
}
82117
});
83118
});

cypress/support/commands/vis-simple-canvas-snapshot.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function visSimpleCanvasSnapshot(
4242
deepObjectAssign<MoveToOptions>(
4343
{
4444
position: { x: 0, y: 0 },
45-
scale: 1
45+
scale: 1,
4646
},
4747
options.moveTo ?? {}
4848
)

lib/network/modules/CanvasRenderer.js

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ class CanvasRenderer {
246246

247247
this.redrawRequested = false;
248248

249+
const drawLater = {
250+
drawExternalLabels: null,
251+
}
252+
249253
// when the container div was hidden, this fixes it back up!
250254
if (this.canvas.frame.canvas.width === 0 || this.canvas.frame.canvas.height === 0) {
251255
this.canvas.setSize();
@@ -284,7 +288,8 @@ class CanvasRenderer {
284288
}
285289

286290
if (this.dragging === false || (this.dragging === true && this.options.hideNodesOnDrag === false)) {
287-
this._drawNodes(ctx, hidden);
291+
const { drawExternalLabels } = this._drawNodes(ctx, hidden);
292+
drawLater.drawExternalLabels = drawExternalLabels;
288293
}
289294

290295
// draw the arrows last so they will be at the top
@@ -297,6 +302,10 @@ class CanvasRenderer {
297302
}
298303
}
299304

305+
if (drawLater.drawExternalLabels != null) {
306+
drawLater.drawExternalLabels();
307+
}
308+
300309
if (hidden === false) {
301310
this._drawSelectionBox(ctx)
302311
}
@@ -352,6 +361,8 @@ class CanvasRenderer {
352361
* @param {CanvasRenderingContext2D} ctx 2D context of a HTML canvas
353362
* @param {boolean} [alwaysShow]
354363
* @private
364+
*
365+
* @returns {Object} Callbacks to draw later on higher layers.
355366
*/
356367
_drawNodes(ctx, alwaysShow = false) {
357368
const nodes = this.body.nodes;
@@ -367,6 +378,8 @@ class CanvasRenderer {
367378
});
368379
const viewableArea = {top:topLeft.y,left:topLeft.x,bottom:bottomRight.y,right:bottomRight.x};
369380

381+
const drawExternalLabels = [];
382+
370383
// draw unselected nodes;
371384
for (let i = 0; i < nodeIndices.length; i++) {
372385
node = nodes[nodeIndices[i]];
@@ -378,10 +391,16 @@ class CanvasRenderer {
378391
}
379392
else {
380393
if (alwaysShow === true) {
381-
node.draw(ctx);
394+
const drawLater = node.draw(ctx);
395+
if (drawLater.drawExternalLabel != null) {
396+
drawExternalLabels.push(drawLater.drawExternalLabel)
397+
}
382398
}
383399
else if (node.isBoundingBoxOverlappingWith(viewableArea) === true) {
384-
node.draw(ctx);
400+
const drawLater = node.draw(ctx);
401+
if (drawLater.drawExternalLabel != null) {
402+
drawExternalLabels.push(drawLater.drawExternalLabel)
403+
}
385404
}
386405
else {
387406
node.updateBoundingBox(ctx, node.selected);
@@ -396,15 +415,28 @@ class CanvasRenderer {
396415
// draw the selected nodes on top
397416
for (i = 0; i < selectedLength; i++) {
398417
node = nodes[selected[i]];
399-
node.draw(ctx);
418+
const drawLater = node.draw(ctx);
419+
if (drawLater.drawExternalLabel != null) {
420+
drawExternalLabels.push(drawLater.drawExternalLabel)
421+
}
400422
}
401423

402424
// draw hovered nodes above everything else: fixes https://github.com/visjs/vis-network/issues/226
403425
for (i = 0; i < hoveredLength; i++) {
404426
node = nodes[hovered[i]];
405-
node.draw(ctx);
427+
const drawLater = node.draw(ctx);
428+
if (drawLater.drawExternalLabel != null) {
429+
drawExternalLabels.push(drawLater.drawExternalLabel)
430+
}
406431
}
407432

433+
return {
434+
drawExternalLabels: () => {
435+
for (const draw of drawExternalLabels) {
436+
draw();
437+
}
438+
},
439+
};
408440
}
409441

410442

lib/network/modules/components/Node.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,10 +639,19 @@ class Node {
639639
* Draw this node in the given canvas
640640
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
641641
* @param {CanvasRenderingContext2D} ctx
642+
*
643+
* @returns {Object} Callbacks to draw later on higher layers.
642644
*/
643645
draw(ctx) {
644646
const values = this.getFormattingValues();
645-
this.shape.draw(ctx, this.x, this.y, this.selected, this.hover, values);
647+
return this.shape.draw(
648+
ctx,
649+
this.x,
650+
this.y,
651+
this.selected,
652+
this.hover,
653+
values
654+
) || {};
646655
}
647656

648657

lib/network/modules/components/nodes/shapes/Diamond.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ class Diamond extends ShapeBase {
2525
* @param {boolean} selected
2626
* @param {boolean} hover
2727
* @param {ArrowOptions} values
28+
*
29+
* @returns {Object} Callbacks to draw later on higher layers.
2830
*/
2931
draw(ctx, x, y, selected, hover, values) {
30-
this._drawShape(ctx, 'diamond', 4, x, y, selected, hover, values);
32+
return this._drawShape(ctx, 'diamond', 4, x, y, selected, hover, values);
3133
}
3234

3335
/**

lib/network/modules/components/nodes/shapes/Dot.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ class Dot extends ShapeBase {
2525
* @param {boolean} selected
2626
* @param {boolean} hover
2727
* @param {ArrowOptions} values
28+
*
29+
* @returns {Object} Callbacks to draw later on higher layers.
2830
*/
2931
draw(ctx, x, y, selected, hover, values) {
30-
this._drawShape(ctx, 'circle', 2, x, y, selected, hover, values);
32+
return this._drawShape(ctx, 'circle', 2, x, y, selected, hover, values);
3133
}
3234

3335
/**

lib/network/modules/components/nodes/shapes/Hexagon.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ class Hexagon extends ShapeBase {
2525
* @param {boolean} selected
2626
* @param {boolean} hover
2727
* @param {ArrowOptions} values
28+
*
29+
* @returns {Object} Callbacks to draw later on higher layers.
2830
*/
2931
draw(ctx, x, y, selected, hover, values) {
30-
this._drawShape(ctx, 'hexagon', 4, x, y, selected, hover, values);
32+
return this._drawShape(ctx, 'hexagon', 4, x, y, selected, hover, values);
3133
}
3234

3335
/**

0 commit comments

Comments
 (0)