vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

470 lines
19 KiB

// Load custom shapes into CanvasRenderingContext2D
require('./shapes');
var Emitter = require('emitter-component');
var Hammer = require('../module/hammer');
var util = require('../util');
var DataSet = require('../DataSet');
var DataView = require('../DataView');
var dotparser = require('./dotparser');
var gephiParser = require('./gephiParser');
var Images = require('./Images');
var Activator = require('../shared/Activator');
import Groups from './modules/Groups';
import NodesHandler from './modules/NodesHandler';
import EdgesHandler from './modules/EdgesHandler';
import PhysicsEngine from './modules/PhysicsEngine';
import ClusterEngine from './modules/Clustering';
import CanvasRenderer from './modules/CanvasRenderer';
import Canvas from './modules/Canvas';
import View from './modules/View';
import InteractionHandler from './modules/InteractionHandler';
import SelectionHandler from "./modules/SelectionHandler";
import LayoutEngine from "./modules/LayoutEngine";
import ManipulationSystem from "./modules/ManipulationSystem";
import ConfigurationSystem from "./modules/ConfigurationSystem";
import Validator from "./modules/Validator";
import {printStyle} from "./modules/Validator";
import {allOptions, configureOptions} from './modules/components/AllOptions.js';
/**
* @constructor Network
* Create a network visualization, displaying nodes and edges.
*
* @param {Element} container The DOM element in which the Network will
* be created. Normally a div element.
* @param {Object} data An object containing parameters
* {Array} nodes
* {Array} edges
* @param {Object} options Options
*/
function Network(container, data, options) {
if (!(this instanceof Network)) {
throw new SyntaxError('Constructor must be called with the new operator');
}
// set constant values
this.options = {};
this.defaultOptions = {
clickToUse: false
};
util.extend(this.options, this.defaultOptions);
// containers for nodes and edges
this.body = {
nodes: {},
nodeIndices: [],
edges: {},
edgeIndices: [],
data: {
nodes: null, // A DataSet or DataView
edges: null // A DataSet or DataView
},
functions: {
createNode: function() {},
createEdge: function() {},
getPointer: function() {}
},
emitter: {
on: this.on.bind(this),
off: this.off.bind(this),
emit: this.emit.bind(this),
once: this.once.bind(this)
},
eventListeners: {
onTap: function() {},
onTouch: function() {},
onDoubleTap: function() {},
onHold: function() {},
onDragStart: function() {},
onDrag: function() {},
onDragEnd: function() {},
onMouseWheel: function() {},
onPinch: function() {},
onMouseMove: function() {},
onRelease: function() {},
onContext: function() {}
},
container: container,
view: {
scale: 1,
translation: {x: 0, y: 0}
}
};
// bind the event listeners
this.bindEventListeners();
// setting up all modules
this.images = new Images(() => this.body.emitter.emit("_requestRedraw")); // object with images
this.groups = new Groups(); // object with groups
this.canvas = new Canvas(this.body); // DOM handler
this.selectionHandler = new SelectionHandler(this.body, this.canvas); // Selection handler
this.interactionHandler = new InteractionHandler(this.body, this.canvas, this.selectionHandler); // Interaction handler handles all the hammer bindings (that are bound by canvas), key
this.view = new View(this.body, this.canvas); // camera handler, does animations and zooms
this.renderer = new CanvasRenderer(this.body, this.canvas); // renderer, starts renderloop, has events that modules can hook into
this.physics = new PhysicsEngine(this.body); // physics engine, does all the simulations
this.layoutEngine = new LayoutEngine(this.body); // layout engine for inital layout and hierarchical layout
this.clustering = new ClusterEngine(this.body); // clustering api
this.manipulation = new ManipulationSystem(this.body, this.canvas, this.selectionHandler); // data manipulation system
this.nodesHandler = new NodesHandler(this.body, this.images, this.groups, this.layoutEngine); // Handle adding, deleting and updating of nodes as well as global options
this.edgesHandler = new EdgesHandler(this.body, this.images, this.groups); // Handle adding, deleting and updating of edges as well as global options
// create the DOM elements
this.canvas._create();
// setup configuration system
this.configurationSystem = new ConfigurationSystem(this, this.body.container, configureOptions, this.canvas.pixelRatio);
// apply options
this.setOptions(options);
// load data (the disable start variable will be the same as the enabled clustering)
this.setData(data);
}
// Extend Network with an Emitter mixin
Emitter(Network.prototype);
/**
* Set options
* @param {Object} options
*/
Network.prototype.setOptions = function (options) {
if (options !== undefined) {
let errorFound = Validator.validate(options, allOptions);
if (errorFound === true) {
options = {};
console.log('%cErrors have been found in the supplied options object. None of the options will be used.', printStyle);
}
//this.placeConvenienceOptions(options);
// the hierarchical system can adapt the edges and the physics to it's own options because not all combinations work with the hierarichical system.
options = this.layoutEngine.setOptions(options.layout, options);
// pass the options to the modules
this.groups.setOptions(options.groups);
this.nodesHandler.setOptions(options.nodes);
this.edgesHandler.setOptions(options.edges);
this.physics.setOptions(options.physics);
this.canvas.setOptions(options.canvas);
this.renderer.setOptions(options.rendering);
this.view.setOptions(options.view);
this.interactionHandler.setOptions(options.interaction);
this.selectionHandler.setOptions(options.selection);
this.clustering.setOptions(options.clustering);
this.manipulation.setOptions(options.manipulation);
this.configurationSystem.setOptions(options.configure);
// if the configuration system is enabled, copy all options and put them into the config system
if (this.configurationSystem.options.enabled === true) {
let networkOptions = {nodes:{},edges:{},layout:{},interaction:{},manipulation:{},physics:{},selection:{},rendering:{}};
util.deepExtend(networkOptions.nodes, this.nodesHandler.options);
util.deepExtend(networkOptions.edges, this.edgesHandler.options);
util.deepExtend(networkOptions.layout, this.layoutEngine.options);
util.deepExtend(networkOptions.interaction, this.interactionHandler.options);
util.deepExtend(networkOptions.manipulation, this.manipulation.options);
util.deepExtend(networkOptions.physics, this.physics.options);
util.deepExtend(networkOptions.selection, this.selectionHandler.options);
util.deepExtend(networkOptions.rendering, this.renderer.options);
this.configurationSystem.setModuleOptions(networkOptions);
}
// handle network global options
if (options.clickToUse !== undefined) {
if (options.clickToUse === true) {
if (this.activator === undefined) {
this.activator = new Activator(this.frame);
this.activator.on('change', this._createKeyBinds.bind(this));
}
}
else {
if (this.activator !== undefined) {
this.activator.destroy();
delete this.activator;
}
this.body.emitter.emit("activate");
}
}
else {
this.body.emitter.emit("activate");
}
this.canvas.setSize();
// start the physics simulation. Can be safely called multiple times.
this.body.emitter.emit("startSimulation");
}
};
//
///**
// *
// */
//Network.prototype.placeConvenienceOptions = function (options) {
// if (options.locale !== undefined) {
// if (options.manipulation === undefined) {
// options.manipulation = {enabled:false, locale:options.locale};
// }
// else if (typeof options.manipulation === 'boolean') {
// options.manipulation = {enabled: options.manipulation, locale:options.locale};
// }
// else {
// options.manipulation.locale = options.locale;
// }
// }
//}
/**
* Update the this.body.nodeIndices with the most recent node index list
* @private
*/
Network.prototype._updateVisibleIndices = function () {
let nodes = this.body.nodes;
let edges = this.body.edges;
this.body.nodeIndices = [];
this.body.edgeIndices = [];
for (let nodeId in nodes) {
if (nodes.hasOwnProperty(nodeId)) {
if (nodes[nodeId].options.hidden === false) {
this.body.nodeIndices.push(nodeId);
}
}
}
for (let edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) {
if (edges[edgeId].options.hidden === false) {
this.body.edgeIndices.push(edgeId);
}
}
}
};
/**
* Bind all events
*/
Network.prototype.bindEventListeners = function () {
// this event will trigger a rebuilding of the cache everything. Used when nodes or edges have been added or removed.
this.body.emitter.on("_dataChanged", () => {
// update shortcut lists
this._updateVisibleIndices();
this.physics.updatePhysicsIndices();
// call the dataUpdated event because the only difference between the two is the updating of the indices
this.body.emitter.emit("_dataUpdated");
});
// this is called when options of EXISTING nodes or edges have changed.
this.body.emitter.on("_dataUpdated", () => {
// update values
this._updateValueRange(this.body.nodes);
this._updateValueRange(this.body.edges);
// start simulation (can be called safely, even if already running)
this.body.emitter.emit("startSimulation");
});
};
/**
* Set nodes and edges, and optionally options as well.
*
* @param {Object} data Object containing parameters:
* {Array | DataSet | DataView} [nodes] Array with nodes
* {Array | DataSet | DataView} [edges] Array with edges
* {String} [dot] String containing data in DOT format
* {String} [gephi] String containing data in gephi JSON format
* {Options} [options] Object with options
*/
Network.prototype.setData = function (data) {
// reset the physics engine.
this.body.emitter.emit("resetPhysics");
this.body.emitter.emit("_resetData");
// unselect all to ensure no selections from old data are carried over.
this.selectionHandler.unselectAll();
if (data && data.dot && (data.nodes || data.edges)) {
throw new SyntaxError('Data must contain either parameter "dot" or ' +
' parameter pair "nodes" and "edges", but not both.');
}
// set options
this.setOptions(data && data.options);
// set all data
if (data && data.dot) {
// parse DOT file
if (data && data.dot) {
var dotData = dotparser.DOTToGraph(data.dot);
this.setData(dotData);
return;
}
}
else if (data && data.gephi) {
// parse DOT file
if (data && data.gephi) {
var gephiData = gephiParser.parseGephi(data.gephi);
this.setData(gephiData);
return;
}
}
else {
this.nodesHandler.setData(data && data.nodes, true);
this.edgesHandler.setData(data && data.edges, true);
}
// emit change in data
this.body.emitter.emit("_dataChanged");
// find a stable position or start animating to a stable position
this.body.emitter.emit("initPhysics");
};
/**
* Cleans up all bindings of the network, removing it fully from the memory IF the variable is set to null after calling this function.
* var network = new vis.Network(..);
* network.destroy();
* network = null;
*/
Network.prototype.destroy = function () {
this.body.emitter.emit("destroy");
// clear events
this.body.emitter.off();
this.off();
// delete modules
delete this.groups;
delete this.canvas;
delete this.selectionHandler;
delete this.interactionHandler;
delete this.view;
delete this.renderer;
delete this.physics;
delete this.layoutEngine;
delete this.clustering;
delete this.manipulation;
delete this.nodesHandler;
delete this.edgesHandler;
delete this.configurationSystem;
delete this.images;
// delete emitter bindings
delete this.body.emitter.emit;
delete this.body.emitter.on;
delete this.body.emitter.off;
delete this.body.emitter.once;
delete this.body.emitter;
for (var nodeId in this.body.nodes) {
delete this.body.nodes[nodeId];
}
for (var edgeId in this.body.edges) {
delete this.body.edges[edgeId];
}
// remove the container and everything inside it recursively
util.recursiveDOMDelete(this.body.container);
};
/**
* Update the values of all object in the given array according to the current
* value range of the objects in the array.
* @param {Object} obj An object containing a set of Edges or Nodes
* The objects must have a method getValue() and
* setValueRange(min, max).
* @private
*/
Network.prototype._updateValueRange = function (obj) {
var id;
// determine the range of the objects
var valueMin = undefined;
var valueMax = undefined;
var valueTotal = 0;
for (id in obj) {
if (obj.hasOwnProperty(id)) {
var value = obj[id].getValue();
if (value !== undefined) {
valueMin = (valueMin === undefined) ? value : Math.min(value, valueMin);
valueMax = (valueMax === undefined) ? value : Math.max(value, valueMax);
valueTotal += value;
}
}
}
// adjust the range of all objects
if (valueMin !== undefined && valueMax !== undefined) {
for (id in obj) {
if (obj.hasOwnProperty(id)) {
obj[id].setValueRange(valueMin, valueMax, valueTotal);
}
}
}
};
/**
* Returns true when the Network is active.
* @returns {boolean}
*/
Network.prototype.isActive = function () {
return !this.activator || this.activator.active;
};
Network.prototype.setSize = function() {this.canvas.setSize.apply(this.canvas,arguments);};
Network.prototype.canvasToDOM = function() {this.canvas.canvasToDOM.apply(this.canvas,arguments);};
Network.prototype.DOMtoCanvas = function() {this.canvas.setSize.DOMtoCanvas(this.canvas,arguments);};
Network.prototype.findNode = function() {this.clustering.findNode.apply(this.clustering,arguments);};
Network.prototype.isCluster = function() {this.clustering.isCluster.apply(this.clustering,arguments);};
Network.prototype.openCluster = function() {this.clustering.openCluster.apply(this.clustering,arguments);};
Network.prototype.cluster = function() {this.clustering.cluster.apply(this.clustering,arguments);};
Network.prototype.clusterByConnection = function() {this.clustering.clusterByConnection.apply(this.clustering,arguments);};
Network.prototype.clusterByHubsize = function() {this.clustering.clusterByHubsize.apply(this.clustering,arguments);};
Network.prototype.clusterOutliers = function() {this.clustering.clusterOutliers.apply(this.clustering,arguments);};
Network.prototype.getSeed = function() {this.layoutEngine.getSeed.apply(this.layoutEngine,arguments);};
Network.prototype.enableEditMode = function() {this.manipulation.enableEditMode.apply(this.manipulation,arguments);};
Network.prototype.disableEditMode = function() {this.manipulation.disableEditMode.apply(this.manipulation,arguments);};
Network.prototype.addNodeMode = function() {this.manipulation.addNodeMode.apply(this.manipulation,arguments);};
Network.prototype.editNodeMode = function() {this.manipulation.editNodeMode.apply(this.manipulation,arguments);};
Network.prototype.addEdgeMode = function() {this.manipulation.addEdgeMode.apply(this.manipulation,arguments);};
Network.prototype.editEdgeMode = function() {this.manipulation.editEdgeMode.apply(this.manipulation,arguments);};
Network.prototype.deleteSelected = function() {this.manipulation.deleteSelected.apply(this.manipulation,arguments);};
Network.prototype.getPositions = function() {this.nodesHandler.getPositions.apply(this.nodesHandler,arguments);};
Network.prototype.storePositions = function() {this.nodesHandler.storePositions.apply(this.nodesHandler,arguments);};
Network.prototype.getBoundingBox = function() {this.nodesHandler.getBoundingBox.apply(this.nodesHandler,arguments);};
Network.prototype.getConnectedNodes = function() {this.nodesHandler.getConnectedNodes.apply(this.nodesHandler,arguments);};
Network.prototype.getEdges = function() {this.nodesHandler.getEdges.apply(this.nodesHandler,arguments);};
Network.prototype.startSimulation = function() {this.physics.startSimulation.apply(this.physics,arguments);};
Network.prototype.stopSimulation = function() {this.physics.stopSimulation.apply(this.physics,arguments);};
Network.prototype.stabilize = function() {this.physics.stabilize.apply(this.physics,arguments);};
Network.prototype.getSelection = function() {this.selectionHandler.getSelection.apply(this.selectionHandler,arguments);};
Network.prototype.getSelectedNodes = function() {this.selectionHandler.getSelectedNodes.apply(this.selectionHandler,arguments);};
Network.prototype.getSelectedEdges = function() {this.selectionHandler.getSelectedEdges.apply(this.selectionHandler,arguments);};
Network.prototype.getNodeAt = function() {this.selectionHandler.getNodeAt.apply(this.selectionHandler,arguments);};
Network.prototype.getEdgeAt = function() {this.selectionHandler.getEdgeAt.apply(this.selectionHandler,arguments);};
Network.prototype.selectNodes = function() {this.selectionHandler.selectNodes.apply(this.selectionHandler,arguments);};
Network.prototype.selectEdges = function() {this.selectionHandler.selectEdges.apply(this.selectionHandler,arguments);};
Network.prototype.unselectAll = function() {this.selectionHandler.unselectAll.apply(this.selectionHandler,arguments);};
Network.prototype.getScale = function() {this.view.getScale.apply(this.view,arguments);};
Network.prototype.getPosition = function() {this.view.getPosition.apply(this.view,arguments);};
Network.prototype.fit = function() {this.view.fit.apply(this.view,arguments);};
Network.prototype.moveTo = function() {this.view.moveTo.apply(this.view,arguments);};
Network.prototype.focus = function() {this.view.focus.apply(this.view,arguments);};
Network.prototype.releaseNode = function() {this.view.releaseNode.apply(this.view,arguments);};
module.exports = Network;