initial commit
This commit is contained in:
514
node_modules/vis/lib/network/modules/NodesHandler.js
generated
vendored
Normal file
514
node_modules/vis/lib/network/modules/NodesHandler.js
generated
vendored
Normal file
@@ -0,0 +1,514 @@
|
||||
let util = require("../../util");
|
||||
let DataSet = require('../../DataSet');
|
||||
let DataView = require('../../DataView');
|
||||
var Node = require("./components/Node").default;
|
||||
|
||||
|
||||
/**
|
||||
* Handler for Nodes
|
||||
*/
|
||||
class NodesHandler {
|
||||
/**
|
||||
* @param {Object} body
|
||||
* @param {Images} images
|
||||
* @param {Array.<Group>} groups
|
||||
* @param {LayoutEngine} layoutEngine
|
||||
*/
|
||||
constructor(body, images, groups, layoutEngine) {
|
||||
this.body = body;
|
||||
this.images = images;
|
||||
this.groups = groups;
|
||||
this.layoutEngine = layoutEngine;
|
||||
|
||||
// create the node API in the body container
|
||||
this.body.functions.createNode = this.create.bind(this);
|
||||
|
||||
this.nodesListeners = {
|
||||
add: (event, params) => { this.add(params.items); },
|
||||
update: (event, params) => { this.update(params.items, params.data, params.oldData); },
|
||||
remove: (event, params) => { this.remove(params.items); }
|
||||
};
|
||||
|
||||
this.defaultOptions = {
|
||||
borderWidth: 1,
|
||||
borderWidthSelected: 2,
|
||||
brokenImage: undefined,
|
||||
color: {
|
||||
border: '#2B7CE9',
|
||||
background: '#97C2FC',
|
||||
highlight: {
|
||||
border: '#2B7CE9',
|
||||
background: '#D2E5FF'
|
||||
},
|
||||
hover: {
|
||||
border: '#2B7CE9',
|
||||
background: '#D2E5FF'
|
||||
}
|
||||
},
|
||||
fixed: {
|
||||
x: false,
|
||||
y: false
|
||||
},
|
||||
font: {
|
||||
color: '#343434',
|
||||
size: 14, // px
|
||||
face: 'arial',
|
||||
background: 'none',
|
||||
strokeWidth: 0, // px
|
||||
strokeColor: '#ffffff',
|
||||
align: 'center',
|
||||
vadjust: 0,
|
||||
multi: false,
|
||||
bold: {
|
||||
mod: 'bold'
|
||||
},
|
||||
boldital: {
|
||||
mod: 'bold italic'
|
||||
},
|
||||
ital: {
|
||||
mod: 'italic'
|
||||
},
|
||||
mono: {
|
||||
mod: '',
|
||||
size: 15, // px
|
||||
face: 'monospace',
|
||||
vadjust: 2
|
||||
}
|
||||
},
|
||||
group: undefined,
|
||||
hidden: false,
|
||||
icon: {
|
||||
face: 'FontAwesome', //'FontAwesome',
|
||||
code: undefined, //'\uf007',
|
||||
size: 50, //50,
|
||||
color: '#2B7CE9' //'#aa00ff'
|
||||
},
|
||||
image: undefined, // --> URL
|
||||
label: undefined,
|
||||
labelHighlightBold: true,
|
||||
level: undefined,
|
||||
margin: {
|
||||
top: 5,
|
||||
right: 5,
|
||||
bottom: 5,
|
||||
left: 5
|
||||
},
|
||||
mass: 1,
|
||||
physics: true,
|
||||
scaling: {
|
||||
min: 10,
|
||||
max: 30,
|
||||
label: {
|
||||
enabled: false,
|
||||
min: 14,
|
||||
max: 30,
|
||||
maxVisible: 30,
|
||||
drawThreshold: 5
|
||||
},
|
||||
customScalingFunction: function (min, max, total, value) {
|
||||
if (max === min) {
|
||||
return 0.5;
|
||||
}
|
||||
else {
|
||||
let scale = 1 / (max - min);
|
||||
return Math.max(0, (value - min) * scale);
|
||||
}
|
||||
}
|
||||
},
|
||||
shadow: {
|
||||
enabled: false,
|
||||
color: 'rgba(0,0,0,0.5)',
|
||||
size: 10,
|
||||
x: 5,
|
||||
y: 5
|
||||
},
|
||||
shape: 'ellipse',
|
||||
shapeProperties: {
|
||||
borderDashes: false, // only for borders
|
||||
borderRadius: 6, // only for box shape
|
||||
interpolation: true, // only for image and circularImage shapes
|
||||
useImageSize: false, // only for image and circularImage shapes
|
||||
useBorderWithImage: false // only for image shape
|
||||
},
|
||||
size: 25,
|
||||
title: undefined,
|
||||
value: undefined,
|
||||
x: undefined,
|
||||
y: undefined
|
||||
};
|
||||
|
||||
// Protect from idiocy
|
||||
if (this.defaultOptions.mass <= 0) {
|
||||
throw 'Internal error: mass in defaultOptions of NodesHandler may not be zero or negative';
|
||||
}
|
||||
|
||||
this.options = util.bridgeObject(this.defaultOptions);
|
||||
|
||||
this.bindEventListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds event listeners
|
||||
*/
|
||||
bindEventListeners() {
|
||||
// refresh the nodes. Used when reverting from hierarchical layout
|
||||
this.body.emitter.on('refreshNodes', this.refresh.bind(this));
|
||||
this.body.emitter.on('refresh', this.refresh.bind(this));
|
||||
this.body.emitter.on('destroy', () => {
|
||||
util.forEach(this.nodesListeners, (callback, event) => {
|
||||
if (this.body.data.nodes)
|
||||
this.body.data.nodes.off(event, callback);
|
||||
});
|
||||
delete this.body.functions.createNode;
|
||||
delete this.nodesListeners.add;
|
||||
delete this.nodesListeners.update;
|
||||
delete this.nodesListeners.remove;
|
||||
delete this.nodesListeners;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
setOptions(options) {
|
||||
if (options !== undefined) {
|
||||
Node.parseOptions(this.options, options);
|
||||
|
||||
// update the shape in all nodes
|
||||
if (options.shape !== undefined) {
|
||||
for (let nodeId in this.body.nodes) {
|
||||
if (this.body.nodes.hasOwnProperty(nodeId)) {
|
||||
this.body.nodes[nodeId].updateShape();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update the font in all nodes
|
||||
if (options.font !== undefined) {
|
||||
for (let nodeId in this.body.nodes) {
|
||||
if (this.body.nodes.hasOwnProperty(nodeId)) {
|
||||
this.body.nodes[nodeId].updateLabelModule();
|
||||
this.body.nodes[nodeId].needsRefresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update the shape size in all nodes
|
||||
if (options.size !== undefined) {
|
||||
for (let nodeId in this.body.nodes) {
|
||||
if (this.body.nodes.hasOwnProperty(nodeId)) {
|
||||
this.body.nodes[nodeId].needsRefresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update the state of the variables if needed
|
||||
if (options.hidden !== undefined || options.physics !== undefined) {
|
||||
this.body.emitter.emit('_dataChanged');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a data set with nodes for the network
|
||||
* @param {Array | DataSet | DataView} nodes The data containing the nodes.
|
||||
* @param {boolean} [doNotEmit=false]
|
||||
* @private
|
||||
*/
|
||||
setData(nodes, doNotEmit = false) {
|
||||
let oldNodesData = this.body.data.nodes;
|
||||
|
||||
if (nodes instanceof DataSet || nodes instanceof DataView) {
|
||||
this.body.data.nodes = nodes;
|
||||
}
|
||||
else if (Array.isArray(nodes)) {
|
||||
this.body.data.nodes = new DataSet();
|
||||
this.body.data.nodes.add(nodes);
|
||||
}
|
||||
else if (!nodes) {
|
||||
this.body.data.nodes = new DataSet();
|
||||
}
|
||||
else {
|
||||
throw new TypeError('Array or DataSet expected');
|
||||
}
|
||||
|
||||
if (oldNodesData) {
|
||||
// unsubscribe from old dataset
|
||||
util.forEach(this.nodesListeners, function (callback, event) {
|
||||
oldNodesData.off(event, callback);
|
||||
});
|
||||
}
|
||||
|
||||
// remove drawn nodes
|
||||
this.body.nodes = {};
|
||||
|
||||
if (this.body.data.nodes) {
|
||||
// subscribe to new dataset
|
||||
let me = this;
|
||||
util.forEach(this.nodesListeners, function (callback, event) {
|
||||
me.body.data.nodes.on(event, callback);
|
||||
});
|
||||
|
||||
// draw all new nodes
|
||||
let ids = this.body.data.nodes.getIds();
|
||||
this.add(ids, true);
|
||||
}
|
||||
|
||||
if (doNotEmit === false) {
|
||||
this.body.emitter.emit("_dataChanged");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add nodes
|
||||
* @param {number[] | string[]} ids
|
||||
* @param {boolean} [doNotEmit=false]
|
||||
* @private
|
||||
*/
|
||||
add(ids, doNotEmit = false) {
|
||||
let id;
|
||||
let newNodes = [];
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
id = ids[i];
|
||||
let properties = this.body.data.nodes.get(id);
|
||||
let node = this.create(properties);
|
||||
newNodes.push(node);
|
||||
this.body.nodes[id] = node; // note: this may replace an existing node
|
||||
}
|
||||
|
||||
this.layoutEngine.positionInitially(newNodes);
|
||||
|
||||
if (doNotEmit === false) {
|
||||
this.body.emitter.emit("_dataChanged");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing nodes, or create them when not yet existing
|
||||
* @param {number[] | string[]} ids id's of changed nodes
|
||||
* @param {Array} changedData array with changed data
|
||||
* @param {Array|undefined} oldData optional; array with previous data
|
||||
* @private
|
||||
*/
|
||||
update(ids, changedData, oldData) {
|
||||
let nodes = this.body.nodes;
|
||||
let dataChanged = false;
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
let id = ids[i];
|
||||
let node = nodes[id];
|
||||
let data = changedData[i];
|
||||
if (node !== undefined) {
|
||||
// update node
|
||||
if (node.setOptions(data)) {
|
||||
dataChanged = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
dataChanged = true;
|
||||
// create node
|
||||
node = this.create(data);
|
||||
nodes[id] = node;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dataChanged && oldData !== undefined) {
|
||||
// Check for any changes which should trigger a layout recalculation
|
||||
// For now, this is just 'level' for hierarchical layout
|
||||
// Assumption: old and new data arranged in same order; at time of writing, this holds.
|
||||
dataChanged = changedData.some(function(newValue, index) {
|
||||
let oldValue = oldData[index];
|
||||
return (oldValue && oldValue.level !== newValue.level);
|
||||
});
|
||||
}
|
||||
|
||||
if (dataChanged === true) {
|
||||
this.body.emitter.emit("_dataChanged");
|
||||
}
|
||||
else {
|
||||
this.body.emitter.emit("_dataUpdated");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove existing nodes. If nodes do not exist, the method will just ignore it.
|
||||
* @param {number[] | string[]} ids
|
||||
* @private
|
||||
*/
|
||||
remove(ids) {
|
||||
let nodes = this.body.nodes;
|
||||
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
let id = ids[i];
|
||||
delete nodes[id];
|
||||
}
|
||||
|
||||
this.body.emitter.emit("_dataChanged");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* create a node
|
||||
* @param {Object} properties
|
||||
* @param {class} [constructorClass=Node.default]
|
||||
* @returns {*}
|
||||
*/
|
||||
create(properties, constructorClass = Node) {
|
||||
return new constructorClass(properties, this.body, this.images, this.groups, this.options, this.defaultOptions)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {boolean} [clearPositions=false]
|
||||
*/
|
||||
refresh(clearPositions = false) {
|
||||
util.forEach(this.body.nodes, (node, nodeId) => {
|
||||
let data = this.body.data.nodes.get(nodeId);
|
||||
if (data !== undefined) {
|
||||
if (clearPositions === true) {
|
||||
node.setOptions({x:null, y:null});
|
||||
}
|
||||
node.setOptions({ fixed: false });
|
||||
node.setOptions(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the positions of the nodes.
|
||||
* @param {Array.<Node.id>|String} [ids] --> optional, can be array of nodeIds, can be string
|
||||
* @returns {{}}
|
||||
*/
|
||||
getPositions(ids) {
|
||||
let dataArray = {};
|
||||
if (ids !== undefined) {
|
||||
if (Array.isArray(ids) === true) {
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
if (this.body.nodes[ids[i]] !== undefined) {
|
||||
let node = this.body.nodes[ids[i]];
|
||||
dataArray[ids[i]] = { x: Math.round(node.x), y: Math.round(node.y) };
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.body.nodes[ids] !== undefined) {
|
||||
let node = this.body.nodes[ids];
|
||||
dataArray[ids] = { x: Math.round(node.x), y: Math.round(node.y) };
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (let i = 0; i < this.body.nodeIndices.length; i++) {
|
||||
let node = this.body.nodes[this.body.nodeIndices[i]];
|
||||
dataArray[this.body.nodeIndices[i]] = { x: Math.round(node.x), y: Math.round(node.y) };
|
||||
}
|
||||
}
|
||||
return dataArray;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load the XY positions of the nodes into the dataset.
|
||||
*/
|
||||
storePositions() {
|
||||
// todo: add support for clusters and hierarchical.
|
||||
let dataArray = [];
|
||||
var dataset = this.body.data.nodes.getDataSet();
|
||||
|
||||
for (let nodeId in dataset._data) {
|
||||
if (dataset._data.hasOwnProperty(nodeId)) {
|
||||
let node = this.body.nodes[nodeId];
|
||||
if (dataset._data[nodeId].x != Math.round(node.x) || dataset._data[nodeId].y != Math.round(node.y)) {
|
||||
dataArray.push({ id: node.id, x: Math.round(node.x), y: Math.round(node.y) });
|
||||
}
|
||||
}
|
||||
}
|
||||
dataset.update(dataArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the bounding box of a node.
|
||||
* @param {Node.id} nodeId
|
||||
* @returns {j|*}
|
||||
*/
|
||||
getBoundingBox(nodeId) {
|
||||
if (this.body.nodes[nodeId] !== undefined) {
|
||||
return this.body.nodes[nodeId].shape.boundingBox;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the Ids of nodes connected to this node.
|
||||
* @param {Node.id} nodeId
|
||||
* @param {'to'|'from'|undefined} direction values 'from' and 'to' select respectively parent and child nodes only.
|
||||
* Any other value returns both parent and child nodes.
|
||||
* @returns {Array}
|
||||
*/
|
||||
getConnectedNodes(nodeId, direction) {
|
||||
let nodeList = [];
|
||||
if (this.body.nodes[nodeId] !== undefined) {
|
||||
let node = this.body.nodes[nodeId];
|
||||
let nodeObj = {}; // used to quickly check if node already exists
|
||||
for (let i = 0; i < node.edges.length; i++) {
|
||||
let edge = node.edges[i];
|
||||
if (direction !== 'to' && edge.toId == node.id) { // these are double equals since ids can be numeric or string
|
||||
if (nodeObj[edge.fromId] === undefined) {
|
||||
nodeList.push(edge.fromId);
|
||||
nodeObj[edge.fromId] = true;
|
||||
}
|
||||
}
|
||||
else if (direction !== 'from' && edge.fromId == node.id) { // these are double equals since ids can be numeric or string
|
||||
if (nodeObj[edge.toId] === undefined) {
|
||||
nodeList.push(edge.toId);
|
||||
nodeObj[edge.toId] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ids of the edges connected to this node.
|
||||
* @param {Node.id} nodeId
|
||||
* @returns {*}
|
||||
*/
|
||||
getConnectedEdges(nodeId) {
|
||||
let edgeList = [];
|
||||
if (this.body.nodes[nodeId] !== undefined) {
|
||||
let node = this.body.nodes[nodeId];
|
||||
for (let i = 0; i < node.edges.length; i++) {
|
||||
edgeList.push(node.edges[i].id)
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("NodeId provided for getConnectedEdges does not exist. Provided: ", nodeId);
|
||||
}
|
||||
return edgeList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Move a node.
|
||||
*
|
||||
* @param {Node.id} nodeId
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
*/
|
||||
moveNode(nodeId, x, y) {
|
||||
if (this.body.nodes[nodeId] !== undefined) {
|
||||
this.body.nodes[nodeId].x = Number(x);
|
||||
this.body.nodes[nodeId].y = Number(y);
|
||||
setTimeout(() => {this.body.emitter.emit("startSimulation")},0);
|
||||
}
|
||||
else {
|
||||
console.log("Node id supplied to moveNode does not exist. Provided: ", nodeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default NodesHandler;
|
||||
Reference in New Issue
Block a user