Browse Source

moved the popup system to 4.0. Fixed #731.

flowchartTest
Alex de Mulder 9 years ago
parent
commit
dc5564a21e
14 changed files with 1434 additions and 1365 deletions
  1. +915
    -855
      dist/vis.js
  2. +1
    -1
      examples/network/01_basic_usage.html
  3. +0
    -3
      index.js
  4. +33
    -194
      lib/network/Network.js
  5. +0
    -135
      lib/network/Popup.js
  6. +64
    -10
      lib/network/modules/EdgesHandler.js
  7. +247
    -104
      lib/network/modules/InteractionHandler.js
  8. +1
    -0
      lib/network/modules/PhysicsEngine.js
  9. +2
    -2
      lib/network/modules/SelectionHandler.js
  10. +25
    -40
      lib/network/modules/components/Edge.js
  11. +1
    -1
      lib/network/modules/components/NavigationHandler.js
  12. +8
    -19
      lib/network/modules/components/Node.js
  13. +137
    -0
      lib/network/modules/components/Popup.js
  14. +0
    -1
      lib/network/modules/components/edges/straightEdge.js

+ 915
- 855
dist/vis.js
File diff suppressed because it is too large
View File


+ 1
- 1
examples/network/01_basic_usage.html View File

@ -22,7 +22,7 @@
<script type="text/javascript">
// create an array with nodes
var nodes = [
{id: 1, label: 'Node 1'},
{id: 1, label: 'Node 1',title:'blaat'},
{id: 2, label: 'Node 2'},
{id: 3, label: 'Node 3'},
{id: 4, label: 'Node 4'},

+ 0
- 3
index.js View File

@ -54,11 +54,8 @@ exports.timeline = {
// Network
exports.Network = require('./lib/network/Network');
exports.network = {
//Edge: require('./lib/network/Edge'),
Groups: require('./lib/network/Groups'),
Images: require('./lib/network/Images'),
//Node: require('./lib/network/Node'),
Popup: require('./lib/network/Popup'),
dotparser: require('./lib/network/dotparser'),
gephiParser: require('./lib/network/gephiParser')
};

+ 33
- 194
lib/network/Network.js View File

@ -10,7 +10,6 @@ var dotparser = require('./dotparser');
var gephiParser = require('./gephiParser');
var Groups = require('./Groups');
var Images = require('./Images');
var Popup = require('./Popup');
var Activator = require('../shared/Activator');
var locales = require('./locales');
@ -94,42 +93,13 @@ function Network (container, data, options) {
}
};
// 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", (params) => {
var t0 = new Date().valueOf();
// update shortcut lists
this._updateVisibleIndices();
this.physics.updatePhysicsIndices();
// update values
this._updateValueRange(this.body.nodes);
this._updateValueRange(this.body.edges);
// update edges
this._reconnectEdges();
this._markAllEdgesAsDirty();
// start simulation (can be called safely, even if already running)
this.body.emitter.emit("startSimulation");
console.log("_dataChanged took:", new Date().valueOf() - t0);
})
// bind the event listeners
this.bindEventListeners();
// this is called when options of EXISTING nodes or edges have changed.
this.body.emitter.on("_dataUpdated", () => {
var t0 = new Date().valueOf();
// update values
this._updateValueRange(this.body.nodes);
this._updateValueRange(this.body.edges);
// update edges
this._reconnectEdges();
this._markAllEdgesAsDirty();
// start simulation (can be called safely, even if already running)
this.body.emitter.emit("startSimulation");
console.log("_dataUpdated took:", new Date().valueOf() - t0);
});
// todo think of good comment for this set
// setting up all modules
var groups = new Groups(); // object with groups
var images = new Images(() => this.body.emitter.emit("_requestRedraw")); // object with images
// data handling modules
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
@ -184,6 +154,36 @@ Network.prototype._updateVisibleIndices = function() {
}
};
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", (params) => {
var t0 = new Date().valueOf();
// 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");
// start simulation (can be called safely, even if already running)
this.body.emitter.emit("startSimulation");
console.log("_dataChanged took:", new Date().valueOf() - t0);
});
// this is called when options of EXISTING nodes or edges have changed.
this.body.emitter.on("_dataUpdated", () => {
var t0 = new Date().valueOf();
// update values
this._updateValueRange(this.body.nodes);
this._updateValueRange(this.body.edges);
// update edges
this._reconnectEdges();
this._markAllEdgesAsDirty();
// start simulation (can be called safely, even if already running)
this.body.emitter.emit("startSimulation");
console.log("_dataUpdated took:", new Date().valueOf() - t0);
});
}
/**
* Set nodes and edges, and optionally options as well.
@ -347,167 +347,6 @@ Network.prototype.destroy = function() {
};
/**
* 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();
}
};
Network.prototype._markAllEdgesAsDirty = function() {
for (var edgeId in this.body.edges) {
this.body.edges[edgeId].colorDirty = true;
}
}
/**
* Reconnect all edges
* @private
*/
Network.prototype._reconnectEdges = function() {
var id;
var nodes = this.body.nodes;
var 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

+ 0
- 135
lib/network/Popup.js View File

@ -1,135 +0,0 @@
/**
* Popup is a class to create a popup window with some text
* @param {Element} container The container object.
* @param {Number} [x]
* @param {Number} [y]
* @param {String} [text]
* @param {Object} [style] An object containing borderColor,
* backgroundColor, etc.
*/
function Popup(container, x, y, text, style) {
if (container) {
this.container = container;
}
else {
this.container = document.body;
}
// x, y and text are optional, see if a style object was passed in their place
if (style === undefined) {
if (typeof x === "object") {
style = x;
x = undefined;
} else if (typeof text === "object") {
style = text;
text = undefined;
} else {
// for backwards compatibility, in case clients other than Network are creating Popup directly
style = {
fontColor: 'black',
fontSize: 14, // px
fontFace: 'verdana',
color: {
border: '#666',
background: '#FFFFC6'
}
}
}
}
this.x = 0;
this.y = 0;
this.padding = 5;
this.hidden = false;
if (x !== undefined && y !== undefined) {
this.setPosition(x, y);
}
if (text !== undefined) {
this.setText(text);
}
// create the frame
this.frame = document.createElement('div');
this.frame.className = 'network-tooltip';
this.frame.style.color = style.fontColor;
this.frame.style.backgroundColor = style.color.background;
this.frame.style.borderColor = style.color.border;
this.frame.style.fontSize = style.fontSize + 'px';
this.frame.style.fontFamily = style.fontFace;
this.container.appendChild(this.frame);
}
/**
* @param {number} x Horizontal position of the popup window
* @param {number} y Vertical position of the popup window
*/
Popup.prototype.setPosition = function(x, y) {
this.x = parseInt(x);
this.y = parseInt(y);
};
/**
* Set the content for the popup window. This can be HTML code or text.
* @param {string | Element} content
*/
Popup.prototype.setText = function(content) {
if (content instanceof Element) {
this.frame.innerHTML = '';
this.frame.appendChild(content);
}
else {
this.frame.innerHTML = content; // string containing text or HTML
}
};
/**
* Show the popup window
* @param {boolean} show Optional. Show or hide the window
*/
Popup.prototype.show = function (show) {
if (show === undefined) {
show = true;
}
if (show) {
var height = this.frame.clientHeight;
var width = this.frame.clientWidth;
var maxHeight = this.frame.parentNode.clientHeight;
var maxWidth = this.frame.parentNode.clientWidth;
var top = (this.y - height);
if (top + height + this.padding > maxHeight) {
top = maxHeight - height - this.padding;
}
if (top < this.padding) {
top = this.padding;
}
var left = this.x;
if (left + width + this.padding > maxWidth) {
left = maxWidth - width - this.padding;
}
if (left < this.padding) {
left = this.padding;
}
this.frame.style.left = left + "px";
this.frame.style.top = top + "px";
this.frame.style.visibility = "visible";
this.hidden = false;
}
else {
this.hide();
}
};
/**
* Hide the popup window
*/
Popup.prototype.hide = function () {
this.hidden = true;
this.frame.style.visibility = "hidden";
};
module.exports = Popup;

+ 64
- 10
lib/network/modules/EdgesHandler.js View File

@ -119,24 +119,26 @@ class EdgesHandler {
if (emitChange === true) {
this.body.emitter.emit("_dataChanged");
}
})
});
// this is called when options of EXISTING nodes or edges have changed.
this.body.emitter.on("_dataUpdated", () => {
var t0 = new Date().valueOf();
// update values
this.reconnectEdges();
this.markAllEdgesAsDirty();
// start simulation (can be called safely, even if already running)
console.log("_dataUpdated took:", new Date().valueOf() - t0);
});
}
setOptions(options) {
if (options !== undefined) {
if (options.color !== undefined) {
if (util.isString(options.color)) {
util.assignAllKeys(this.options.color, options.color);
}
else {
util.extend(this.options.color, options.color);
}
this.options.color.inherit.enabled = false;
}
util.mergeOptions(this.options, options, 'smooth');
util.mergeOptions(this.options, options, 'dashes');
// hanlde multiple input cases for arrows
if (options.arrows !== undefined) {
if (typeof options.arrows === 'string') {
let arrows = options.arrows.toLowerCase();
@ -153,6 +155,23 @@ class EdgesHandler {
throw new Error("The arrow options can only be an object or a string. Refer to the documentation. You used:" + JSON.stringify(options.arrows));
}
}
// hanlde multiple input cases for color
if (options.color !== undefined) {
if (util.isString(options.color)) {
util.assignAllKeys(this.options.color, options.color);
this.options.color.inherit.enabled = false;
}
else {
util.extend(this.options.color, options.color);
if (options.color.inherit === undefined) {
this.options.color.inherit.enabled = false;
}
}
util.mergeOptions(this.options.color, options.color, 'inherit');
}
// font cases are handled by the Label class
}
}
@ -295,6 +314,41 @@ class EdgesHandler {
return new Edge(properties, this.body, this.options)
}
markAllEdgesAsDirty() {
for (var edgeId in this.body.edges) {
this.body.edges[edgeId].colorDirty = true;
}
}
/**
* Reconnect all edges
* @private
*/
reconnectEdges() {
var id;
var nodes = this.body.nodes;
var 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();
}
}
}
}
export default EdgesHandler;

