UNPKG

@nodesecure/vis-network

Version:

NodeSecure vis.js network front module

212 lines (177 loc) 6.25 kB
// Import Third-party Dependencies import { Network } from "vis-network/standalone/esm/index.js"; // Import Internal Dependencies import * as CONSTANTS from "./constants.js"; import * as utils from "./utils.js"; import NodeSecureDataSet from "./dataset.js"; // CONSTANTS export const NETWORK_OPTIONS = { nodes: { mass: 6, shape: "box", size: 5, font: { face: "Roboto", vadjust: 2, size: 40 }, margin: 20, shadow: { enabled: true, color: "rgba(20, 20, 20, 0.1)" } }, edges: { arrows: "from", hoverWidth: 3, selectionWidth: 3, width: 2 }, physics: { forceAtlas2Based: { gravitationalConstant: -35, centralGravity: 0.002, springLength: 200, springConstant: 0.17, avoidOverlap: 0.8 }, maxVelocity: 150, solver: "forceAtlas2Based", timestep: 0.35, stabilization: { enabled: true } } }; export default class NodeSecureNetwork { // DOM Elements static networkElementId = "network-graph"; static networkLoaderElementId = "network-loader"; /** * @param {!NodeSecureDataSet} secureDataSet * @param {object} [options] * @param {"LIGHT" | "DARK"} [options.theme="LIGHT"] * @param {*} [options.colors] */ constructor(secureDataSet, options = {}) { console.log("[Network] created"); const networkElement = document.getElementById(NodeSecureNetwork.networkElementId); networkElement.click(); this.secureDataSet = secureDataSet; this.highlightEnabled = false; this.isLoaded = false; const { nodes, edges } = secureDataSet.build(); const theme = options.theme?.toUpperCase() ?? 'LIGHT'; if (!(theme in CONSTANTS.COLORS)) { throw new Error(`Unknown theme ${options.theme}. Theme value can be LIGHT or DARK`) } this.theme = theme; this.colors = { ...CONSTANTS.COLORS[this.theme], ...(options.colors ?? {}) }; this.nodes = nodes; this.edges = edges; this.linker = secureDataSet.linker; this.network = new Network(networkElement, { nodes, edges }, NETWORK_OPTIONS); this.network.on("stabilizationIterationsDone", () => { if (this.isLoaded) { this.network.focus(0, { animation: true, scale: 0.35 }); return; } console.log("[NETWORK] stabilizationIterationsDone triggered"); const networkLoaderElement = document.getElementById(NodeSecureNetwork.networkLoaderElementId); networkLoaderElement.style.display = "none"; this.isLoaded = true; this.network.stopSimulation(); this.network.on("click", this.neighbourHighlight.bind(this)); }); this.network.stabilize(500); } /** * @description Focus/move to a Node by id * @param {number} [id=0] */ focusNodeById(id = 0) { this.network.emit("click", { nodes: [id] }); } /** * @description Focus/move to a Node by package name * @param {!string} packageName * @returns {boolean} */ focusNodeByName(packageName) { let wantedId = null; for (const [id, opt] of this.linker) { if (opt.name === packageName) { wantedId = id; break; } } if (wantedId !== null) { this.focusNodeById(wantedId); return true; } return false; } /** * @param {!number} node * @param {boolean} hidden */ highlightNodeNeighbour(node, hidden = false) { this.network.startSimulation(); const updatedNodes = [...this.searchForNeighbourIds(node)] .map((id) => ({ id, hidden })); this.nodes.update(updatedNodes); } * searchForNeighbourIds(/** @type {number} */selectedNode) { const { name, version } = this.linker.get(selectedNode); for (const descriptor of Object.values(this.secureDataSet.data.dependencies)) { for (const { id, usedBy } of Object.values(descriptor.versions)) { if (Reflect.has(usedBy, name) && usedBy[name] === version) { yield* this.searchForNeighbourIds(id); yield id; } } } } neighbourHighlight(params) { const allNodes = this.nodes.get({ returnType: "Object" }); const allEdges = this.edges.get(); // if something is selected: if (params.nodes.length > 0) { this.highlightEnabled = true; const selectedNode = params.nodes[0]; // mark all nodes as hard to read. for (const node of Object.values(allNodes)) { Object.assign(node, this.colors.HARDTOREAD); } // get the second degree nodes const connectedNodes = this.network.getConnectedNodes(selectedNode); const allConnectedNodes = []; for (let id = 0; id < connectedNodes.length; id++) { allConnectedNodes.push(...this.network.getConnectedNodes(connectedNodes[id])); } // all second degree nodes get a different color and their label back for (let id = 0; id < allConnectedNodes.length; id++) { Object.assign(allNodes[allConnectedNodes[id]], this.colors.DEFAULT); } // all first degree nodes get their own color and their label back for (let id = 0; id < connectedNodes.length; id++) { const isNodeConnectedIn = allEdges.some(edge => edge.from === selectedNode && edge.to === connectedNodes[id]); const color = this.colors[isNodeConnectedIn ? "CONNECTED_IN" : "CONNECTED_OUT"]; Object.assign(allNodes[connectedNodes[id]], color); } // the main node gets its own color and its label back. Object.assign(allNodes[selectedNode], this.colors.SELECTED); this.network.focus(selectedNode, { animation: true, scale: 0.35 }); } else if (this.highlightEnabled) { this.highlightEnabled = false; for (const node of Object.values(allNodes)) { const { id, hasWarnings } = this.linker.get(Number(node.id)); Object.assign(node, utils.getNodeColor(id, hasWarnings, this.theme)); } } // transform the object into an array this.nodes.update(Object.values(allNodes)); this.network.stopSimulation(); } }