From 565bdbdece8d15159203a3aa8be667865b6a4cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Vy=C4=8D=C3=ADtal?= Date: Sat, 8 Aug 2020 20:48:25 +0200 Subject: [PATCH] fix(rendering): render external node labels above arrows (#932) --- cypress/integration/visual/z-index.spec.ts | 185 +++++++++++------- .../commands/vis-simple-canvas-snapshot.ts | 2 +- lib/network/modules/CanvasRenderer.js | 42 +++- lib/network/modules/components/Node.js | 11 +- .../components/nodes/shapes/Diamond.js | 4 +- .../modules/components/nodes/shapes/Dot.js | 4 +- .../components/nodes/shapes/Hexagon.js | 4 +- .../modules/components/nodes/shapes/Icon.js | 24 ++- .../modules/components/nodes/shapes/Square.js | 4 +- .../modules/components/nodes/shapes/Star.js | 4 +- .../components/nodes/shapes/Triangle.js | 4 +- .../components/nodes/shapes/TriangleDown.js | 13 +- .../components/nodes/util/ShapeBase.js | 29 ++- 13 files changed, 227 insertions(+), 103 deletions(-) diff --git a/cypress/integration/visual/z-index.spec.ts b/cypress/integration/visual/z-index.spec.ts index 3a10ebd7cb..3f8668766c 100644 --- a/cypress/integration/visual/z-index.spec.ts +++ b/cypress/integration/visual/z-index.spec.ts @@ -1,83 +1,118 @@ +const EXTERNAL_LABEL_SHAPES = new Set([ + "diamond", + "dot", + "hexagon", + "square", + "star", + "triangle", + "triangleDown", +]); +const INTERNAL_LABEL_SHAPES = new Set([ + "box", + "circle", + "database", + "ellipse", + "text", +]); + +const EXTERNAL_LABEL = new Array(15) + .fill(null) + .map((): string => "This label should be above edge labels and arrows.") + .join("\n"); +const INTERNAL_LABEL = [ + "This", + "label", + "should", + "be", + "above", + "edge", + "labels", + "but", + "bellow", + "arrows.", +].join("\n"); + context("Z-index", (): void => { - it("node labels > nodes > edge labels > edge arrows > edges", (): void => { - cy.visSimpleCanvasSnapshot( - "node-labels_nodes_edge-labels_edge-arrows_edges", - { - nodes: [ + describe("external node labels > edge arrows > internal node labels > nodes > edge labels > edges", (): void => { + for (const shape of [...EXTERNAL_LABEL_SHAPES, ...INTERNAL_LABEL_SHAPES]) { + it(shape, function (): void { + cy.visSimpleCanvasSnapshot( + `z-index_${shape}`, { - id: 1, - fixed: true, - x: 0, - y: -300, - shape: "dot", - label: new Array(15) - .fill(null) - .map( - (): string => - "This label should be above edge labels and arrows." - ) - .join("\n"), - font: { - color: "green", - size: 40, - }, - }, - { - id: 3, - fixed: true, - x: 100, - y: 300, - shape: "box", - label: new Array(5) - .fill(null) - .map( - (): string => - "This label should be above edge labels but bellow arrows." - ) - .join("\n"), - font: { - color: "black", - size: 20, - }, - }, - ], - edges: [ - { - id: 2, - from: 1, - to: 3, - label: new Array(80) - .fill(null) - .map( - (): string => - "This label should be bellow nodes and their labels but above edge arrows." - ) - .join("\n"), - font: { - color: "red", - size: 10, - }, - arrows: { - from: { - enabled: true, - scaleFactor: 6, + nodes: [ + { + id: 1, + fixed: true, + x: 0, + y: -300, + shape, + label: EXTERNAL_LABEL_SHAPES.has(shape) + ? EXTERNAL_LABEL + : INTERNAL_LABEL, + font: { + color: "green", + size: 40, + }, }, - middle: { - enabled: true, - scaleFactor: 6, + { + id: 3, + fixed: true, + x: 100, + y: 300, + shape: "box", + label: new Array(5) + .fill(null) + .map( + (): string => + "This label should be above edge labels but bellow arrows." + ) + .join("\n"), + font: { + color: "black", + size: 20, + }, }, - to: { - enabled: true, - scaleFactor: 6, + ], + edges: [ + { + id: 2, + from: 1, + to: 3, + label: new Array(80) + .fill(null) + .map( + (): string => + "This label should be bellow nodes and their labels but above edge arrows." + ) + .join("\n"), + font: { + color: "red", + size: 10, + }, + arrows: { + from: { + enabled: true, + scaleFactor: 6, + }, + middle: { + enabled: true, + scaleFactor: 6, + }, + to: { + enabled: true, + scaleFactor: 6, + }, + }, + endPointOffset: { + from: -50, + to: -50, + }, }, - }, - endPointOffset: { - from: -50, - to: -50, - }, + ], }, - ], - } - ); + { requireNewerVersionThan: "8.0.1" } + ); + }); + } }); }); diff --git a/cypress/support/commands/vis-simple-canvas-snapshot.ts b/cypress/support/commands/vis-simple-canvas-snapshot.ts index 638adb71d6..0c1c5f9a00 100644 --- a/cypress/support/commands/vis-simple-canvas-snapshot.ts +++ b/cypress/support/commands/vis-simple-canvas-snapshot.ts @@ -42,7 +42,7 @@ export function visSimpleCanvasSnapshot( deepObjectAssign( { position: { x: 0, y: 0 }, - scale: 1 + scale: 1, }, options.moveTo ?? {} ) diff --git a/lib/network/modules/CanvasRenderer.js b/lib/network/modules/CanvasRenderer.js index e2b042672a..42810ce43e 100644 --- a/lib/network/modules/CanvasRenderer.js +++ b/lib/network/modules/CanvasRenderer.js @@ -246,6 +246,10 @@ class CanvasRenderer { this.redrawRequested = false; + const drawLater = { + drawExternalLabels: null, + } + // when the container div was hidden, this fixes it back up! if (this.canvas.frame.canvas.width === 0 || this.canvas.frame.canvas.height === 0) { this.canvas.setSize(); @@ -284,7 +288,8 @@ class CanvasRenderer { } if (this.dragging === false || (this.dragging === true && this.options.hideNodesOnDrag === false)) { - this._drawNodes(ctx, hidden); + const { drawExternalLabels } = this._drawNodes(ctx, hidden); + drawLater.drawExternalLabels = drawExternalLabels; } // draw the arrows last so they will be at the top @@ -297,6 +302,10 @@ class CanvasRenderer { } } + if (drawLater.drawExternalLabels != null) { + drawLater.drawExternalLabels(); + } + if (hidden === false) { this._drawSelectionBox(ctx) } @@ -352,6 +361,8 @@ class CanvasRenderer { * @param {CanvasRenderingContext2D} ctx 2D context of a HTML canvas * @param {boolean} [alwaysShow] * @private + * + * @returns {Object} Callbacks to draw later on higher layers. */ _drawNodes(ctx, alwaysShow = false) { const nodes = this.body.nodes; @@ -367,6 +378,8 @@ class CanvasRenderer { }); const viewableArea = {top:topLeft.y,left:topLeft.x,bottom:bottomRight.y,right:bottomRight.x}; + const drawExternalLabels = []; + // draw unselected nodes; for (let i = 0; i < nodeIndices.length; i++) { node = nodes[nodeIndices[i]]; @@ -378,10 +391,16 @@ class CanvasRenderer { } else { if (alwaysShow === true) { - node.draw(ctx); + const drawLater = node.draw(ctx); + if (drawLater.drawExternalLabel != null) { + drawExternalLabels.push(drawLater.drawExternalLabel) + } } else if (node.isBoundingBoxOverlappingWith(viewableArea) === true) { - node.draw(ctx); + const drawLater = node.draw(ctx); + if (drawLater.drawExternalLabel != null) { + drawExternalLabels.push(drawLater.drawExternalLabel) + } } else { node.updateBoundingBox(ctx, node.selected); @@ -396,15 +415,28 @@ class CanvasRenderer { // draw the selected nodes on top for (i = 0; i < selectedLength; i++) { node = nodes[selected[i]]; - node.draw(ctx); + const drawLater = node.draw(ctx); + if (drawLater.drawExternalLabel != null) { + drawExternalLabels.push(drawLater.drawExternalLabel) + } } // draw hovered nodes above everything else: fixes https://github.com/visjs/vis-network/issues/226 for (i = 0; i < hoveredLength; i++) { node = nodes[hovered[i]]; - node.draw(ctx); + const drawLater = node.draw(ctx); + if (drawLater.drawExternalLabel != null) { + drawExternalLabels.push(drawLater.drawExternalLabel) + } } + return { + drawExternalLabels: () => { + for (const draw of drawExternalLabels) { + draw(); + } + }, + }; } diff --git a/lib/network/modules/components/Node.js b/lib/network/modules/components/Node.js index 4421d0e096..bee962d74e 100644 --- a/lib/network/modules/components/Node.js +++ b/lib/network/modules/components/Node.js @@ -639,10 +639,19 @@ class Node { * Draw this node in the given canvas * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); * @param {CanvasRenderingContext2D} ctx + * + * @returns {Object} Callbacks to draw later on higher layers. */ draw(ctx) { const values = this.getFormattingValues(); - this.shape.draw(ctx, this.x, this.y, this.selected, this.hover, values); + return this.shape.draw( + ctx, + this.x, + this.y, + this.selected, + this.hover, + values + ) || {}; } diff --git a/lib/network/modules/components/nodes/shapes/Diamond.js b/lib/network/modules/components/nodes/shapes/Diamond.js index 832afc7016..1f342496e8 100644 --- a/lib/network/modules/components/nodes/shapes/Diamond.js +++ b/lib/network/modules/components/nodes/shapes/Diamond.js @@ -25,9 +25,11 @@ class Diamond extends ShapeBase { * @param {boolean} selected * @param {boolean} hover * @param {ArrowOptions} values + * + * @returns {Object} Callbacks to draw later on higher layers. */ draw(ctx, x, y, selected, hover, values) { - this._drawShape(ctx, 'diamond', 4, x, y, selected, hover, values); + return this._drawShape(ctx, 'diamond', 4, x, y, selected, hover, values); } /** diff --git a/lib/network/modules/components/nodes/shapes/Dot.js b/lib/network/modules/components/nodes/shapes/Dot.js index 070ddcf071..3ff7af88b5 100644 --- a/lib/network/modules/components/nodes/shapes/Dot.js +++ b/lib/network/modules/components/nodes/shapes/Dot.js @@ -25,9 +25,11 @@ class Dot extends ShapeBase { * @param {boolean} selected * @param {boolean} hover * @param {ArrowOptions} values + * + * @returns {Object} Callbacks to draw later on higher layers. */ draw(ctx, x, y, selected, hover, values) { - this._drawShape(ctx, 'circle', 2, x, y, selected, hover, values); + return this._drawShape(ctx, 'circle', 2, x, y, selected, hover, values); } /** diff --git a/lib/network/modules/components/nodes/shapes/Hexagon.js b/lib/network/modules/components/nodes/shapes/Hexagon.js index e7359f4f10..0351f54a74 100644 --- a/lib/network/modules/components/nodes/shapes/Hexagon.js +++ b/lib/network/modules/components/nodes/shapes/Hexagon.js @@ -25,9 +25,11 @@ class Hexagon extends ShapeBase { * @param {boolean} selected * @param {boolean} hover * @param {ArrowOptions} values + * + * @returns {Object} Callbacks to draw later on higher layers. */ draw(ctx, x, y, selected, hover, values) { - this._drawShape(ctx, 'hexagon', 4, x, y, selected, hover, values); + return this._drawShape(ctx, 'hexagon', 4, x, y, selected, hover, values); } /** diff --git a/lib/network/modules/components/nodes/shapes/Icon.js b/lib/network/modules/components/nodes/shapes/Icon.js index 1f3a9fa00c..6bfb81d6d9 100644 --- a/lib/network/modules/components/nodes/shapes/Icon.js +++ b/lib/network/modules/components/nodes/shapes/Icon.js @@ -44,6 +44,8 @@ class Icon extends NodeBase { * @param {boolean} selected * @param {boolean} hover * @param {ArrowOptions} values + * + * @returns {Object} Callbacks to draw later on higher layers. */ draw(ctx, x, y, selected, hover, values) { this.resize(ctx, selected, hover); @@ -53,13 +55,21 @@ class Icon extends NodeBase { this.top = y - this.height / 2; this._icon(ctx, x, y, selected, hover, values); - if (this.options.label !== undefined) { - const iconTextSpacing = 5; - this.labelModule.draw(ctx, this.left + this.iconSize.width / 2 + this.margin.left, - y + this.height / 2 + iconTextSpacing, selected); - } - - this.updateBoundingBox(x, y) + return { + drawExternalLabel: () => { + if (this.options.label !== undefined) { + const iconTextSpacing = 5; + this.labelModule.draw( + ctx, + this.left + this.iconSize.width / 2 + this.margin.left, + y + this.height / 2 + iconTextSpacing, + selected + ); + } + + this.updateBoundingBox(x, y) + } + }; } /** diff --git a/lib/network/modules/components/nodes/shapes/Square.js b/lib/network/modules/components/nodes/shapes/Square.js index b61b7e5b8a..50eca5b9fb 100644 --- a/lib/network/modules/components/nodes/shapes/Square.js +++ b/lib/network/modules/components/nodes/shapes/Square.js @@ -25,9 +25,11 @@ class Square extends ShapeBase { * @param {boolean} selected * @param {boolean} hover * @param {ArrowOptions} values + * + * @returns {Object} Callbacks to draw later on higher layers. */ draw(ctx, x, y, selected, hover, values) { - this._drawShape(ctx, 'square', 2, x, y, selected, hover, values); + return this._drawShape(ctx, 'square', 2, x, y, selected, hover, values); } /** diff --git a/lib/network/modules/components/nodes/shapes/Star.js b/lib/network/modules/components/nodes/shapes/Star.js index d9bf4fb33b..803bd5b087 100644 --- a/lib/network/modules/components/nodes/shapes/Star.js +++ b/lib/network/modules/components/nodes/shapes/Star.js @@ -25,9 +25,11 @@ class Star extends ShapeBase { * @param {boolean} selected * @param {boolean} hover * @param {ArrowOptions} values + * + * @returns {Object} Callbacks to draw later on higher layers. */ draw(ctx, x, y, selected, hover, values) { - this._drawShape(ctx, 'star', 4, x, y, selected, hover, values); + return this._drawShape(ctx, 'star', 4, x, y, selected, hover, values); } /** diff --git a/lib/network/modules/components/nodes/shapes/Triangle.js b/lib/network/modules/components/nodes/shapes/Triangle.js index 9c67b0da04..5c41566dd8 100644 --- a/lib/network/modules/components/nodes/shapes/Triangle.js +++ b/lib/network/modules/components/nodes/shapes/Triangle.js @@ -25,9 +25,11 @@ class Triangle extends ShapeBase { * @param {boolean} selected * @param {boolean} hover * @param {ArrowOptions} values + * + * @returns {Object} Callbacks to draw later on higher layers. */ draw(ctx, x, y, selected, hover, values) { - this._drawShape(ctx, 'triangle', 3, x, y, selected, hover, values); + return this._drawShape(ctx, 'triangle', 3, x, y, selected, hover, values); } /** diff --git a/lib/network/modules/components/nodes/shapes/TriangleDown.js b/lib/network/modules/components/nodes/shapes/TriangleDown.js index f87c3d075f..69c7f8b25e 100644 --- a/lib/network/modules/components/nodes/shapes/TriangleDown.js +++ b/lib/network/modules/components/nodes/shapes/TriangleDown.js @@ -25,9 +25,20 @@ class TriangleDown extends ShapeBase { * @param {boolean} selected * @param {boolean} hover * @param {ArrowOptions} values + * + * @returns {Object} Callbacks to draw later on higher layers. */ draw(ctx, x, y, selected, hover, values) { - this._drawShape(ctx, 'triangleDown', 3, x, y, selected, hover, values); + return this._drawShape( + ctx, + 'triangleDown', + 3, + x, + y, + selected, + hover, + values + ); } /** diff --git a/lib/network/modules/components/nodes/util/ShapeBase.js b/lib/network/modules/components/nodes/util/ShapeBase.js index 9d3b5fdb89..5a5baa7dc7 100644 --- a/lib/network/modules/components/nodes/util/ShapeBase.js +++ b/lib/network/modules/components/nodes/util/ShapeBase.js @@ -45,6 +45,8 @@ class ShapeBase extends NodeBase { * @param {ArrowOptions} values * @param {function} customRenderer - a custom shape renderer similar to getShape(shape) functions * @private + * + * @returns {Object} Callbacks to draw later on higher layers. */ _drawShape(ctx, shape, sizeMultiplier, x, y, selected, hover, values, customRenderer) { this.resize(ctx, selected, hover, values); @@ -74,14 +76,27 @@ class ShapeBase extends NodeBase { } } - if (this.options.label !== undefined) { - // Need to call following here in order to ensure value for `this.labelModule.size.height` - this.labelModule.calculateLabelSize(ctx, selected, hover, x, y, 'hanging') - const yLabel = y + 0.5 * this.height + 0.5 * this.labelModule.size.height; - this.labelModule.draw(ctx, x, yLabel, selected, hover, 'hanging'); - } + return { + drawExternalLabel: () => { + if (this.options.label !== undefined) { + // Need to call following here in order to ensure value for + // `this.labelModule.size.height`. + this.labelModule.calculateLabelSize( + ctx, + selected, + hover, + x, + y, + 'hanging' + ); + const yLabel = + y + 0.5 * this.height + 0.5 * this.labelModule.size.height; + this.labelModule.draw(ctx, x, yLabel, selected, hover, 'hanging'); + } - this.updateBoundingBox(x,y); + this.updateBoundingBox(x,y); + }, + }; } /**