+ 247
- 104
lib/network/modules/InteractionHandler.js View File

@ -3,10 +3,10 @@
*
*/
let util = require('../../util');
var util = require('../../util');
import { NavigationHandler } from "./components/NavigationHandler"
import NavigationHandler from "./components/NavigationHandler"
import Popup from "./components/Popup"
class InteractionHandler {
constructor(body, canvas, selectionHandler) {
@ -33,6 +33,9 @@ class InteractionHandler {
this.pinch = {};
this.pointerPosition = {x:0,y:0};
this.hoverObj = {nodes:{},edges:{}};
this.popup = undefined;
this.popupObj = undefined;
this.popupTimer = undefined;
this.options = {};
@ -59,12 +62,16 @@ class InteractionHandler {
}
}
util.extend(this.options,this.defaultOptions);
this.body.emitter.on("_dataChanged", () => {
})
}
setOptions(options) {
if (options !== undefined) {
// extend all but the values in fields
var fields = ['keyboard'];
let fields = ['keyboard'];
util.selectiveNotDeepExtend(fields,this.options, options);
// merge the keyboard options in.
@ -110,10 +117,10 @@ class InteractionHandler {
* @private
*/
onTap(event) {
var pointer = this.getPointer(event.center);
let pointer = this.getPointer(event.center);
var previouslySelected = this.selectionHandler._getSelectedObjectCount() > 0;
var selected = this.selectionHandler.selectOnPoint(pointer);
let previouslySelected = this.selectionHandler._getSelectedObjectCount() > 0;
let selected = this.selectionHandler.selectOnPoint(pointer);
if (selected === true || (previouslySelected == true && selected === false)) { // select or unselect
this.body.emitter.emit('select', this.selectionHandler.getSelection());
@ -128,7 +135,7 @@ class InteractionHandler {
* @private
*/
onDoubleTap(event) {
var pointer = this.getPointer(event.center);
let pointer = this.getPointer(event.center);
this.selectionHandler._generateClickEvent("doubleClick",pointer);
}
@ -139,9 +146,9 @@ class InteractionHandler {
* @private
*/
onHold(event) {
var pointer = this.getPointer(event.center);
let pointer = this.getPointer(event.center);
var selectionChanged = this.selectionHandler.selectAdditionalOnPoint(pointer);
let selectionChanged = this.selectionHandler.selectAdditionalOnPoint(pointer);
if (selectionChanged === true) { // select or longpress
this.body.emitter.emit('select', this.selectionHandler.getSelection());
@ -174,7 +181,7 @@ class InteractionHandler {
}
// note: drag.pointer is set in onTouch to get the initial touch location
var node = this.selectionHandler.getNodeAt(this.drag.pointer);
let node = this.selectionHandler.getNodeAt(this.drag.pointer);
this.drag.dragging = true;
this.drag.selection = [];
@ -191,12 +198,12 @@ class InteractionHandler {
this.selectionHandler.selectObject(node);
}
var selection = this.selectionHandler.selectionObj.nodes;
let selection = this.selectionHandler.selectionObj.nodes;
// create an array with the selected nodes and their original location and status
for (let nodeId in selection) {
if (selection.hasOwnProperty(nodeId)) {
var object = selection[nodeId];
var s = {
let object = selection[nodeId];
let s = {
id: object.id,
node: object,
@ -229,16 +236,16 @@ class InteractionHandler {
// remove the focus on node if it is focussed on by the focusOnNode
this.body.emitter.emit("unlockNode");
var pointer = this.getPointer(event.center);
var selection = this.drag.selection;
let pointer = this.getPointer(event.center);
let selection = this.drag.selection;
if (selection && selection.length && this.options.dragNodes === true) {
// calculate delta's and new location
var deltaX = pointer.x - this.drag.pointer.x;
var deltaY = pointer.y - this.drag.pointer.y;
let deltaX = pointer.x - this.drag.pointer.x;
let deltaY = pointer.y - this.drag.pointer.y;
// update position of all selected nodes
selection.forEach((selection) => {
var node = selection.node;
let node = selection.node;
// only move the node if it was not fixed initially
if (selection.xFixed === false) {
node.x = this.canvas._XconvertDOMtoCanvas(this.canvas._XconvertCanvasToDOM(selection.x) + deltaX);
@ -261,8 +268,8 @@ class InteractionHandler {
this._handleDragStart(event);
return;
}
var diffX = pointer.x - this.drag.pointer.x;
var diffY = pointer.y - this.drag.pointer.y;
let diffX = pointer.x - this.drag.pointer.x;
let diffY = pointer.y - this.drag.pointer.y;
this.body.view.translation = {x:this.drag.translation.x + diffX, y:this.drag.translation.y + diffY};
this.body.emitter.emit("_redraw");
@ -277,7 +284,7 @@ class InteractionHandler {
*/
onDragEnd(event) {
this.drag.dragging = false;
var selection = this.drag.selection;
let selection = this.drag.selection;
if (selection && selection.length) {
selection.forEach(function (s) {
// restore original xFixed and yFixed
@ -301,7 +308,7 @@ class InteractionHandler {
* @private
*/
onPinch(event) {
var pointer = this.getPointer(event.center);
let pointer = this.getPointer(event.center);
this.drag.pinched = true;
if (this.pinch['scale'] === undefined) {
@ -309,7 +316,7 @@ class InteractionHandler {
}
// TODO: enabled moving while pinching?
var scale = this.pinch.scale * event.scale;
let scale = this.pinch.scale * event.scale;
this.zoom(scale, pointer)
}
@ -323,7 +330,7 @@ class InteractionHandler {
*/
zoom(scale, pointer) {
if (this.options.zoomView === true) {
var scaleOld = this.body.view.scale;
let scaleOld = this.body.view.scale;
if (scale < 0.00001) {
scale = 0.00001;
}
@ -331,24 +338,24 @@ class InteractionHandler {
scale = 10;
}
var preScaleDragPointer = null;
let preScaleDragPointer = null;
if (this.drag !== undefined) {
if (this.drag.dragging === true) {
preScaleDragPointer = this.canvas.DOMtoCanvas(this.drag.pointer);
}
}
// + this.canvas.frame.canvas.clientHeight / 2
var translation = this.body.view.translation;
let translation = this.body.view.translation;
var scaleFrac = scale / scaleOld;
var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
let scaleFrac = scale / scaleOld;
let tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
let ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
this.body.view.scale = scale;
this.body.view.translation = {x:tx, y:ty};
if (preScaleDragPointer != null) {
var postScaleDragPointer = this.canvas.canvasToDOM(preScaleDragPointer);
let postScaleDragPointer = this.canvas.canvasToDOM(preScaleDragPointer);
this.drag.pointer.x = postScaleDragPointer.x;
this.drag.pointer.y = postScaleDragPointer.y;
}
@ -374,7 +381,7 @@ class InteractionHandler {
*/
onMouseWheel(event) {
// retrieve delta
var delta = 0;
let delta = 0;
if (event.wheelDelta) { /* IE/Opera. */
delta = event.wheelDelta / 120;
} else if (event.detail) { /* Mozilla case. */
@ -389,15 +396,15 @@ class InteractionHandler {
if (delta) {
// calculate the new scale
var scale = this.body.view.scale;
var zoom = delta / 10;
let scale = this.body.view.scale;
let zoom = delta / 10;
if (delta < 0) {
zoom = zoom / (1 - zoom);
}
scale *= (1 + zoom);
// calculate the pointer location
var pointer = {x:event.pageX, y:event.pageY};
let pointer = {x:event.pageX, y:event.pageY};
// apply the new scale
this.zoom(scale, pointer);
@ -414,76 +421,212 @@ class InteractionHandler {
* @private
*/
onMouseMove(event) {
// var pointer = {x:event.pageX, y:event.pageY};
// var popupVisible = false;
//
// // check if the previously selected node is still selected
// if (this.popup !== undefined) {
// if (this.popup.hidden === false) {
// this._checkHidePopup(pointer);
// }
//
// // if the popup was not hidden above
// if (this.popup.hidden === false) {
// popupVisible = true;
// this.popup.setPosition(pointer.x + 3, pointer.y - 5)
// this.popup.show();
// }
// }
//
// // if we bind the keyboard to the div, we have to highlight it to use it. This highlights it on mouse over
// if (this.options.keyboard.bindToWindow == false && this.options.keyboard.enabled === true) {
// this.canvas.frame.focus();
// }
//
// // start a timeout that will check if the mouse is positioned above an element
// if (popupVisible === false) {
// var me = this;
// var checkShow = function() {
// me._checkShowPopup(pointer);
// };
//
// if (this.popupTimer) {
// clearInterval(this.popupTimer); // stop any running calculationTimer
// }
// if (!this.drag.dragging) {
// this.popupTimer = setTimeout(checkShow, this.options.tooltip.delay);
// }
// }
//
// /**
// * Adding hover highlights
// */
// if (this.options.hoverEnabled === true) {
// // removing all hover highlights
// for (var edgeId in this.hoverObj.edges) {
// if (this.hoverObj.edges.hasOwnProperty(edgeId)) {
// this.hoverObj.edges[edgeId].hover = false;
// delete this.hoverObj.edges[edgeId];
// }
// }
//
// // adding hover highlights
// var obj = this.selectionHandler.getNodeAt(pointer);
// if (obj == null) {
// obj = this.selectionHandler.getEdgeAt(pointer);
// }
// if (obj != null) {
// this._hoverObject(obj);
// }
//
// // removing all node hover highlights except for the selected one.
// for (var nodeId in this.hoverObj.nodes) {
// if (this.hoverObj.nodes.hasOwnProperty(nodeId)) {
// if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj == null) {
// this._blurObject(this.hoverObj.nodes[nodeId]);
// delete this.hoverObj.nodes[nodeId];
// }
// }
// }
// this.body.emitter.emit("_requestRedraw");
// }
let pointer = {x:event.pageX, y:event.pageY};
let popupVisible = false;
// check if the previously selected node is still selected
if (this.popup !== undefined) {
if (this.popup.hidden === false) {
this._checkHidePopup(pointer);
}
// if the popup was not hidden above
if (this.popup.hidden === false) {
popupVisible = true;
this.popup.setPosition(pointer.x + 3, pointer.y - 5)
this.popup.show();
}
}
// if we bind the keyboard to the div, we have to highlight it to use it. This highlights it on mouse over.
if (this.options.keyboard.bindToWindow == false && this.options.keyboard.enabled === true) {
this.canvas.frame.focus();
}
// start a timeout that will check if the mouse is positioned above an element
if (popupVisible === false) {
if (this.popupTimer !== undefined) {
clearInterval(this.popupTimer); // stop any running calculationTimer
this.popupTimer = undefined;
}
if (!this.drag.dragging) {
this.popupTimer = setTimeout(() => this._checkShowPopup(pointer), this.options.tooltip.delay);
}
}
/**
* Adding hover highlights
*/
if (this.options.hoverEnabled === true) {
// removing all hover highlights
for (let edgeId in this.hoverObj.edges) {
if (this.hoverObj.edges.hasOwnProperty(edgeId)) {
this.hoverObj.edges[edgeId].hover = false;
delete this.hoverObj.edges[edgeId];
}
}
// adding hover highlights
let obj = this.selectionHandler.getNodeAt(pointer);
if (obj == null) {
obj = this.selectionHandler.getEdgeAt(pointer);
}
if (obj != null) {
this.selectionHandler.hoverObject(obj);
}
// removing all node hover highlights except for the selected one.
for (let nodeId in this.hoverObj.nodes) {
if (this.hoverObj.nodes.hasOwnProperty(nodeId)) {
if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj == null) {
this.selectionHandler.blurObject(this.hoverObj.nodes[nodeId]);
delete this.hoverObj.nodes[nodeId];
}
}
}
this.body.emitter.emit("_requestRedraw");
}
}
/**
* 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
*/
_checkShowPopup(pointer) {
let x = this.canvas._XconvertDOMtoCanvas(pointer.x);
let y = this.canvas._YconvertDOMtoCanvas(pointer.y);
let pointerObj = {
left: x,
top: y,
right: x,
bottom: y
};
let previousPopupObjId = this.popupObj === undefined ? "" : this.popupObj.id;
let nodeUnderCursor = false;
let popupType = "node";
// check if a node is under the cursor.
if (this.popupObj === undefined) {
// search the nodes for overlap, select the top one in case of multiple nodes
let nodeIndices = this.body.nodeIndices;
let nodes = this.body.nodes;
let node;
let overlappingNodes = [];
for (let i = 0; i < nodeIndices.length; i++) {
node = nodes[nodeIndices[i]];
if (node.isOverlappingWith(pointerObj) === true) {
if (node.getTitle() !== undefined) {
overlappingNodes.push(nodeIndices[i]);
}
}
}
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 = 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
let edgeIndices = this.body.edgeIndices;
let edges = this.body.edges;
let edge;
let overlappingEdges = [];
for (let i = 0; i < edgeIndices.length; i++) {
edge = edges[edgeIndices[i]];
if (edge.isOverlappingWith(pointerObj) === true) {
if (edge.connected === true && edge.getTitle() !== undefined) {
overlappingEdges.push(edgeIndices[i]);
}
}
}
if (overlappingEdges.length > 0) {
this.popupObj = edges[overlappingEdges[overlappingEdges.length - 1]];
popupType = "edge";
}
}
if (this.popupObj !== undefined) {
// show popup message window
if (this.popupObj.id != previousPopupObjId) {
if (this.popup === undefined) {
this.popup = new Popup(this.frame, this.options.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
*/
_checkHidePopup(pointer) {
let x = this.canvas._XconvertDOMtoCanvas(pointer.x);
let y = this.canvas._YconvertDOMtoCanvas(pointer.y);
let pointerObj = {
left: x,
top: y,
right: x,
bottom: y
};
let stillOnObj = false;
if (this.popup.popupTargetType == 'node') {
if (this.body.nodes[this.popup.popupTargetId] !== undefined) {
stillOnObj = this.body.nodes[this.popup.popupTargetId].isOverlappingWith(pointerObj);
// if the mouse is still one the node, we have to check if it is not also on one that is drawn on top of it.
// we initially only check stillOnObj because this is much faster.
if (stillOnObj === true) {
let overNode = this.selectionHandler.getNodeAt(pointer);
stillOnObj = overNode.id == this.popup.popupTargetId;
}
}
}
else {
if (this.selectionHandler.getNodeAt(pointer) === null) {
if (this.body.edges[this.popup.popupTargetId] !== undefined) {
stillOnObj = this.body.edges[this.popup.popupTargetId].isOverlappingWith(pointerObj);
}
}
}
if (stillOnObj === false) {
this.popupObj = undefined;
this.popup.hide();
}
}
}
export default InteractionHandler;

+ 1
- 0
lib/network/modules/PhysicsEngine.js View File

@ -81,6 +81,7 @@ class PhysicsEngine {
setOptions(options) {
if (options === false) {
this.physicsEnabled = false;
this.stopSimulation();
}
else {
if (options !== undefined) {

+ 2
- 2
lib/network/modules/SelectionHandler.js View File

@ -459,7 +459,7 @@ class SelectionHandler {
* @param {Node || Edge} object
* @private
*/
_blurObject(object) {
blurObject(object) {
if (object.hover == true) {
object.hover = false;
this.body.emitter.emit("blurNode",{node:object.id});
@ -473,7 +473,7 @@ class SelectionHandler {
* @param {Node || Edge} object
* @private
*/
_hoverObject(object) {
hoverObject(object) {
if (object.hover == false) {
object.hover = true;
this._addToHover(object);

+ 25
- 40
lib/network/modules/components/Edge.js View File

@ -32,7 +32,6 @@ class Edge {
this.id = undefined;
this.fromId = undefined;
this.toId = undefined;
this.title = undefined;
this.value = undefined;
this.selected = false;
this.hover = false;
@ -81,6 +80,7 @@ class Edge {
'scaling',
'selfReferenceSize',
'to',
'title',
'value',
'width',
'widthMin',
@ -92,6 +92,13 @@ class Edge {
util.mergeOptions(this.options, options, 'smooth');
util.mergeOptions(this.options, options, 'dashes');
if (options.id !== undefined) {this.id = options.id;}
if (options.from !== undefined) {this.fromId = options.from;}
if (options.to !== undefined) {this.toId = options.to;}
if (options.title !== undefined) {this.title = options.title;}
if (options.value !== undefined) {this.value = options.value;}
// hanlde multiple input cases for arrows
if (options.arrows !== undefined) {
if (typeof options.arrows === 'string') {
let arrows = options.arrows.toLowerCase();
@ -109,55 +116,29 @@ class Edge {
}
}
if (options.id !== undefined) {this.id = options.id;}
if (options.from !== undefined) {this.fromId = options.from;}
if (options.to !== undefined) {this.toId = options.to;}
if (options.title !== undefined) {this.title = options.title;}
if (options.value !== undefined) {this.value = options.value;}
// hanlde multiple input cases for color
if (options.color !== undefined) {
if (options.color !== undefined) {
if (util.isString(options.color)) {
util.assignAllKeys(this.options.color, options.color);
}
else {
util.extend(this.options.color, options.color);
}
if (util.isString(options.color)) {
util.assignAllKeys(this.options.color, options.color);
this.options.color.inherit.enabled = false;
}
//if (util.isString(options.color)) {
// this.options.color.color = options.color;
// this.options.color.highlight = options.color;
//}
//else {
// if (options.color.color !== undefined) {
// this.options.color.color = options.color.color;
// }
// if (options.color.highlight !== undefined) {
// this.options.color.highlight = options.color.highlight;
// }
// if (options.color.hover !== undefined) {
// this.options.color.hover = options.color.hover;
// }
//}
//
//// inherit colors
//if (options.color.inherit === undefined) {
// this.options.color.inherit.enabled = false;
//}
//else {
// util.mergeOptions(this.options.color, options.color, 'inherit');
//}
else {
util.extend(this.options.color, options.color);
if (options.color.inherit === undefined) {
this.options.color.inherit.enabled = false;
}
}
util.mergeOptions(this.options.color, options.color, 'inherit');
}
// A node is connected when it has a from and to node that both exist in the network.body.nodes.
this.connect();
this.labelModule.setOptions(this.options);
this.updateEdgeType();
return this.edgeType.setOptions(this.options);
let dataChanged = this.updateEdgeType();
return dataChanged;
}
updateEdgeType() {
@ -187,6 +168,10 @@ class Edge {
this.edgeType = new StraightEdge(this.options, this.body, this.labelModule);
}
}
else {
// if nothing changes, we just set the options.
this.edgeType.setOptions(this.options);
}
return dataChanged;
}

+ 1
- 1
lib/network/modules/components/NavigationHandler.js View File

@ -201,4 +201,4 @@ class NavigationHandler {
}
export {NavigationHandler};
export default NavigationHandler;

+ 8
- 19
lib/network/modules/components/Node.js View File

@ -122,35 +122,24 @@ class Node {
'physics',
'shape',
'size',
'title',
'value',
'x',
'y'
];
util.selectiveDeepExtend(fields, this.options, options);
// basic options
if (options.id !== undefined) {
this.id = options.id;
}
if (options.x !== undefined) {
this.x = options.x;
this.predefinedPosition = true;
}
if (options.y !== undefined) {
this.y = options.y;
this.predefinedPosition = true;
}
if (options.value !== undefined) {
this.value = options.value;
}
if (options.level !== undefined) {
this.level = options.level;
this.preassignedLevel = true;
}
if (options.id !== undefined) {this.id = options.id;}
if (this.id === undefined) {
throw "Node must have an id";
}
if (options.x !== undefined) {this.x = options.x; this.predefinedPosition = true;}
if (options.y !== undefined) {this.y = options.y; this.predefinedPosition = true;}
if (options.value !== undefined) {this.value = options.value;}
// copy group options
if (typeof options.group === 'number' || (typeof options.group === 'string' && options.group != '')) {
var groupObj = this.grouplist.get(options.group);
@ -280,7 +269,7 @@ class Node {
* has been set.
*/
getTitle() {
return typeof this.title === "function" ? this.title() : this.title;
return typeof this.options.title === "function" ? this.options.title() : this.options.title;
}

+ 137
- 0
lib/network/modules/components/Popup.js View File

@ -0,0 +1,137 @@
/**
* Popup is a class to create a popup window with some text
* @param {Element} container The container object.
* @param {Number} [x]
* @param {Number} [y]
* @param {String} [text]
* @param {Object} [style] An object containing borderColor,
* backgroundColor, etc.
*/
class Popup {
constructor(container, x, y, text, style) {
if (container) {
this.container = container;
}
else {
this.container = document.body;
}
// x, y and text are optional, see if a style object was passed in their place
if (style === undefined) {
if (typeof x === "object") {
style = x;
x = undefined;
} else if (typeof text === "object") {
style = text;
text = undefined;
} else {
// for backwards compatibility, in case clients other than Network are creating Popup directly
style = {
fontColor: 'black',
fontSize: 14, // px
fontFace: 'verdana',
color: {
border: '#666',
background: '#FFFFC6'
}
}
}
}
this.x = 0;
this.y = 0;
this.padding = 5;
this.hidden = false;
if (x !== undefined && y !== undefined) {
this.setPosition(x, y);
}
if (text !== undefined) {
this.setText(text);
}
// create the frame
this.frame = document.createElement('div');
this.frame.className = 'network-tooltip';
this.frame.style.color = style.fontColor;
this.frame.style.backgroundColor = style.color.background;
this.frame.style.borderColor = style.color.border;
this.frame.style.fontSize = style.fontSize + 'px';
this.frame.style.fontFamily = style.fontFace;
this.container.appendChild(this.frame);
}
/**
* @param {number} x Horizontal position of the popup window
* @param {number} y Vertical position of the popup window
*/
setPosition(x, y) {
this.x = parseInt(x);
this.y = parseInt(y);
}
/**
* Set the content for the popup window. This can be HTML code or text.
* @param {string | Element} content
*/
setText(content) {
if (content instanceof Element) {
this.frame.innerHTML = '';
this.frame.appendChild(content);
}
else {
this.frame.innerHTML = content; // string containing text or HTML
}
}
/**
* Show the popup window
* @param {boolean} show Optional. Show or hide the window
*/
show(show) {
if (show === undefined) {
show = true;
}
if (show === true) {
var height = this.frame.clientHeight;
var width = this.frame.clientWidth;
var maxHeight = this.frame.parentNode.clientHeight;
var maxWidth = this.frame.parentNode.clientWidth;
var top = (this.y - height);
if (top + height + this.padding > maxHeight) {
top = maxHeight - height - this.padding;
}
if (top < this.padding) {
top = this.padding;
}
var left = this.x;
if (left + width + this.padding > maxWidth) {
left = maxWidth - width - this.padding;
}
if (left < this.padding) {
left = this.padding;
}
this.frame.style.left = left + "px";
this.frame.style.top = top + "px";
this.frame.style.visibility = "visible";
this.hidden = false;
}
else {
this.hide();
}
}
/**
* Hide the popup window
*/
hide() {
this.hidden = true;
this.frame.style.visibility = "hidden";
}
}
export default Popup;

+ 0
- 1
lib/network/modules/components/edges/straightEdge.js View File

@ -26,7 +26,6 @@ class StraightEdge extends EdgeBase {
return undefined;
}
/**
* Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way
* @param percentage

Loading…
Cancel
Save