Skip to content

Commit

Permalink
feat: add node opacity (#554)
Browse files Browse the repository at this point in the history
* Fix: #226

* Add opacity option to nodes

* Fix shadow color being mangled

* Remove console.log

* Change to use util version of overrideOpacity

* Fix updating groups instead of node

Change import to only import overrideOpacity

* Fix local opacity not being applied

* Fix wrong property name

* Fix drawing selected instead of hovered

* Update example

* Update example

* Fix opacity of 0 wouldn't pass

* Fix checks for truthiness instead of nullness

Add groups to example

* Added to docs

KR Docs todo

* Update example

Fix problems with values invalid for opacity

* Update example

Fix error logging for invalid node opacity options

* Create function to check opacity

Clear up comment on opacity option

* Fix image opacity transferring to shapes

* Remove console.log

* WIP: Fix group opacity overriding node opacity

* Fix group options overriding local options

Co-authored-by: Tyler Maclachlan <[email protected]>
  • Loading branch information
Tyler-Maclachlan and Tyler Maclachlan authored Mar 31, 2020
1 parent 1cef4a2 commit b9ff848
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 11 deletions.
8 changes: 8 additions & 0 deletions docs-kr/network/nodes.html
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,14 @@ <h3>옵션</h3>
<td> 마우스로 이리저리 이동하는 Node의 배경의 색상 <i>(상호작용 모듈안에서 호버가 활성화 되어있다고 가정)</i>.</td>

</tr>
<tr>
<td>opacity</td>
<td>Number</td>
<td><code>undefined</code></td>
<td>
<!-- TODO -->
</td>
</tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','fixed', this);">
<td><span parent="fixed" class="right-caret"></span> fixed</td>
<td>Object or Boolean</td>
Expand Down
7 changes: 7 additions & 0 deletions docs/network/nodes.html
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ <h3>Options</h3>
background: '#D2E5FF'
}
},
opacity: 1,
fixed: {
x:false,
y:false
Expand Down Expand Up @@ -417,6 +418,12 @@ <h3>Options</h3>
the interaction module)</i>.
</td>
</tr>
<tr>
<td>opacity</td>
<td>Number</td>
<td><code>undefined</code></td>
<td>Overall opacity of a node <i>(overrides any opacity on border, background, image, and shadow)</i></td>
</tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','fixed', this);">
<td><span parent="fixed" class="right-caret"></span> fixed</td>
<td>Object or Boolean</td>
Expand Down
92 changes: 92 additions & 0 deletions examples/network/nodeStyles/imagesWithOpacity.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<!doctype html>
<html>
<head>
<title>Vis Network | Node Styles | Images with Opacity</title>

<style type="text/css">
#mynetwork {
width: 600px;
height: 600px;
border: 1px solid lightgray;
}
</style>

<script type="text/javascript" src="../../../standalone/umd/vis-network.min.js"></script>

<script type="text/javascript">
var nodes = null;
var edges = null;
var network = null;

var DIR = '../img/refresh-cl/';
var EDGE_LENGTH_MAIN = 150;
var EDGE_LENGTH_SUB = 50;

// Called when the Visualization API is loaded.
function draw() {
// Create a data table with nodes.
nodes = [];

// Create a data table with links.
edges = [];

nodes.push({id: 1, label: 'Main', image: DIR + 'Network-Pipe-icon.png', shape: 'image', opacity: .7});
nodes.push({id: 2, label: 'Office', image: DIR + 'Network-Pipe-icon.png', shape: 'image'});
nodes.push({id: 3, label: 'Wireless', image: DIR + 'Network-Pipe-icon.png', shape: 'image'});
nodes.push({id: 22, label: 'Normal', opacity: 1})
edges.push({from: 1, to: 2, length: EDGE_LENGTH_MAIN});
edges.push({from: 1, to: 3, length: EDGE_LENGTH_MAIN});
edges.push({from: 1, to: 22, length: EDGE_LENGTH_MAIN});

for (var i = 4; i <= 7; i++) {
nodes.push({id: i, label: 'Computer', image: DIR + 'Hardware-My-Computer-3-icon.png', shape: 'image', group: 'computer', opacity: 1});
edges.push({from: 2, to: i, length: EDGE_LENGTH_SUB});
}

nodes.push({id: 101, label: 'Printer', image: DIR + 'Hardware-Printer-Blue-icon.png', shape: 'image'});
edges.push({from: 2, to: 101, length: EDGE_LENGTH_SUB});

nodes.push({id: 102, label: 'Laptop', image: DIR + 'Hardware-Laptop-1-icon.png', shape: 'image'});
edges.push({from: 3, to: 102, length: EDGE_LENGTH_SUB});

nodes.push({id: 103, label: 'network drive', image: DIR + 'Network-Drive-icon.png', shape: 'image'});
edges.push({from: 1, to: 103, length: EDGE_LENGTH_SUB});

nodes.push({id: 104, label: 'Internet', image: DIR + 'System-Firewall-2-icon.png', shape: 'image'});
edges.push({from: 1, to: 104, length: EDGE_LENGTH_SUB});

for (var i = 200; i <= 201; i++ ) {
nodes.push({id: i, label: 'Smartphone', image: DIR + 'Hardware-My-PDA-02-icon.png', shape: 'image'});
edges.push({from: 3, to: i, length: EDGE_LENGTH_SUB});
}

// create a network
var container = document.getElementById('mynetwork');
var data = {
nodes: nodes,
edges: edges
};
var options = {
// nodes: {
// opacity: .5
// },
groups: {
computer: {
opacity: .3
}
}
};
network = new vis.Network(container, data, options);
}
</script>


<body onload="draw()">

<p>
Display nodes with global opacity, individual opacity, and opacity in a group.
</p>
<div id="mynetwork"></div>

</body>
</html>
20 changes: 17 additions & 3 deletions lib/network/modules/CanvasRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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 {
Expand All @@ -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);
}

}


Expand Down
12 changes: 12 additions & 0 deletions lib/network/modules/NodesHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class NodesHandler {
background: '#D2E5FF'
}
},
opacity: undefined, // number between 0 and 1

This comment has been minimized.

Copy link
@kumaryatham

kumaryatham Jun 5, 2020

Is this for #635 issue?

fixed: {
x: false,
y: false
Expand Down Expand Up @@ -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) {
Expand Down
55 changes: 52 additions & 3 deletions lib/network/modules/components/Node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}


/**
Expand All @@ -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
Expand All @@ -245,7 +273,6 @@ class Node {
* @static
*/
static parseOptions(parentOptions, newOptions, allowDeletion = false, globalOptions = {}, groupList) {

var fields = [
'color',
'fixed',
Expand All @@ -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);

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}

Expand Down
14 changes: 11 additions & 3 deletions lib/network/modules/components/nodes/shapes/Image.js
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -91,6 +98,7 @@ class Image extends CircleImageBase {
this._drawImageLabel(ctx, x, y, selected, hover);

this.updateBoundingBox(x,y);
ctx.restore();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 4 additions & 1 deletion lib/network/modules/components/nodes/util/NodeBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Loading

0 comments on commit b9ff848

Please sign in to comment.