diff --git a/docs-kr/network/nodes.html b/docs-kr/network/nodes.html index becde518fd..263862c5e1 100644 --- a/docs-kr/network/nodes.html +++ b/docs-kr/network/nodes.html @@ -413,6 +413,14 @@
undefinedundefined+ Display nodes with global opacity, individual opacity, and opacity in a group. +
+ + + + diff --git a/lib/network/modules/CanvasRenderer.js b/lib/network/modules/CanvasRenderer.js index 20f47f9178..802f31e396 100644 --- a/lib/network/modules/CanvasRenderer.js +++ b/lib/network/modules/CanvasRenderer.js @@ -343,6 +343,7 @@ class CanvasRenderer { let nodeIndices = this.body.nodeIndices; let node; let selected = []; + let hovered = []; let margin = 20; let topLeft = this.canvas.DOMtoCanvas({x:-margin,y:-margin}); let bottomRight = this.canvas.DOMtoCanvas({ @@ -354,8 +355,10 @@ class CanvasRenderer { // draw unselected nodes; for (let i = 0; i < nodeIndices.length; i++) { node = nodes[nodeIndices[i]]; - // set selected nodes aside - if (node.isSelected()) { + // set selected and hovered nodes aside + if (node.hover) { + hovered.push(nodeIndices[i]); + } else if (node.isSelected()) { selected.push(nodeIndices[i]); } else { @@ -371,11 +374,22 @@ class CanvasRenderer { } } + let i; + const selectedLength = selected.length; + const hoveredLength = hovered.length; + // draw the selected nodes on top - for (let i = 0; i < selected.length; i++) { + for (i = 0; i < selectedLength; i++) { node = nodes[selected[i]]; node.draw(ctx); } + + // 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); + } + } diff --git a/lib/network/modules/NodesHandler.js b/lib/network/modules/NodesHandler.js index 70b9a5d88f..21e1a8f7ec 100644 --- a/lib/network/modules/NodesHandler.js +++ b/lib/network/modules/NodesHandler.js @@ -43,6 +43,7 @@ class NodesHandler { background: '#D2E5FF' } }, + opacity: undefined, // number between 0 and 1 fixed: { x: false, y: false @@ -178,6 +179,17 @@ class NodesHandler { setOptions(options) { if (options !== undefined) { Node.parseOptions(this.options, options); + + // Need to set opacity here because Node.parseOptions is also used for groups, + // if you set opacity in Node.parseOptions it overwrites group opacity. + if (options.opacity !== undefined) { + if (Number.isNaN(options.opacity) || !Number.isFinite(options.opacity) || options.opacity < 0 || options.opacity > 1) { + console.error("Invalid option for node opacity. Value must be between 0 and 1, found: " + options.opacity); + } + else { + this.options.opacity = options.opacity; + } + } // update the shape in all nodes if (options.shape !== undefined) { diff --git a/lib/network/modules/components/Node.js b/lib/network/modules/components/Node.js index cd1822a954..f076212af7 100644 --- a/lib/network/modules/components/Node.js +++ b/lib/network/modules/components/Node.js @@ -103,6 +103,7 @@ class Node { */ setOptions(options) { let currentShape = this.options.shape; + if (!options) { return; // Note that the return value will be 'undefined'! This is OK. } @@ -139,12 +140,20 @@ class Node { // this transforms all shorthands into fully defined options Node.parseOptions(this.options, options, true, this.globalOptions, this.grouplist); - + let pile = [options, this.options, this.defaultOptions]; this.chooser = ComponentUtil.choosify('node', pile); + + this._load_images(); this.updateLabelModule(options); + + // Need to set local opacity after `this.updateLabelModule(options);` because `this.updateLabelModule(options);` overrites local opacity with group opacity + if (options.opacity !== undefined && Node.checkOpacity(options.opacity)) { + this.options.opacity = options.opacity; + } + this.updateShape(currentShape); return (options.hidden !== undefined || options.physics !== undefined); @@ -190,6 +199,16 @@ class Node { } } } + + /** + * Check that opacity is only between 0 and 1 + * + * @param {Number} opacity + * @returns {boolean} + */ + static checkOpacity (opacity) { + return 0 <= opacity && opacity <= 1; + } /** @@ -214,11 +233,20 @@ class Node { throw new Error("updateGroupOptions: group values in options don't match."); } + var hasGroup = (typeof group === 'number' || (typeof group === 'string' && group != '')); if (!hasGroup) return; // current node has no group, no need to merge - + + var groupObj = groupList.get(group); + if (groupObj.opacity !== undefined && newOptions.opacity === undefined) { + if (!Node.checkOpacity(groupObj.opacity)) { + console.error("Invalid option for node opacity. Value must be between 0 and 1, found: " + groupObj.opacity); + groupObj.opacity = undefined; + } + } + // Skip merging of group font options into parent; these are required to be distinct for labels // Also skip mergin of color IF it is already defined in the node itself. This is to avoid the color of the // group overriding the color set at the node level @@ -245,7 +273,6 @@ class Node { * @static */ static parseOptions(parentOptions, newOptions, allowDeletion = false, globalOptions = {}, groupList) { - var fields = [ 'color', 'fixed', @@ -255,6 +282,21 @@ class Node { Node.checkMass(newOptions); + + if (parentOptions.opacity !== undefined) { + if (!Node.checkOpacity(parentOptions.opacity)) { + console.error("Invalid option for node opacity. Value must be between 0 and 1, found: " + parentOptions.opacity); + parentOptions.opacity = undefined; + } + } + + if (newOptions.opacity !== undefined) { + if (!Node.checkOpacity(newOptions.opacity)) { + console.error("Invalid option for node opacity. Value must be between 0 and 1, found: " + newOptions.opacity); + newOptions.opacity = undefined; + } + } + // merge the shadow options into the parent. util.mergeOptions(parentOptions, newOptions, 'shadow', globalOptions); @@ -303,6 +345,7 @@ class Node { getFormattingValues() { let values = { color: this.options.color.background, + opacity: this.options.opacity, borderWidth: this.options.borderWidth, borderColor: this.options.color.border, size: this.options.size, @@ -340,6 +383,12 @@ class Node { } else { values.shadow = this.options.shadow.enabled; } + if (this.options.opacity !== undefined) { + const opacity = this.options.opacity; + values.borderColor = util.overrideOpacity(values.borderColor, opacity); + values.color = util.overrideOpacity(values.color, opacity); + values.shadowColor = util.overrideOpacity(values.shadowColor, opacity); + } return values; } diff --git a/lib/network/modules/components/nodes/shapes/Image.js b/lib/network/modules/components/nodes/shapes/Image.js index 1aa2a9580d..ce84f275a4 100644 --- a/lib/network/modules/components/nodes/shapes/Image.js +++ b/lib/network/modules/components/nodes/shapes/Image.js @@ -1,7 +1,7 @@ 'use strict'; import CircleImageBase from '../util/CircleImageBase' - +import { overrideOpacity } from 'vis-util/esnext'; /** * An image-based replacement for the default Node shape. @@ -55,6 +55,7 @@ class Image extends CircleImageBase { * @param {ArrowOptions} values */ draw(ctx, x, y, selected, hover, values) { + ctx.save(); this.switchImages(selected); this.resize(); this.left = x - this.width / 2; @@ -67,12 +68,18 @@ class Image extends CircleImageBase { ctx.lineWidth = Math.min(this.width, borderWidth); ctx.beginPath(); + let strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border; + let fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background; + if (values.opacity !== undefined) { + strokeStyle = overrideOpacity(strokeStyle, values.opacity); + fillStyle = overrideOpacity(fillStyle, values.opacity); + } // setup the line properties. - ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border; + ctx.strokeStyle = strokeStyle; // set a fillstyle - ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background; + ctx.fillStyle = fillStyle; // draw a rectangle to form the border around. This rectangle is filled so the opacity of a picture (in future vis releases?) can be used to tint the image ctx.rect(this.left - 0.5 * ctx.lineWidth, @@ -91,6 +98,7 @@ class Image extends CircleImageBase { this._drawImageLabel(ctx, x, y, selected, hover); this.updateBoundingBox(x,y); + ctx.restore(); } /** diff --git a/lib/network/modules/components/nodes/util/CircleImageBase.js b/lib/network/modules/components/nodes/util/CircleImageBase.js index 1c0f4c82d7..d5077c48f7 100644 --- a/lib/network/modules/components/nodes/util/CircleImageBase.js +++ b/lib/network/modules/components/nodes/util/CircleImageBase.js @@ -170,7 +170,7 @@ class CircleImageBase extends NodeBase { _drawImageAtPosition(ctx, values) { if (this.imageObj.width != 0) { // draw the image - ctx.globalAlpha = 1.0; + ctx.globalAlpha = values.opacity !== undefined ? values.opacity : 1; // draw shadow if enabled this.enableShadow(ctx, values); diff --git a/lib/network/modules/components/nodes/util/NodeBase.js b/lib/network/modules/components/nodes/util/NodeBase.js index 2ac9234724..ab1ba64f03 100644 --- a/lib/network/modules/components/nodes/util/NodeBase.js +++ b/lib/network/modules/components/nodes/util/NodeBase.js @@ -196,13 +196,16 @@ class NodeBase { * @param {ArrowOptions} values */ performFill(ctx, values) { + ctx.save(); + ctx.fillStyle = values.color; // draw shadow if enabled this.enableShadow(ctx, values); // draw the background ctx.fill(); // disable shadows for other elements. this.disableShadow(ctx, values); - + + ctx.restore(); this.performStroke(ctx, values); } diff --git a/lib/network/options.js b/lib/network/options.js index 14451c3750..46e788f473 100644 --- a/lib/network/options.js +++ b/lib/network/options.js @@ -272,6 +272,7 @@ let allOptions = { }, __type__: { object, string } }, + opacity: { number, 'undefined': 'undefined' }, fixed: { x: { boolean: bool }, y: { boolean: bool }, @@ -520,6 +521,7 @@ let configureOptions = { background: ['color', '#D2E5FF'] } }, + opacity: [0, 0, 1, 0.1], fixed: { x: false, y: false diff --git a/types/network/Network.d.ts b/types/network/Network.d.ts index 1338b3eb15..eb80ae8727 100644 --- a/types/network/Network.d.ts +++ b/types/network/Network.d.ts @@ -826,6 +826,8 @@ export interface NodeOptions { color?: string | Color; + opacity?: number; + fixed?: boolean | { x?: boolean, y?: boolean,