|
|
- var Emitter = require('emitter-component');
- var Hammer = require('../module/hammer');
- var keycharm = require('keycharm');
- var util = require('../util');
- var hammerUtil = require('../hammerUtil');
- var DataSet = require('../DataSet');
- var DataView = require('../DataView');
- var dotparser = require('./dotparser');
- var gephiParser = require('./gephiParser');
- var Groups = require('./Groups');
- var Images = require('./Images');
- var Node = require('./Node');
- var Edge = require('./Edge');
- var Popup = require('./Popup');
- var MixinLoader = require('./mixins/MixinLoader');
- var Activator = require('../shared/Activator');
- var locales = require('./locales');
-
- // Load custom shapes into CanvasRenderingContext2D
- require('./shapes');
-
- 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"
-
- /**
- * @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');
- }
-
- this._initializeMixinLoaders();
-
-
- // render and calculation settings
- this.initializing = true;
-
- this.triggerFunctions = {add:null,edit:null,editEdge:null,connect:null,del:null};
-
- var customScalingFunction = function (min,max,total,value) {
- if (max == min) {
- return 0.5;
- }
- else {
- var scale = 1 / (max - min);
- return Math.max(0,(value - min)*scale);
- }
- };
-
- // set constant values
- this.defaultOptions = {
- nodes: {
- customScalingFunction: customScalingFunction,
- mass: 1,
- radiusMin: 10,
- radiusMax: 30,
- radius: 10,
- shape: 'ellipse',
- image: undefined,
- widthMin: 16, // px
- widthMax: 64, // px
- fontColor: 'black',
- fontSize: 14, // px
- fontFace: 'verdana',
- fontFill: undefined,
- fontStrokeWidth: 0, // px
- fontStrokeColor: '#ffffff',
- fontDrawThreshold: 3,
- scaleFontWithValue: false,
- fontSizeMin: 14,
- fontSizeMax: 30,
- fontSizeMaxVisible: 30,
- value: 1,
- level: -1,
- color: {
- border: '#2B7CE9',
- background: '#97C2FC',
- highlight: {
- border: '#2B7CE9',
- background: '#D2E5FF'
- },
- hover: {
- border: '#2B7CE9',
- background: '#D2E5FF'
- }
- },
- group: undefined,
- borderWidth: 1,
- borderWidthSelected: undefined
- },
- edges: {
- customScalingFunction: customScalingFunction,
- widthMin: 1, //
- widthMax: 15,//
- width: 1,
- widthSelectionMultiplier: 2,
- hoverWidth: 1.5,
- value:1,
- style: 'line',
- color: {
- color:'#848484',
- highlight:'#848484',
- hover: '#848484'
- },
- opacity:1.0,
- fontColor: '#343434',
- fontSize: 14, // px
- fontFace: 'arial',
- fontFill: 'white',
- fontStrokeWidth: 0, // px
- fontStrokeColor: 'white',
- labelAlignment:'horizontal',
- arrowScaleFactor: 1,
- dash: {
- length: 10,
- gap: 5,
- altLength: undefined
- },
- inheritColor: "from", // to, from, false, true (== from)
- useGradients: false // release in 4.0
- },
- navigation: {
- enabled: false
- },
- dataManipulation: {
- enabled: false,
- initiallyVisible: false
- },
- hierarchicalLayout: {
- enabled:false,
- levelSeparation: 150,
- nodeSpacing: 100,
- direction: "UD", // UD, DU, LR, RL
- layout: "hubsize" // hubsize, directed
- },
- interaction: {
- dragNodes:true,
- dragView: true,
- zoomView: true,
- hoverEnabled: false,
- tooltip: {
- delay: 300,
- fontColor: 'black',
- fontSize: 14, // px
- fontFace: 'verdana',
- color: {
- border: '#666',
- background: '#FFFFC6'
- }
- },
- keyboard: {
- enabled: false,
- speed: {x: 10, y: 10, zoom: 0.02},
- bindToWindow: true
- }
- },
- selection: {
- enabled: true,
- selectConnectedEdges: true
- },
- smoothCurves: {
- enabled: true,
- dynamic: true,
- type: "continuous",
- roundness: 0.5
- },
- locale: 'en',
- locales: locales,
- useDefaultGroups: true
- };
- this.constants = util.extend({}, this.defaultOptions);
-
- // containers for nodes and edges
- this.body = {
- nodes: {},
- nodeIndices: [],
- supportNodes: {},
- supportNodeIndices: [],
- edges: {},
- data: {
- nodes: null, // A DataSet or DataView
- edges: null // A DataSet or DataView
- },
- functions:{
- createNode: this._createNode.bind(this),
- createEdge: this._createEdge.bind(this)
- },
- 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() {}
- },
- container: container,
- view: {
- scale:1,
- translation:{x:0,y:0}
- }
- };
-
- // modules
- this.canvas = new Canvas(this.body);
- this.selectionHandler = new SelectionHandler(this.body, this.canvas);
- this.interactionHandler = new InteractionHandler(this.body, this.canvas, this.selectionHandler);
- this.view = new View(this.body, this.canvas);
- this.renderer = new CanvasRenderer(this.body, this.canvas);
- this.clustering = new ClusterEngine(this.body);
- this.physics = new PhysicsEngine(this.body);
-
- // create the DOM elements
- this.canvas.create();
-
- this.hoverObj = {nodes:{},edges:{}};
- this.controlNodesActive = false;
- this.navigationHammers = [];
- this.manipulationHammers = [];
-
- // Node variables
- var me = this;
- this.groups = new Groups(); // object with groups
- this.images = new Images(); // object with images
- this.images.setOnloadCallback(function (status) {
- me._requestRedraw();
- });
-
- // keyboard navigation variables
- this.xIncrement = 0;
- this.yIncrement = 0;
- this.zoomIncrement = 0;
-
- // loading all the mixins:
- // load the force calculation functions, grouped under the physics system.
- //this._loadPhysicsSystem();
- // create a frame and canvas
- // load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it)
- // load the selection system. (mandatory, required by Network)
- this._loadSelectionSystem();
- // load the selection system. (mandatory, required by Network)
- //this._loadHierarchySystem();
-
- // apply options
- this.setOptions(options);
-
- // position and scale variables and objects
-
- this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
- this.scale = 1; // defining the global scale variable in the constructor
-
- // create event listeners used to subscribe on the DataSets of the nodes and edges
- this.nodesListeners = {
- 'add': function (event, params) {
- me._addNodes(params.items);
- me.start();
- },
- 'update': function (event, params) {
- me._updateNodes(params.items, params.data);
- me.start();
- },
- 'remove': function (event, params) {
- me._removeNodes(params.items);
- me.start();
- }
- };
- this.edgesListeners = {
- 'add': function (event, params) {
- me._addEdges(params.items);
- me.start();
- },
- 'update': function (event, params) {
- me._updateEdges(params.items);
- me.start();
- },
- 'remove': function (event, params) {
- me._removeEdges(params.items);
- me.start();
- }
- };
-
-
- // properties for the animation
- this.moving = true;
- this.renderTimer = undefined; // Scheduling function. Is definded in this.start();
-
- // load data (the disable start variable will be the same as the enabled clustering)
- this.setData(data, this.constants.hierarchicalLayout.enabled);
-
- // hierarchical layout
- if (this.constants.hierarchicalLayout.enabled == true) {
- this._setupHierarchicalLayout();
- }
- else {
- // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here.
- if (this.constants.stabilize == false) {
- this.zoomExtent({duration:0}, true, this.constants.clustering.enabled);
- }
- }
-
- if (this.constants.stabilize == false) {
- this.initializing = false;
- }
-
- var me = this;
- // this event will trigger a rebuilding of the cache of colors, nodes etc.
- this.on("_dataChanged", function () {
- me._updateNodeIndexList();
- me.physics._updateCalculationNodes();
- me._markAllEdgesAsDirty();
- if (me.initializing !== true) {
- me.moving = true;
- me.start();
- }
- })
-
- this.on("_newEdgesCreated", this._createBezierNodes.bind(this));
- //this.on("stabilizationIterationsDone", function () {me.initializing = false; me.start();}.bind(this));
- }
-
- // Extend Network with an Emitter mixin
- Emitter(Network.prototype);
-
-
- Network.prototype._createNode = function(properties) {
- return new Node(properties, this.images, this.groups, this.constants)
- }
-
- Network.prototype._createEdge = function(properties) {
- return new Edge(properties, this.body, this.constants)
- }
-
-
-
-
- /**
- * Update the this.body.nodeIndices with the most recent node index list
- * @private
- */
- Network.prototype._updateNodeIndexList = function() {
- this.body.supportNodeIndices = Object.keys(this.body.supportNodes)
- this.body.nodeIndices = Object.keys(this.body.nodes);
- };
-
-
- /**
- * 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
- * @param {Boolean} [disableStart] | optional: disable the calling of the start function.
- */
- Network.prototype.setData = function(data, disableStart) {
- if (disableStart === undefined) {
- disableStart = false;
- }
-
- // unselect all to ensure no selections from old data are carried over.
- this.selectionHandler.unselectAll();
-
- // we set initializing to true to ensure that the hierarchical layout is not performed until both nodes and edges are added.
- this.initializing = true;
-
- 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.');
- }
-
- // clean up in case there is anyone in an active mode of the manipulation. This is the same option as bound to the escape button.
- if (this.constants.dataManipulation.enabled == true) {
- this._createManipulatorBar();
- }
-
- // 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._setNodes(data && data.nodes);
- this._setEdges(data && data.edges);
- }
-
- if (disableStart == false) {
- if (this.constants.hierarchicalLayout.enabled == true) {
- this._resetLevels();
- this._setupHierarchicalLayout();
- }
- else {
- // find a stable position or start animating to a stable position
- this.body.emitter.emit("stabilize");
- }
- }
- else {
- this.initializing = false;
- }
- };
-
- /**
- * Set options
- * @param {Object} options
- */
- Network.prototype.setOptions = function (options) {
- if (options) {
- var prop;
- var fields = ['nodes','edges','smoothCurves','hierarchicalLayout','navigation',
- 'keyboard','dataManipulation','onAdd','onEdit','onEditEdge','onConnect','onDelete','clickToUse'
- ];
- // extend all but the values in fields
- util.selectiveNotDeepExtend(fields,this.constants, options);
- util.selectiveNotDeepExtend(['color'],this.constants.nodes, options.nodes);
- util.selectiveNotDeepExtend(['color','length'],this.constants.edges, options.edges);
-
- this.groups.useDefaultGroups = this.constants.useDefaultGroups;
-
- this.physics.setOptions(options.physics);
- this.canvas.setOptions(options.canvas);
- this.renderer.setOptions(options.rendering);
- this.interactionHandler.setOptions(options.interaction);
- this.selectionHandler.setOptions(options.selection);
-
-
- if (options.onAdd) {this.triggerFunctions.add = options.onAdd;}
- if (options.onEdit) {this.triggerFunctions.edit = options.onEdit;}
- if (options.onEditEdge) {this.triggerFunctions.editEdge = options.onEditEdge;}
- if (options.onConnect) {this.triggerFunctions.connect = options.onConnect;}
- if (options.onDelete) {this.triggerFunctions.del = options.onDelete;}
-
- util.mergeOptions(this.constants, options,'smoothCurves');
- util.mergeOptions(this.constants, options,'hierarchicalLayout');
- util.mergeOptions(this.constants, options,'clustering');
- util.mergeOptions(this.constants, options,'navigation');
- util.mergeOptions(this.constants, options,'keyboard');
- util.mergeOptions(this.constants, options,'dataManipulation');
-
-
- if (options.dataManipulation) {
- this.editMode = this.constants.dataManipulation.initiallyVisible;
- }
-
-
- // TODO: work out these options and document them
- if (options.edges) {
- if (options.edges.color !== undefined) {
- if (util.isString(options.edges.color)) {
- this.constants.edges.color = {};
- this.constants.edges.color.color = options.edges.color;
- this.constants.edges.color.highlight = options.edges.color;
- this.constants.edges.color.hover = options.edges.color;
- }
- else {
- if (options.edges.color.color !== undefined) {this.constants.edges.color.color = options.edges.color.color;}
- if (options.edges.color.highlight !== undefined) {this.constants.edges.color.highlight = options.edges.color.highlight;}
- if (options.edges.color.hover !== undefined) {this.constants.edges.color.hover = options.edges.color.hover;}
- }
- this.constants.edges.inheritColor = false;
- }
-
- if (!options.edges.fontColor) {
- if (options.edges.color !== undefined) {
- if (util.isString(options.edges.color)) {this.constants.edges.fontColor = options.edges.color;}
- else if (options.edges.color.color !== undefined) {this.constants.edges.fontColor = options.edges.color.color;}
- }
- }
- }
-
- if (options.nodes) {
- if (options.nodes.color) {
- var newColorObj = util.parseColor(options.nodes.color);
- this.constants.nodes.color.background = newColorObj.background;
- this.constants.nodes.color.border = newColorObj.border;
- this.constants.nodes.color.highlight.background = newColorObj.highlight.background;
- this.constants.nodes.color.highlight.border = newColorObj.highlight.border;
- this.constants.nodes.color.hover.background = newColorObj.hover.background;
- this.constants.nodes.color.hover.border = newColorObj.hover.border;
- }
- }
- if (options.groups) {
- for (var groupname in options.groups) {
- if (options.groups.hasOwnProperty(groupname)) {
- var group = options.groups[groupname];
- this.groups.add(groupname, group);
- }
- }
- }
-
- if (options.tooltip) {
- for (prop in options.tooltip) {
- if (options.tooltip.hasOwnProperty(prop)) {
- this.constants.tooltip[prop] = options.tooltip[prop];
- }
- }
- if (options.tooltip.color) {
- this.constants.tooltip.color = util.parseColor(options.tooltip.color);
- }
- }
-
- if ('clickToUse' in options) {
- if (options.clickToUse) {
- if (!this.activator) {
- this.activator = new Activator(this.frame);
- this.activator.on('change', this._createKeyBinds.bind(this));
- }
- }
- else {
- if (this.activator) {
- this.activator.destroy();
- delete this.activator;
- }
- }
- }
-
- if (options.labels) {
- throw new Error('Option "labels" is deprecated. Use options "locale" and "locales" instead.');
- }
-
- // (Re)loading the mixins that can be enabled or disabled in the options.
- // load the force calculation functions, grouped under the physics system.
- // load the navigation system.
- //this._loadNavigationControls();
- //// load the data manipulation system
- //this._loadManipulationSystem();
- //// configure the smooth curves
- //this._configureSmoothCurves();
-
- // bind hammer
- //this.canvas._bindHammer();
-
- // bind keys. If disabled, this will not do anything;
- //this._createKeyBinds();
-
- this._markAllEdgesAsDirty();
- this.canvas.setSize();
- if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
- this._resetLevels();
- this._setupHierarchicalLayout();
- }
-
- if (this.initializing !== true) {
- this.moving = true;
- this.start();
- }
- }
- };
-
-
-
- /**
- * Binding the keys for keyboard navigation. These functions are defined in the NavigationMixin
- * @private
- */
- Network.prototype._createKeyBinds = function() {
- return;
-
- //var me = this;
- //if (this.keycharm !== undefined) {
- // this.keycharm.destroy();
- //}
- //
- //if (this.constants.keyboard.bindToWindow == true) {
- // this.keycharm = keycharm({container: window, preventDefault: false});
- //}
- //else {
- // this.keycharm = keycharm({container: this.frame, preventDefault: false});
- //}
- //
- //this.keycharm.reset();
- //
- //if (this.constants.keyboard.enabled && this.isActive()) {
- // this.keycharm.bind("up", this._moveUp.bind(me) , "keydown");
- // this.keycharm.bind("up", this._yStopMoving.bind(me), "keyup");
- // this.keycharm.bind("down", this._moveDown.bind(me) , "keydown");
- // this.keycharm.bind("down", this._yStopMoving.bind(me), "keyup");
- // this.keycharm.bind("left", this._moveLeft.bind(me) , "keydown");
- // this.keycharm.bind("left", this._xStopMoving.bind(me), "keyup");
- // this.keycharm.bind("right",this._moveRight.bind(me), "keydown");
- // this.keycharm.bind("right",this._xStopMoving.bind(me), "keyup");
- // this.keycharm.bind("=", this._zoomIn.bind(me), "keydown");
- // this.keycharm.bind("=", this._stopZoom.bind(me), "keyup");
- // this.keycharm.bind("num+", this._zoomIn.bind(me), "keydown");
- // this.keycharm.bind("num+", this._stopZoom.bind(me), "keyup");
- // this.keycharm.bind("num-", this._zoomOut.bind(me), "keydown");
- // this.keycharm.bind("num-", this._stopZoom.bind(me), "keyup");
- // this.keycharm.bind("-", this._zoomOut.bind(me), "keydown");
- // this.keycharm.bind("-", this._stopZoom.bind(me), "keyup");
- // this.keycharm.bind("[", this._zoomIn.bind(me), "keydown");
- // this.keycharm.bind("[", this._stopZoom.bind(me), "keyup");
- // this.keycharm.bind("]", this._zoomOut.bind(me), "keydown");
- // this.keycharm.bind("]", this._stopZoom.bind(me), "keyup");
- // this.keycharm.bind("pageup",this._zoomIn.bind(me), "keydown");
- // this.keycharm.bind("pageup",this._stopZoom.bind(me), "keyup");
- // this.keycharm.bind("pagedown",this._zoomOut.bind(me),"keydown");
- // this.keycharm.bind("pagedown",this._stopZoom.bind(me), "keyup");
- //}
- //
- //if (this.constants.dataManipulation.enabled == true) {
- // this.keycharm.bind("esc",this._createManipulatorBar.bind(me));
- // this.keycharm.bind("delete",this._deleteSelected.bind(me));
- //}
- };
-
- /**
- * 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.start = function () {};
- this.redraw = function () {};
- this.renderTimer = false;
-
- // cleanup physicsConfiguration if it exists
- this._cleanupPhysicsConfiguration();
-
- // remove keybindings
- this.keycharm.reset();
-
- // clear hammer bindings
- this.hammer.destroy();
-
- // clear events
- this.off();
-
- this._recursiveDOMDelete(this.containerElement);
- };
-
- Network.prototype._recursiveDOMDelete = function(DOMobject) {
- while (DOMobject.hasChildNodes() == true) {
- this._recursiveDOMDelete(DOMobject.firstChild);
- DOMobject.removeChild(DOMobject.firstChild);
- }
- };
-
- /**
- * Check if there is an element on the given position in the network
- * (a node or edge). If so, and if this element has a title,
- * show a popup window with its title.
- *
- * @param {{x:Number, y:Number}} pointer
- * @private
- */
- Network.prototype._checkShowPopup = function (pointer) {
- var obj = {
- left: this._XconvertDOMtoCanvas(pointer.x),
- top: this._YconvertDOMtoCanvas(pointer.y),
- right: this._XconvertDOMtoCanvas(pointer.x),
- bottom: this._YconvertDOMtoCanvas(pointer.y)
- };
-
- var id;
- var previousPopupObjId = this.popupObj === undefined ? "" : this.popupObj.id;
- var nodeUnderCursor = false;
- var popupType = "node";
-
- if (this.popupObj == undefined) {
- // search the nodes for overlap, select the top one in case of multiple nodes
- var nodes = this.body.nodes;
- var overlappingNodes = [];
- for (id in nodes) {
- if (nodes.hasOwnProperty(id)) {
- var node = nodes[id];
- if (node.isOverlappingWith(obj)) {
- if (node.getTitle() !== undefined) {
- overlappingNodes.push(id);
- }
- }
- }
- }
-
- if (overlappingNodes.length > 0) {
- // if there are overlapping nodes, select the last one, this is the
- // one which is drawn on top of the others
- this.popupObj = this.body.nodes[overlappingNodes[overlappingNodes.length - 1]];
- // if you hover over a node, the title of the edge is not supposed to be shown.
- nodeUnderCursor = true;
- }
- }
-
- if (this.popupObj === undefined && nodeUnderCursor == false) {
- // search the edges for overlap
- var edges = this.body.edges;
- var overlappingEdges = [];
- for (id in edges) {
- if (edges.hasOwnProperty(id)) {
- var edge = edges[id];
- if (edge.connected === true && (edge.getTitle() !== undefined) &&
- edge.isOverlappingWith(obj)) {
- overlappingEdges.push(id);
- }
- }
- }
-
- if (overlappingEdges.length > 0) {
- this.popupObj = this.body.edges[overlappingEdges[overlappingEdges.length - 1]];
- popupType = "edge";
- }
- }
-
- if (this.popupObj) {
- // show popup message window
- if (this.popupObj.id != previousPopupObjId) {
- if (this.popup === undefined) {
- this.popup = new Popup(this.frame, this.constants.tooltip);
- }
-
- this.popup.popupTargetType = popupType;
- this.popup.popupTargetId = this.popupObj.id;
-
- // adjust a small offset such that the mouse cursor is located in the
- // bottom left location of the popup, and you can easily move over the
- // popup area
- this.popup.setPosition(pointer.x + 3, pointer.y - 5);
- this.popup.setText(this.popupObj.getTitle());
- this.popup.show();
- }
- }
- else {
- if (this.popup) {
- this.popup.hide();
- }
- }
- };
-
-
- /**
- * Check if the popup must be hidden, which is the case when the mouse is no
- * longer hovering on the object
- * @param {{x:Number, y:Number}} pointer
- * @private
- */
- Network.prototype._checkHidePopup = function (pointer) {
- var pointerObj = {
- left: this._XconvertDOMtoCanvas(pointer.x),
- top: this._YconvertDOMtoCanvas(pointer.y),
- right: this._XconvertDOMtoCanvas(pointer.x),
- bottom: this._YconvertDOMtoCanvas(pointer.y)
- };
-
- var stillOnObj = false;
- if (this.popup.popupTargetType == 'node') {
- stillOnObj = this.body.nodes[this.popup.popupTargetId].isOverlappingWith(pointerObj);
- if (stillOnObj === true) {
- var overNode = this.getNodeAt(pointer);
- stillOnObj = overNode.id == this.popup.popupTargetId;
- }
- }
- else {
- if (this.getNodeAt(pointer) === null) {
- stillOnObj = this.body.edges[this.popup.popupTargetId].isOverlappingWith(pointerObj);
- }
- }
-
-
- if (stillOnObj === false) {
- this.popupObj = undefined;
- this.popup.hide();
- }
- };
-
-
- /**
- * Set a data set with nodes for the network
- * @param {Array | DataSet | DataView} nodes The data containing the nodes.
- * @private
- */
- Network.prototype._setNodes = function(nodes) {
- var 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
- var me = this;
- util.forEach(this.nodesListeners, function (callback, event) {
- me.body.data.nodes.on(event, callback);
- });
-
- // draw all new nodes
- var ids = this.body.data.nodes.getIds();
- this._addNodes(ids);
- }
- this._updateSelection();
- };
-
- /**
- * Add nodes
- * @param {Number[] | String[]} ids
- * @private
- */
- Network.prototype._addNodes = function(ids) {
- var id;
- for (var i = 0, len = ids.length; i < len; i++) {
- id = ids[i];
- var data = this.body.data.nodes.get(id);
- var node = new Node(data, this.images, this.groups, this.constants);
- this.body.nodes[id] = node; // note: this may replace an existing node
- if ((node.xFixed == false || node.yFixed == false) && (node.x === null || node.y === null)) {
- var radius = 10 * 0.1*ids.length + 10;
- var angle = 2 * Math.PI * Math.random();
- if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
- if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
- }
- this.moving = true;
- }
-
- this._updateNodeIndexList();
- if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
- this._resetLevels();
- this._setupHierarchicalLayout();
- }
- this.physics._updateCalculationNodes();
- this._reconnectEdges();
- this._updateValueRange(this.body.nodes);
- };
-
- /**
- * Update existing nodes, or create them when not yet existing
- * @param {Number[] | String[]} ids
- * @private
- */
- Network.prototype._updateNodes = function(ids,changedData) {
- var nodes = this.body.nodes;
- for (var i = 0, len = ids.length; i < len; i++) {
- var id = ids[i];
- var node = nodes[id];
- var data = changedData[i];
- if (node) {
- // update node
- node.setProperties(data, this.constants);
- }
- else {
- // create node
- node = new Node(properties, this.images, this.groups, this.constants);
- nodes[id] = node;
- }
- }
- this.moving = true;
- if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
- this._resetLevels();
- this._setupHierarchicalLayout();
- }
- this._updateNodeIndexList();
- this._updateValueRange(nodes);
- this._markAllEdgesAsDirty();
- };
-
-
- Network.prototype._markAllEdgesAsDirty = function() {
- for (var edgeId in this.body.edges) {
- this.body.edges[edgeId].colorDirty = true;
- }
- }
-
- /**
- * Remove existing nodes. If nodes do not exist, the method will just ignore it.
- * @param {Number[] | String[]} ids
- * @private
- */
- Network.prototype._removeNodes = function(ids) {
- var nodes = this.body.nodes;
-
- // remove from selection
- for (var i = 0, len = ids.length; i < len; i++) {
- if (this.selectionObj.nodes[ids[i]] !== undefined) {
- this.body.nodes[ids[i]].unselect();
- this._removeFromSelection(this.body.nodes[ids[i]]);
- }
- }
-
- for (var i = 0, len = ids.length; i < len; i++) {
- var id = ids[i];
- delete nodes[id];
- }
-
-
-
- this._updateNodeIndexList();
- if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
- this._resetLevels();
- this._setupHierarchicalLayout();
- }
- this.physics._updateCalculationNodes();
- this._reconnectEdges();
- this._updateSelection();
- this._updateValueRange(nodes);
- };
-
- /**
- * Load edges by reading the data table
- * @param {Array | DataSet | DataView} edges The data containing the edges.
- * @private
- * @private
- */
- Network.prototype._setEdges = function(edges) {
- var oldEdgesData = this.body.data.edges;
-
- if (edges instanceof DataSet || edges instanceof DataView) {
- this.body.data.edges = edges;
- }
- else if (Array.isArray(edges)) {
- this.body.data.edges = new DataSet();
- this.body.data.edges.add(edges);
- }
- else if (!edges) {
- this.body.data.edges = new DataSet();
- }
- else {
- throw new TypeError('Array or DataSet expected');
- }
-
- if (oldEdgesData) {
- // unsubscribe from old dataset
- util.forEach(this.edgesListeners, function (callback, event) {
- oldEdgesData.off(event, callback);
- });
- }
-
- // remove drawn edges
- this.body.edges = {};
-
- if (this.body.data.edges) {
- // subscribe to new dataset
- var me = this;
- util.forEach(this.edgesListeners, function (callback, event) {
- me.body.data.edges.on(event, callback);
- });
-
- // draw all new nodes
- var ids = this.body.data.edges.getIds();
- this._addEdges(ids);
- }
-
- this._reconnectEdges();
- };
-
- /**
- * Add edges
- * @param {Number[] | String[]} ids
- * @private
- */
- Network.prototype._addEdges = function (ids) {
- var edges = this.body.edges,
- edgesData = this.body.data.edges;
-
- for (var i = 0, len = ids.length; i < len; i++) {
- var id = ids[i];
-
- var oldEdge = edges[id];
- if (oldEdge) {
- oldEdge.disconnect();
- }
-
- var data = edgesData.get(id, {"showInternalIds" : true});
- edges[id] = new Edge(data, this.body, this.constants);
- }
- this.moving = true;
- this._updateValueRange(edges);
- this._createBezierNodes();
- this.physics._updateCalculationNodes();
- if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
- this._resetLevels();
- this._setupHierarchicalLayout();
- }
- };
-
- /**
- * Update existing edges, or create them when not yet existing
- * @param {Number[] | String[]} ids
- * @private
- */
- Network.prototype._updateEdges = function (ids) {
- var edges = this.body.edges;
- var edgesData = this.body.data.edges;
- for (var i = 0, len = ids.length; i < len; i++) {
- var id = ids[i];
-
- var data = edgesData.get(id);
- var edge = edges[id];
- if (edge) {
- // update edge
- edge.disconnect();
- edge.setProperties(data);
- edge.connect();
- }
- else {
- // create edge
- edge = new Edge(data, this.body, this.constants);
- this.body.edges[id] = edge;
- }
- }
-
- this._createBezierNodes();
- if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
- this._resetLevels();
- this._setupHierarchicalLayout();
- }
- this.moving = true;
- this._updateValueRange(edges);
- };
-
- /**
- * Remove existing edges. Non existing ids will be ignored
- * @param {Number[] | String[]} ids
- * @private
- */
- Network.prototype._removeEdges = function (ids) {
- var edges = this.body.edges;
-
- // remove from selection
- for (var i = 0, len = ids.length; i < len; i++) {
- if (this.selectionObj.edges[ids[i]] !== undefined) {
- edges[ids[i]].unselect();
- this._removeFromSelection(edges[ids[i]]);
- }
- }
-
- for (var i = 0, len = ids.length; i < len; i++) {
- var id = ids[i];
- var edge = edges[id];
- if (edge) {
- if (edge.via != null) {
- delete this.body.supportNodes[edge.via.id];
- }
- edge.disconnect();
- delete edges[id];
- }
- }
-
- this.moving = true;
- this._updateValueRange(edges);
- if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
- this._resetLevels();
- this._setupHierarchicalLayout();
- }
- this.physics._updateCalculationNodes();
- };
-
- /**
- * Reconnect all edges
- * @private
- */
- Network.prototype._reconnectEdges = function() {
- var id,
- nodes = this.body.nodes,
- edges = this.body.edges;
- for (id in nodes) {
- if (nodes.hasOwnProperty(id)) {
- nodes[id].edges = [];
- }
- }
-
- for (id in edges) {
- if (edges.hasOwnProperty(id)) {
- var edge = edges[id];
- edge.from = null;
- edge.to = null;
- edge.connect();
- }
- }
- };
-
- /**
- * 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);
- }
- }
- }
- };
-
-
- /**
- * Set the translation of the network
- * @param {Number} offsetX Horizontal offset
- * @param {Number} offsetY Vertical offset
- * @private
- */
- Network.prototype._setTranslation = function(offsetX, offsetY) {
- if (this.translation === undefined) {
- this.translation = {
- x: 0,
- y: 0
- };
- }
-
- if (offsetX !== undefined) {
- this.translation.x = offsetX;
- }
- if (offsetY !== undefined) {
- this.translation.y = offsetY;
- }
-
- this.emit('viewChanged');
- };
-
- /**
- * Get the translation of the network
- * @return {Object} translation An object with parameters x and y, both a number
- * @private
- */
- Network.prototype._getTranslation = function() {
- return {
- x: this.translation.x,
- y: this.translation.y
- };
- };
-
- /**
- * Scale the network
- * @param {Number} scale Scaling factor 1.0 is unscaled
- * @private
- */
- Network.prototype._setScale = function(scale) {
- this.scale = scale;
- };
-
- /**
- * Get the current scale of the network
- * @return {Number} scale Scaling factor 1.0 is unscaled
- * @private
- */
- Network.prototype._getScale = function() {
- return this.scale;
- };
-
-
- /**
- * Move the network according to the keyboard presses.
- *
- * @private
- */
- Network.prototype._handleNavigation = function() {
- if (this.xIncrement != 0 || this.yIncrement != 0) {
- var translation = this._getTranslation();
- this._setTranslation(translation.x+this.xIncrement, translation.y+this.yIncrement);
- }
- if (this.zoomIncrement != 0) {
- var center = {
- x: this.frame.canvas.clientWidth / 2,
- y: this.frame.canvas.clientHeight / 2
- };
- this.zoom(this.scale*(1 + this.zoomIncrement), center);
- }
- };
-
-
- /**
- * Freeze the _animationStep
- */
- Network.prototype.freezeSimulation = function(freeze) {
- if (freeze == true) {
- this.freezeSimulationEnabled = true;
- this.moving = false;
- }
- else {
- this.freezeSimulationEnabled = false;
- this.moving = true;
- this.start();
- }
- };
-
-
- /**
- * This function cleans the support nodes if they are not needed and adds them when they are.
- *
- * @param {boolean} [disableStart]
- * @private
- */
- Network.prototype._configureSmoothCurves = function(disableStart = true) {
- if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
- this._createBezierNodes();
- // cleanup unused support nodes
- for (let i = 0; i < this.body.supportNodeIndices.length; i++) {
- let nodeId = this.body.supportNodeIndices[i];
- // delete support nodes for edges that have been deleted
- if (this.body.edges[this.body.supportNodes[nodeId].parentEdgeId] === undefined) {
- delete this.body.supportNodes[nodeId];
- }
- }
- }
- else {
- // delete the support nodes
- this.body.supportNodes = {};
- for (var edgeId in this.body.edges) {
- if (this.body.edges.hasOwnProperty(edgeId)) {
- this.body.edges[edgeId].via = null;
- }
- }
- }
-
-
- this._updateNodeIndexList();
- this.physics._updateCalculationNodes();
- if (!disableStart) {
- this.moving = true;
- this.start();
- }
- };
-
-
- /**
- * Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
- * are used for the force calculation.
- *
- * @private
- */
- Network.prototype._createBezierNodes = function(specificEdges = this.body.edges) {
- if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
- for (var edgeId in specificEdges) {
- if (specificEdges.hasOwnProperty(edgeId)) {
- var edge = specificEdges[edgeId];
- if (edge.via == null) {
- var nodeId = "edgeId:".concat(edge.id);
- var node = new Node(
- {id:nodeId,
- mass:1,
- shape:'circle',
- image:"",
- internalMultiplier:1
- },{},{},this.constants);
- this.body.supportNodes[nodeId] = node;
- edge.via = node;
- edge.via.parentEdgeId = edge.id;
- edge.positionBezierNode();
- }
- }
- }
- this._updateNodeIndexList();
- }
- };
-
- /**
- * load the functions that load the mixins into the prototype.
- *
- * @private
- */
- Network.prototype._initializeMixinLoaders = function () {
- for (var mixin in MixinLoader) {
- if (MixinLoader.hasOwnProperty(mixin)) {
- Network.prototype[mixin] = MixinLoader[mixin];
- }
- }
- };
-
- /**
- * Load the XY positions of the nodes into the dataset.
- */
- Network.prototype.storePosition = function() {
- console.log("storePosition is depricated: use .storePositions() from now on.")
- this.storePositions();
- };
-
- /**
- * Load the XY positions of the nodes into the dataset.
- */
- Network.prototype.storePositions = function() {
- var dataArray = [];
- for (var nodeId in this.body.nodes) {
- if (this.body.nodes.hasOwnProperty(nodeId)) {
- var node = this.body.nodes[nodeId];
- var allowedToMoveX = !this.body.nodes.xFixed;
- var allowedToMoveY = !this.body.nodes.yFixed;
- if (this.body.data.nodes._data[nodeId].x != Math.round(node.x) || this.body.data.nodes._data[nodeId].y != Math.round(node.y)) {
- dataArray.push({id:nodeId,x:Math.round(node.x),y:Math.round(node.y),allowedToMoveX:allowedToMoveX,allowedToMoveY:allowedToMoveY});
- }
- }
- }
- this.body.data.nodes.update(dataArray);
- };
-
- /**
- * Return the positions of the nodes.
- */
- Network.prototype.getPositions = function(ids) {
- var dataArray = {};
- if (ids !== undefined) {
- if (Array.isArray(ids) == true) {
- for (var i = 0; i < ids.length; i++) {
- if (this.body.nodes[ids[i]] !== undefined) {
- var 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) {
- var node = this.body.nodes[ids];
- dataArray[ids] = {x: Math.round(node.x), y: Math.round(node.y)};
- }
- }
- }
- else {
- for (var nodeId in this.body.nodes) {
- if (this.body.nodes.hasOwnProperty(nodeId)) {
- var node = this.body.nodes[nodeId];
- dataArray[nodeId] = {x: Math.round(node.x), y: Math.round(node.y)};
- }
- }
- }
- return dataArray;
- };
-
-
- /**
- * Returns true when the Network is active.
- * @returns {boolean}
- */
- Network.prototype.isActive = function () {
- return !this.activator || this.activator.active;
- };
-
-
- /**
- * Sets the scale
- * @returns {Number}
- */
- Network.prototype.setScale = function () {
- return this._setScale();
- };
-
-
- /**
- * Returns the scale
- * @returns {Number}
- */
- Network.prototype.getScale = function () {
- return this._getScale();
- };
-
-
- /**
- * Check if a node is a cluster.
- * @param nodeId
- * @returns {*}
- */
- Network.prototype.isCluster = function(nodeId) {
- if (this.body.nodes[nodeId] !== undefined) {
- return this.body.nodes[nodeId].isCluster;
- }
- else {
- console.log("Node does not exist.")
- return false;
- }
- };
-
- /**
- * Returns the scale
- * @returns {Number}
- */
- Network.prototype.getCenterCoordinates = function () {
- return this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
- };
-
-
- Network.prototype.getBoundingBox = function(nodeId) {
- if (this.body.nodes[nodeId] !== undefined) {
- return this.body.nodes[nodeId].boundingBox;
- }
- }
-
- Network.prototype.getConnectedNodes = function(nodeId) {
- var nodeList = [];
- if (this.body.nodes[nodeId] !== undefined) {
- var node = this.body.nodes[nodeId];
- var nodeObj = {nodeId : true}; // used to quickly check if node already exists
- for (var i = 0; i < node.edges.length; i++) {
- var edge = node.edges[i];
- if (edge.toId == nodeId) {
- if (nodeObj[edge.fromId] === undefined) {
- nodeList.push(edge.fromId);
- nodeObj[edge.fromId] = true;
- }
- }
- else if (edge.fromId == nodeId) {
- if (nodeObj[edge.toId] === undefined) {
- nodeList.push(edge.toId)
- nodeObj[edge.toId] = true;
- }
- }
- }
- }
- return nodeList;
- }
-
-
- Network.prototype.getEdgesFromNode = function(nodeId) {
- var edgesList = [];
- if (this.body.nodes[nodeId] !== undefined) {
- var node = this.body.nodes[nodeId];
- for (var i = 0; i < node.edges.length; i++) {
- edgesList.push(node.edges[i].id);
- }
- }
- return edgesList;
- }
-
- Network.prototype.generateColorObject = function(color) {
- return util.parseColor(color);
-
- }
-
- module.exports = Network;
|