When true, the nodes are not drawn when dragging the view. This can greatly speed up responsiveness on dragging, improving user experience.
hover
Boolean
false
When true, the nodes use their hover colors when the mouse moves over them.
hoverConnectedEdges
Boolean
true
When true, on hovering over a node, it's connecting edges are highlighted.
+
keyboard
Object or Boolean
Object
When true, the keyboard shortcuts are enabled with the default settings. For further customization, you can supply an object.
keyboard.enabled
Boolean
false
Toggle the usage of the keyboard shortcuts. If this option is not defined, it is set to true if any of the properties in this object are defined.
keyboard.speed.x
Number
1
The speed at which the view moves in the x direction on pressing a key or pressing a navigation button.
keyboard.speed.y
Number
1
The speed at which the view moves in the y direction on pressing a key or pressing a navigation button.
keyboard.speed.zoom
Number
0.02
The speed at which the view zooms in or out pressing a key or pressing a navigation button.
keyboard.bindToWindow
Boolean
true
When binding the keyboard shortcuts to the window, they will work regardless of which DOM object has the focus. If you have multiple networks on your page, you could set this to false, making sure the keyboard shortcuts only work on the network that has the focus.
+
multiselect
Boolean
false
When true, a longheld click (or touch) as well as a control-click will add to the selection.
+
+
selectionBox
Object or Boolean
Object
When true, the selectionBox is enabled with the default settings. For further customization, you can supply an object.
+
selectionBox.enabled
Boolean
false
Toggle the usage of the selectionBox shortcuts
+
selectionBox.nodes
Boolean
true
Whether the selection box will select nodes when the user releases the box.
+
selectionBox.edges
Boolean
false
Whether the selection box will select edges when the user releases the box.
+
selectionBox.strokeStyle
String
rgb(0,0,0)
The style of the selection box's border. Any valid color,gradient or pattern will work (see MDN: CanvasRenderingContext2D.strokeStyle), but there is no error checking for this value!
+
selectionBox.fillStyle
String
rgba(0,0,0,.0625)
The style of the selection box's fill. Any valid color,gradient or pattern will work (see MDN: CanvasRenderingContext2D.fillStyle), but there is no error checking for this value!
+
selectionBox.edgeAccuracy
Number
25
"Accuracy" of the edge detection. Edges in Vis.js are Bezier curves, so we plot points along an edge and check if a point is within our selection box. The number of points per edge used during this process is specified with this value.
+
navigationButtons
Boolean
false
When true, navigation buttons are drawn on the network canvas. These are HTML buttons and can be completely customized using CSS.
selectable
Boolean
true
When true, the nodes and edges can be selected by the user.
selectConnectedEdges
Boolean
true
When true, on selecting a node, its connecting edges are highlighted.
+ This example shows how the selection box works. Press and hold the control key and click with your mouse on an area within the Network.
+ Drag and release to select nodes and edges within the bounds of the resulting box. Here, you can enable / disable the options (or the entire feature) in the configurator.
+ To modify the options programmatically simply call Ne
+
+
+ Options available:
+
+
"nodes": Select nodes within the box's bounds
+
"edges": Select edges within the box's bounds
+
"strokeStyle": An [RGB|RGBA] string to set the color of the box's border
+
"fillStyle": An RGBA string to set the color of the box's fill (you probably want this at a low opacity)
+
"edgeAccuracy": The number of points generated on each edge that are used to check if the edge is contaned within the box
+ The default is 25, which means for every edge, we calculate at most 25 evenly distributed points across each line.
+
+
+
+
+
+
+
+
+
diff --git a/lib/network/modules/Canvas.js b/lib/network/modules/Canvas.js
index 134083eb5..753e56cb9 100644
--- a/lib/network/modules/Canvas.js
+++ b/lib/network/modules/Canvas.js
@@ -254,10 +254,6 @@ class Canvas {
this.frame.canvas.addEventListener('mousemove', (event) => {this.body.eventListeners.onMouseMove(event)});
this.frame.canvas.addEventListener('contextmenu', (event) => {this.body.eventListeners.onContext(event)});
- //this.frame.canvas.addEventListener('mousedown', (event) => {this.body.eventListeners.onMouseDown(event)});
-
- this.frame.canvas.addEventListener('mouseenter', (event) => {this.body.eventListeners.onMouseEnter(event)});
- this.frame.canvas.addEventListener('mouseleave', (event) => {this.body.eventListeners.onMouseLeave(event)});
this.hammerFrame = new Hammer(this.frame);
hammerUtil.onRelease(this.hammerFrame, (event) => {this.body.eventListeners.onRelease(event)});
diff --git a/lib/network/modules/CanvasRenderer.js b/lib/network/modules/CanvasRenderer.js
index a29b32556..8c82a7212 100644
--- a/lib/network/modules/CanvasRenderer.js
+++ b/lib/network/modules/CanvasRenderer.js
@@ -138,7 +138,7 @@ class CanvasRenderer {
* @returns {function|undefined}
* @private
*/
- _requestNextFrame(callback, delay) {
+ _requestNextFrame(callback, delay) {
// During unit testing, it happens that the mock window object is reset while
// the next frame is still pending. Then, either 'window' is not present, or
// 'requestAnimationFrame()' is not present because it is not defined on the
@@ -274,7 +274,8 @@ class CanvasRenderer {
this._drawNodes(ctx, hidden);
}
- // if it should draw selection box:
+
+ // if it should draw selection box
if (this.selectionBox.isActive()) {
this._drawSelectionBox(ctx);
}
@@ -295,11 +296,14 @@ class CanvasRenderer {
_drawSelectionBox(ctx) {
ctx.save();
{
+ ctx.beginPath();
+ ctx.strokeStyle = this.selectionBox.options.strokeStyle;
+ ctx.fillStyle = this.selectionBox.options.fillStyle;
+
ctx.rect(this.selectionBox.x, this.selectionBox.y, this.selectionBox.width, this.selectionBox.height);
ctx.stroke();
-
- ctx.fillStyle = "rgba(0, 0, 0, 0.0625)";
ctx.fillRect(this.selectionBox.x + 1, this.selectionBox.y + 1, this.selectionBox.width - 2, this.selectionBox.height - 2);
+ ctx.closePath();
}
ctx.restore();
}
diff --git a/lib/network/modules/InteractionHandler.js b/lib/network/modules/InteractionHandler.js
index 1281894be..bcf1c88aa 100644
--- a/lib/network/modules/InteractionHandler.js
+++ b/lib/network/modules/InteractionHandler.js
@@ -35,9 +35,6 @@ class InteractionHandler {
this.body.eventListeners.onRelease = this.onRelease.bind(this);
this.body.eventListeners.onContext = this.onContext.bind(this);
- this.body.eventListeners.onMouseEnter = this.onMouseEnter.bind(this);
- this.body.eventListeners.onMouseLeave = this.onMouseLeave.bind(this);
-
this.touchTime = 0;
this.drag = {};
this.pinch = {};
@@ -67,18 +64,6 @@ class InteractionHandler {
this.bindEventListeners()
}
- onMouseLeave(event) {
- if (this._selectionBoxOption()) {
- this.selectionBox.mouseLeave(event);
- }
- }
-
- onMouseEnter(event) {
- if (this._selectionBoxOption()) {
- this.selectionBox.mouseEnter(event);
- }
- }
-
_selectionBoxOption() {
return this.selectionHandler.options.selectionBox.enabled;
//return true;
@@ -157,7 +142,7 @@ class InteractionHandler {
x: event.srcEvent.offsetX,
y: event.srcEvent.offsetY
});
- this.selectionBox.activate(p);
+ this.selectionBox.activate(event.srcEvent);
}
}
@@ -223,7 +208,7 @@ class InteractionHandler {
onRelease(event) {
if (new Date().valueOf() - this.touchTime > 10) {
if (this.selectionBox.isActive()) {
- this.selectionBox.complete();
+ this.selectionBox.release();
}
let pointer = this.getPointer(event.center);
this.selectionHandler._generateClickEvent('release', event, pointer);
diff --git a/lib/network/modules/SelectionBox.js b/lib/network/modules/SelectionBox.js
index 07645e668..55b05e981 100644
--- a/lib/network/modules/SelectionBox.js
+++ b/lib/network/modules/SelectionBox.js
@@ -2,18 +2,13 @@ class SelectionBox {
constructor(body, selectionHandler) {
this.body = body;
this.selectionHandler = selectionHandler;
+
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
- // assist in adjusting width / height correctly when mouse moves out of canvas element
- this.domAdjustX = 0;
- this.domAdjustY = 0;
- this.lastCanvasScreenX = 0;
- this.lastCanvasScreenY = 0;
- this._consumeMouseEvent = this._defaultMouseEventConsumer;
-
+ this.options = this.selectionHandler.options.selectionBox;
this.active = false;
}
@@ -24,12 +19,16 @@ class SelectionBox {
return this.active;
}
- //
- // activate the selectionBox
- // pass in the origin point in canvas space
- // p := {x: number, y: number}
- //
- activate(p) {
+ /**
+ * activate the selectionBox, user has pressed ctrl + mousebutton
+ * @param {MouseEvent} ev
+ */
+ activate(ev) {
+ let p = this.selectionHandler.canvas.DOMtoCanvas({
+ x: ev.offsetX,
+ y: ev.offsetY
+ });
+
this.x = p.x;
this.y = p.y;
this.width = 0;
@@ -45,61 +44,12 @@ class SelectionBox {
this.active = false;
}
- //
- //
- //
-
- _defaultMouseEventConsumer(ev) {
- let p = this.selectionHandler.canvas.DOMtoCanvas({
- x: ev.offsetX,
- y: ev.offsetY
- });
-
- let xDir = this.x <= p.x ? 1 : -1;
- let yDir = this.y <= p.y ? 1 : -1;
-
- //this.width = Math.abs(ev.x - this.x) * xDir;
- //this.height = Math.abs(ev.y - this.y) * yDir;
- let width = Math.abs(p.x - this.x) * xDir;
- let height = Math.abs(p.y - this.y) * yDir;
- return {
- x: width,
- y: height
- }
- }
-
- _outsideCanvasMouseEventConsumer(ev) {
- return {
- x: this.width + (ev.movementX * 1 / this.selectionHandler.canvas.body.view.scale), // ev.offsetX + this.domAdjustX,
- y: this.height + (ev.movementY * 1 / this.selectionHandler.canvas.body.view.scale) //ev.offsetY + this.domAdjustY
- }
- }
-
- mouseLeave(ev) {
- if (this.isActive()) {
- console.log("MOUSELEAVE " + ev.offsetX + ", " + ev.offsetY);
- //this.lastCanvasScreenX = ev.offsetX;
- //this.lastCanvasScreenY = ev.offsetY;
- //this._consumeMouseEvent = this._findDomAdjustment;
- this._consumeMouseEvent = this._outsideCanvasMouseEventConsumer;
- }
- }
-
- mouseEnter(ev) {
- if (this.isActive()) {
- this.lastCanvasScreenX = 0;
- this.lastCanvasScreenY = 0;
- this.domAdjustX = 0;
- this.domAdjustY = 0;
- this._consumeMouseEvent = this._defaultMouseEventConsumer;
- }
- }
//
- // complete the selectionBox, the user has let go of their mouse button
+ // the user has let go of their mouse button, release the selectionBox
// select the nodes and edges within the bounds of the selection box
//
- complete() {
+ release() {
let boundingBox = this.getBoundingBox();
if (this.selectionHandler.options.selectionBox.edges) {
// run the edges first, so that if "this.selectionHandler.options.selectConnectedEdges" is set,
@@ -130,6 +80,100 @@ class SelectionBox {
this.deactivate();
}
+ /**
+ * Calculate new box corner {x,y} based on mouse input
+ * returning values in canvas space
+ * this is easy when the user remains in the canvas
+ * but needs a bit of extra logic to handle a mouse movement that extends outside of the canvas
+ * NOTE: the order of the per-axis predicates is important here!
+ *
+ * @param {MouseEvent} ev
+ *
+ */
+ _consumeMouseEvent(ev) {
+ let frameRect = this.selectionHandler.canvas.frame.getBoundingClientRect();
+ let x, y;
+
+ //
+ // X-Axis
+ //
+
+ // mouse to the left of viewport
+ if (ev.clientX < 0) {
+ // is top of frame also above viewport?
+ if (frameRect.left < 0) {
+ x = (-frameRect.left) + 1;
+ }
+ else {
+ x = 1;
+ }
+ }
+ // mouse to the left of frame
+ else if (ev.clientX < frameRect.left) {
+ x = 1;
+ }
+ // mouse to the right of viewport
+ else if (ev.clientX > window.innerWidth) {
+ // is right of frame also beyond viewport?
+ if (frameRect.right > window.innerWidth) {
+ x = window.innerWidth - frameRect.left - 1;
+ }
+ else {
+ x = frameRect.right - frameRect.left - 1;
+ }
+ }
+ // mouse to the right of frame
+ else if (ev.clientX > frameRect.right) {
+ x = frameRect.right - frameRect.left - 1;
+ }
+ // mouse horizontally within frame
+ else {
+ x = ev.clientX - frameRect.x;
+ }
+
+ //
+ // Y-Axis
+ //
+
+ // mouse above viewport
+ if (ev.clientY < 0) {
+ // is top of frame also above viewport?
+ if (frameRect.top < 0) {
+ y = (-frameRect.top) + 1;
+ }
+ else {
+ y = 1;
+ }
+ }
+ // mouse above frame
+ else if (ev.clientY < frameRect.top) {
+ y = 1;
+ }
+ // mouse below viewport
+ else if (ev.clientY > window.innerHeight) {
+ // is bottom of frame also below viewport?
+ if (frameRect.bottom > window.innerHeight) {
+ y = window.innerHeight - frameRect.top - 1;
+ }
+ else {
+ y = frameRect.bottom - frameRect.top - 1;
+ }
+ }
+ // mouse below frame
+ else if (ev.clientY > frameRect.bottom) {
+ y = frameRect.bottom - frameRect.top - 1;
+ }
+ // mouse vertically within frame
+ else {
+ y = ev.clientY - frameRect.y;
+ }
+
+ return this.selectionHandler.canvas.DOMtoCanvas({
+ x: x,
+ y: y
+ });
+ }
+
//
// get the current bounding box for the user's selection box
// returns {left, top, right, bottom} in canvas model space
@@ -161,28 +205,25 @@ class SelectionBox {
}
return result;
- }
-
- //
- // update the bounding box as per user mouse movement
- // p is an object {x, y} in canvas model space
- // new position of mouse is used as new corner of box
- // (point at which user began the bounding box remains fixed)
- //
- updateBoundingBox(/*p*/ ev) {
- let {x, y} = this._consumeMouseEvent(ev);
- let xDir = this.x <= x ? 1 : -1;
- let yDir = this.y <= y ? 1 : -1;
-
- this.width = x; // Math.abs(x - this.x) * xDir;
- this.height = y; // Math.abs(y - this.y) * yDir;
-
- /*
- this.width = Math.abs(p.x - this.x) * xDir;
- this.height = Math.abs(p.y - this.y) * yDir;
- */
+ }
+
+ /**
+ * update the bounding box as per user mouse movement
+ * new position of mouse is used as new corner of box
+ * (point at which user began the bounding box remains fixed)
+ *
+ * @param {MouseEvent} ev
+ */
+ updateBoundingBox(ev) {
+ let {x: newX, y: newY} = this._consumeMouseEvent(ev);
+ let xDir = this.x <= newX ? 1 : -1;
+ let yDir = this.y <= newY ? 1 : -1;
+
+ this.width = Math.abs(newX - this.x) * xDir;
+ this.height = Math.abs(newY - this.y) * yDir;
+
this.body.emitter.emit("_redraw");
- }
+ }
}
export default SelectionBox;
\ No newline at end of file
diff --git a/lib/network/modules/SelectionHandler.js b/lib/network/modules/SelectionHandler.js
index 6d50d61d9..bd1f29adb 100644
--- a/lib/network/modules/SelectionHandler.js
+++ b/lib/network/modules/SelectionHandler.js
@@ -24,9 +24,12 @@ class SelectionHandler {
selectConnectedEdges: true,
hoverConnectedEdges: true,
selectionBox: {
- enabled: true,
+ enabled: false,
nodes: true,
- edges: false
+ edges: false,
+ strokeStyle: "rgb(0,0,0)",
+ fillStyle: "rgba(0,0,0,.0625)",
+ edgeAccuracy: 25
}
};
@@ -42,14 +45,12 @@ class SelectionHandler {
*
* @param {Object} [options]
*/
- setOptions(options) {
- if (options !== undefined) {
+ setOptions(options) {
+ if (options !== undefined) {
let fields = ['multiselect','hoverConnectedEdges','selectable','selectConnectedEdges'];
util.selectiveDeepExtend(fields,this.options, options);
util.mergeOptions(this.options, options, "selectionBox");
-
- console.log(this.options.selectionBox);
}
}
@@ -216,7 +217,7 @@ class SelectionHandler {
return this._getAllNodesOverlappingWith(object);
}
-
+
/**
@@ -273,7 +274,7 @@ class SelectionHandler {
_getEdgesOverlappingWith(object, overlappingEdges) {
let edges = this.body.edges;
for (let i = 0; i < this.body.edgeIndices.length; i++) {
- let edgeId = this.body.edgeIndices[i];
+ let edgeId = this.body.edgeIndices[i];
if (edges[edgeId].isOverlappingWith(object)) {
overlappingEdges.push(edgeId);
}
@@ -293,11 +294,16 @@ class SelectionHandler {
return overlappingEdges;
}
- getAllEdgesWithinBoundingBox(object) {
+ /**
+ * get edge ids of all edges within a bounding box (bounding box in canvas model space)
+ * @param {{left: number, top: number, right: number, bottom:number}} boundingBox
+ * @return edgeId[]
+ */
+ getAllEdgesWithinBoundingBox(boundingBox) {
let result = [];
for (let i = 0; i < this.body.edgeIndices.length; i++) {
- let edgeId = this.body.edgeIndices[i];
- if (this.body.edges[edgeId].SB_checkBoundingBox(object, 25)) {
+ let edgeId = this.body.edgeIndices[i];
+ if (this.body.edges[edgeId].SB_checkBoundingBox(boundingBox, this.options.selectionBox.edgeAccuracy)) {
result.push(edgeId);
}
}
@@ -855,7 +861,7 @@ class SelectionHandler {
* @param {point} pointer mouse position in screen coordinates
* @returns {Array.}
* @private
- */
+ */
getClickedItems(pointer) {
let point = this.canvas.DOMtoCanvas(pointer);
var items = [];
diff --git a/lib/network/modules/components/Edge.js b/lib/network/modules/components/Edge.js
index f00cf834a..043583cc0 100644
--- a/lib/network/modules/components/Edge.js
+++ b/lib/network/modules/components/Edge.js
@@ -52,7 +52,7 @@ class Edge {
this.setOptions(options);
}
- SB_checkBoundingBox(boundingBox, points = 5) {
+ SB_checkBoundingBox(boundingBox, points) {
let step = 1 / points;
let percentage = 0;
for (let i = 0; i < points; i++) {
@@ -694,47 +694,7 @@ class Edge {
}
}
- SB_existsWithinBoundingBox(boundingBox) {
- let thisBox = {
- left: 0,
- top: 0,
- right: 0,
- bottom: 0
- };
-
- if (this.from.x < this.to.x) {
- thisBox.left = this.from.x;
- thisBox.right = this.to.x;
- }
- else {
- thisBox.left = this.to.x;
- thisBox.right = this.from.x;
- }
-
- if (this.from.y < this.to.y) {
- thisBox.top = this.from.y;
- thisBox.bottom = this.to.y;
- }
- else {
- thisBox.top = this.to.y;
- thisBox.bottom = this.from.y;
- }
-
- console.log(boundingBox);
- console.log(thisBox);
- console.log("====");
- if (thisBox.left >= boundingBox.left &&
- thisBox.right <= boundingBox.right &&
- thisBox.top >= boundingBox.top &&
- thisBox.bottom <= boundingBox.bottom) {
- return true;
- }
- else {
- return false;
- }
- }
-
- /**
+ /**
* Determine the rotation point, if any.
*
* @param {CanvasRenderingContext2D} [ctx] if passed, do a recalculation of the label size
diff --git a/lib/network/options.js b/lib/network/options.js
index 610ec23c1..5740d5a62 100644
--- a/lib/network/options.js
+++ b/lib/network/options.js
@@ -167,7 +167,10 @@ let allOptions = {
selectionBox: {
enabled: { boolean: bool },
nodes: { boolean: bool },
- edges: { boolean: bool },
+ edges: { boolean: bool },
+ strokeStyle: { string },
+ fillStyle: { string },
+ edgeAccuracy: { number },
__type__: { object, boolean: bool }
},
hoverConnectedEdges: { boolean: bool },
@@ -580,7 +583,10 @@ let configureOptions = {
selectionBox: {
enabled: false,
nodes: true,
- edges: false
+ edges: false,
+ strokeStyle: "rgb(0,0,0)",
+ fillStyle: "rgba(0,0,0,.0625)",
+ edgeAccuracy: [25, 1, 100, 1]
},
hoverConnectedEdges: true,
tooltipDelay: [300, 0, 1000, 25],
From dd76ffc041c0e6db78a918eca35d3528abe8aa91 Mon Sep 17 00:00:00 2001
From: softwareCobbler
Date: Sat, 2 Feb 2019 21:32:48 -0500
Subject: [PATCH 06/10] fix broken tests
---
lib/network/modules/CanvasRenderer.js | 6 ++++
lib/network/modules/InteractionHandler.js | 11 +++----
lib/network/modules/SelectionBox.js | 38 +++++++++++++++++------
lib/network/modules/SelectionHandler.js | 14 +++++----
lib/network/modules/components/Edge.js | 8 +++++
5 files changed, 54 insertions(+), 23 deletions(-)
diff --git a/lib/network/modules/CanvasRenderer.js b/lib/network/modules/CanvasRenderer.js
index 8c82a7212..bed0f79b1 100644
--- a/lib/network/modules/CanvasRenderer.js
+++ b/lib/network/modules/CanvasRenderer.js
@@ -50,6 +50,7 @@ class CanvasRenderer {
/**
* @param {Object} body
* @param {Canvas} canvas
+ * @param {SelectionBox} selectionBox
*/
constructor(body, canvas, selectionBox) {
_initRequestAnimationFrame();
@@ -293,6 +294,11 @@ class CanvasRenderer {
}
}
+ /**
+ * Draws the selectionBox
+ * @param {CanvasRenderingContext2D} ctx
+ * @private
+ */
_drawSelectionBox(ctx) {
ctx.save();
{
diff --git a/lib/network/modules/InteractionHandler.js b/lib/network/modules/InteractionHandler.js
index bcf1c88aa..ceca383d4 100644
--- a/lib/network/modules/InteractionHandler.js
+++ b/lib/network/modules/InteractionHandler.js
@@ -1,8 +1,6 @@
let util = require('../../util');
var NavigationHandler = require('./components/NavigationHandler').default;
var Popup = require('./../../shared/Popup').default;
-var SelectionBox = require('./SelectionBox').default;
-
/**
* Handler for interactions
@@ -64,9 +62,12 @@ class InteractionHandler {
this.bindEventListeners()
}
+ /**
+ * Helper function to check if the selectionBox option is enabled
+ * @private
+ */
_selectionBoxOption() {
return this.selectionHandler.options.selectionBox.enabled;
- //return true;
}
/**
@@ -138,10 +139,6 @@ class InteractionHandler {
if (new Date().valueOf() - this.touchTime > 50) {
if (this._selectionBoxOption()) {
if (event.srcEvent.ctrlKey) {
- let p = this.canvas.DOMtoCanvas({
- x: event.srcEvent.offsetX,
- y: event.srcEvent.offsetY
- });
this.selectionBox.activate(event.srcEvent);
}
}
diff --git a/lib/network/modules/SelectionBox.js b/lib/network/modules/SelectionBox.js
index 55b05e981..c7d35f238 100644
--- a/lib/network/modules/SelectionBox.js
+++ b/lib/network/modules/SelectionBox.js
@@ -1,10 +1,20 @@
+/**
+ * SelectionBox
+ */
class SelectionBox {
+ /**
+ *
+ * @param {Network.body} body
+ * @param {Network.SelectionHandler} selectionHandler
+ */
constructor(body, selectionHandler) {
this.body = body;
this.selectionHandler = selectionHandler;
+ // corner from initial ctrl+click, canvas model space
this.x = 0;
this.y = 0;
+ // width and height recalculated on mousemove events, canvas model space
this.width = 0;
this.height = 0;
@@ -12,9 +22,9 @@ class SelectionBox {
this.active = false;
}
- //
- // check to see if the user is currently drawing a selection box
- //
+ /**
+ * check to see if user is currently drawing a selection box
+ */
isActive() {
return this.active;
}
@@ -36,7 +46,11 @@ class SelectionBox {
this.active = true;
}
- deactivate() {
+ /**
+ * reset the selection box state
+ * @private
+ */
+ _deactivate() {
this.x = 0;
this.y = 0;
this.width = 0;
@@ -45,10 +59,10 @@ class SelectionBox {
}
- //
- // the user has let go of their mouse button, release the selectionBox
- // select the nodes and edges within the bounds of the selection box
- //
+ /**
+ * called when user release mouse button, completing their selection box
+ * selects the nodes and edges within the selection box
+ */
release() {
let boundingBox = this.getBoundingBox();
if (this.selectionHandler.options.selectionBox.edges) {
@@ -77,7 +91,7 @@ class SelectionBox {
}
}
}
- this.deactivate();
+ this._deactivate();
}
/**
@@ -88,7 +102,7 @@ class SelectionBox {
* NOTE: the order of the per-axis predicates is important here!
*
* @param {MouseEvent} ev
- *
+ * @returns {{x: number, y: number}}
*/
_consumeMouseEvent(ev) {
let frameRect = this.selectionHandler.canvas.frame.getBoundingClientRect();
@@ -178,6 +192,10 @@ class SelectionBox {
// get the current bounding box for the user's selection box
// returns {left, top, right, bottom} in canvas model space
//
+ /**
+ * get the current bounding box for the user's selection box
+ * @return {{left: number, top: number, right: number, bottom: number}}
+ */
getBoundingBox() {
let result = {
left: 0,
diff --git a/lib/network/modules/SelectionHandler.js b/lib/network/modules/SelectionHandler.js
index bd1f29adb..f58b9d9ab 100644
--- a/lib/network/modules/SelectionHandler.js
+++ b/lib/network/modules/SelectionHandler.js
@@ -213,13 +213,15 @@ class SelectionHandler {
return overlappingNodes;
}
- getAllNodesWithinBoundingBox(object) {
- return this._getAllNodesOverlappingWith(object);
+ /**
+ *
+ * @param {{left: number, top: number, right: number, bottom: number}} boundingBox
+ * @return {number[]} An array with node id's
+ */
+ getAllNodesWithinBoundingBox(boundingBox) {
+ return this._getAllNodesOverlappingWith(boundingBox);
}
-
-
-
/**
* Return a position object in canvasspace from a single point in screenspace
*
@@ -297,7 +299,7 @@ class SelectionHandler {
/**
* get edge ids of all edges within a bounding box (bounding box in canvas model space)
* @param {{left: number, top: number, right: number, bottom:number}} boundingBox
- * @return edgeId[]
+ * @return {any[]} Array with edge id's
*/
getAllEdgesWithinBoundingBox(boundingBox) {
let result = [];
diff --git a/lib/network/modules/components/Edge.js b/lib/network/modules/components/Edge.js
index 043583cc0..938e56062 100644
--- a/lib/network/modules/components/Edge.js
+++ b/lib/network/modules/components/Edge.js
@@ -52,6 +52,14 @@ class Edge {
this.setOptions(options);
}
+ /**
+ * Initially written to facilitate the selectionBox interaction option.
+ * checks N evenly spaced points across this edge bezier to see if any of them lay within the bounding box
+ *
+ * @param {{left: number, top: number, right: number, bottom: number}} boundingBox
+ * @param {number} points
+ * @return {boolean} whether the edge exists at least minimally within a given bounding box
+ */
SB_checkBoundingBox(boundingBox, points) {
let step = 1 / points;
let percentage = 0;
From 4b272e5852cd870399aebf02ed139f23252c82bf Mon Sep 17 00:00:00 2001
From: softwareCobbler
Date: Sat, 2 Feb 2019 21:50:16 -0500
Subject: [PATCH 07/10] fixing broken tests
---
lib/network/modules/InteractionHandler.js | 4 ++--
lib/network/modules/SelectionBox.js | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/lib/network/modules/InteractionHandler.js b/lib/network/modules/InteractionHandler.js
index ceca383d4..447dcc7cf 100644
--- a/lib/network/modules/InteractionHandler.js
+++ b/lib/network/modules/InteractionHandler.js
@@ -63,7 +63,7 @@ class InteractionHandler {
}
/**
- * Helper function to check if the selectionBox option is enabled
+ * @returns {boolean} if the selectionBox option is enabled
* @private
*/
_selectionBoxOption() {
@@ -119,7 +119,7 @@ class InteractionHandler {
/**
* Get the pointer location from a touch location
* @param {{x: number, y: number}} touch
- * @return {{x: number, y: number}} pointer
+ * @returns {{x: number, y: number}} pointer
* @private
*/
getPointer(touch) {
diff --git a/lib/network/modules/SelectionBox.js b/lib/network/modules/SelectionBox.js
index c7d35f238..85e1560d4 100644
--- a/lib/network/modules/SelectionBox.js
+++ b/lib/network/modules/SelectionBox.js
@@ -23,7 +23,7 @@ class SelectionBox {
}
/**
- * check to see if user is currently drawing a selection box
+ * @returns {boolean} if user is currently drawing a selection box
*/
isActive() {
return this.active;
From f2bec3e6a28bd0e8e48dbbfaf90c3f886dfeaa5e Mon Sep 17 00:00:00 2001
From: softwareCobbler
Date: Sat, 2 Feb 2019 22:24:56 -0500
Subject: [PATCH 08/10] fix failing test from ItemSet.js
---
lib/timeline/component/ItemSet.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js
index 485c5fe27..5e615b7af 100644
--- a/lib/timeline/component/ItemSet.js
+++ b/lib/timeline/component/ItemSet.js
@@ -541,6 +541,7 @@ ItemSet.prototype.show = function() {
/**
* Activates the popup timer to show the given popup after a fixed time.
+ * @param {any} popup
*/
ItemSet.prototype.setPopupTimer = function (popup) {
this.clearPopupTimer();
From e0e7acf1b2450508632f41216aa0c00a59797d10 Mon Sep 17 00:00:00 2001
From: softwareCobbler
Date: Wed, 6 Feb 2019 22:05:33 -0500
Subject: [PATCH 09/10] added lineWidth option
---
docs/network/interaction.html | 1 +
examples/network/other/selectionBox.html | 1 +
lib/network/modules/CanvasRenderer.js | 18 ++++--
lib/network/modules/SelectionBox.js | 72 ++++++++++++------------
lib/network/modules/SelectionHandler.js | 14 +++++
lib/network/options.js | 2 +
6 files changed, 65 insertions(+), 43 deletions(-)
diff --git a/docs/network/interaction.html b/docs/network/interaction.html
index fe7f768e4..0c8ba638a 100644
--- a/docs/network/interaction.html
+++ b/docs/network/interaction.html
@@ -122,6 +122,7 @@
Options
selectionBox.edges
Boolean
false
Whether the selection box will select edges when the user releases the box.
selectionBox.strokeStyle
String
rgb(0,0,0)
The style of the selection box's border. Any valid color,gradient or pattern will work (see MDN: CanvasRenderingContext2D.strokeStyle), but there is no error checking for this value!
selectionBox.fillStyle
String
rgba(0,0,0,.0625)
The style of the selection box's fill. Any valid color,gradient or pattern will work (see MDN: CanvasRenderingContext2D.fillStyle), but there is no error checking for this value!
+
selectionBox.lineWidth
Number (integer) >= 0
2
Width of the border line for the selection box, roughly in pixels (some aliasing occurs due to canvas scaling). When setting this value, Vis.js rounds it down to the nearest integer (i.e., 3.2 becomes 3), and forces it to 0 if assigned a negative value. A zero value results in no border being drawn.
selectionBox.edgeAccuracy
Number
25
"Accuracy" of the edge detection. Edges in Vis.js are Bezier curves, so we plot points along an edge and check if a point is within our selection box. The number of points per edge used during this process is specified with this value.
navigationButtons
Boolean
false
When true, navigation buttons are drawn on the network canvas. These are HTML buttons and can be completely customized using CSS.
"strokeStyle": An [RGB|RGBA] string to set the color of the box's border
"fillStyle": An RGBA string to set the color of the box's fill (you probably want this at a low opacity)
+
"lineWidth": Width of line, roughly in pixels (some aliasing occurs due to canvas scaling).
"edgeAccuracy": The number of points generated on each edge that are used to check if the edge is contaned within the box
The default is 25, which means for every edge, we calculate at most 25 evenly distributed points across each line.
diff --git a/lib/network/modules/CanvasRenderer.js b/lib/network/modules/CanvasRenderer.js
index bed0f79b1..75e6d1bd7 100644
--- a/lib/network/modules/CanvasRenderer.js
+++ b/lib/network/modules/CanvasRenderer.js
@@ -301,16 +301,22 @@ class CanvasRenderer {
*/
_drawSelectionBox(ctx) {
ctx.save();
+ ctx.beginPath();
{
- ctx.beginPath();
+ ctx.lineWidth = this.selectionBox.options.lineWidth / this.body.view.scale;
ctx.strokeStyle = this.selectionBox.options.strokeStyle;
ctx.fillStyle = this.selectionBox.options.fillStyle;
-
- ctx.rect(this.selectionBox.x, this.selectionBox.y, this.selectionBox.width, this.selectionBox.height);
- ctx.stroke();
- ctx.fillRect(this.selectionBox.x + 1, this.selectionBox.y + 1, this.selectionBox.width - 2, this.selectionBox.height - 2);
- ctx.closePath();
+
+ // draw the fill rect first, then the border ON TOP of it
+ ctx.fillRect(this.selectionBox.x, this.selectionBox.y, this.selectionBox.width, this.selectionBox.height);
+
+ // CanvasRenderingContext2d.lineWidth ignores 0 values, so if the user has set the width to 0, just don't draw the border
+ if (this.selectionBox.options.lineWidth >= 1) {
+ ctx.rect(this.selectionBox.x, this.selectionBox.y, this.selectionBox.width, this.selectionBox.height);
+ ctx.stroke();
+ }
}
+ ctx.closePath();
ctx.restore();
}
diff --git a/lib/network/modules/SelectionBox.js b/lib/network/modules/SelectionBox.js
index 85e1560d4..aeeabba5c 100644
--- a/lib/network/modules/SelectionBox.js
+++ b/lib/network/modules/SelectionBox.js
@@ -95,11 +95,13 @@ class SelectionBox {
}
/**
- * Calculate new box corner {x,y} based on mouse input
+ * Calculate new box corner {x,y} based on mouse input, taking into account the width of the line in screen pixels
* returning values in canvas space
* this is easy when the user remains in the canvas
* but needs a bit of extra logic to handle a mouse movement that extends outside of the canvas
- * NOTE: the order of the per-axis predicates is important here!
+ * NOTE' : adjusting for the user's line width may leave out some nodes/edges between the canvas border and (linewWidth / 2) pixels from the border!
+ * if this is an issue it can surely be fixed
+ * NOTE'': the order of the per-axis predicates is important here!
*
* @param {MouseEvent} ev
* @returns {{x: number, y: number}}
@@ -112,35 +114,35 @@ class SelectionBox {
// X-Axis
//
- // mouse to the left of viewport
- if (ev.clientX < 0) {
- // is top of frame also above viewport?
+ // mouse to the left of browser viewport
+ if (ev.clientX - (this.options.lineWidth / 2) < 0) {
+ // is left of frame also to left of viewport?
if (frameRect.left < 0) {
- x = (-frameRect.left) + 1;
+ x = (-frameRect.left) + Math.ceil(this.options.lineWidth / 2);
}
else {
- x = 1;
+ x = Math.ceil(this.options.lineWidth / 2);
}
}
- // mouse to the left of frame
- else if (ev.clientX < frameRect.left) {
- x = 1;
+ // mouse to the left of canvas
+ else if (ev.clientX - (this.options.lineWidth / 2) < frameRect.left) {
+ x = Math.ceil(this.options.lineWidth / 2);
}
- // mouse to the right of viewport
- else if (ev.clientX > window.innerWidth) {
+ // mouse to the right of browser viewport
+ else if (ev.clientX + (this.options.lineWidth / 2) > window.innerWidth) {
// is right of frame also beyond viewport?
if (frameRect.right > window.innerWidth) {
- x = window.innerWidth - frameRect.left - 1;
+ x = window.innerWidth - frameRect.left - Math.ceil(this.options.lineWidth / 2);
}
else {
- x = frameRect.right - frameRect.left - 1;
+ x = frameRect.right - frameRect.left - Math.ceil(this.options.lineWidth / 2);
}
}
- // mouse to the right of frame
- else if (ev.clientX > frameRect.right) {
- x = frameRect.right - frameRect.left - 1;
+ // mouse to the right of canvas
+ else if (ev.clientX + (this.options.lineWidth / 2) > frameRect.right) {
+ x = frameRect.right - frameRect.left - Math.ceil(this.options.lineWidth / 2);
}
- // mouse horizontally within frame
+ // mouse horizontally within canvas
else {
x = ev.clientX - frameRect.x;
}
@@ -149,35 +151,35 @@ class SelectionBox {
// Y-Axis
//
- // mouse above viewport
- if (ev.clientY < 0) {
+ // mouse above browser viewport
+ if (ev.clientY - (this.options.lineWidth / 2) < 0) {
// is top of frame also above viewport?
if (frameRect.top < 0) {
- y = (-frameRect.top) + 1;
+ y = (-frameRect.top) + Math.ceil(this.options.lineWidth / 2);
}
else {
- y = 1;
+ y = Math.ceil(this.options.lineWidth / 2);
}
}
- // mouse above frame
- else if (ev.clientY < frameRect.top) {
- y = 1;
+ // mouse above canvas
+ else if (ev.clientY - (this.options.lineWidth / 2) < frameRect.top) {
+ y = Math.ceil(this.options.lineWidth / 2);
}
- // mouse below viewport
- else if (ev.clientY > window.innerHeight) {
+ // mouse below browser viewport
+ else if (ev.clientY + (this.options.lineWidth / 2) > window.innerHeight) {
// is bottom of frame also below viewport?
if (frameRect.bottom > window.innerHeight) {
- y = window.innerHeight - frameRect.top - 1;
+ y = window.innerHeight - frameRect.top - Math.ceil(this.options.lineWidth / 2);
}
else {
- y = frameRect.bottom - frameRect.top - 1;
+ y = frameRect.bottom - frameRect.top - Math.ceil(this.options.lineWidth / 2);
}
}
- // mouse below frame
- else if (ev.clientY > frameRect.bottom) {
- y = frameRect.bottom - frameRect.top - 1;
+ // mouse below canvas
+ else if (ev.clientY + (this.options.lineWidth / 2) > frameRect.bottom) {
+ y = frameRect.bottom - frameRect.top - Math.ceil(this.options.lineWidth / 2);
}
- // mouse vertically within frame
+ // mouse vertically within canvas
else {
y = ev.clientY - frameRect.y;
}
@@ -188,10 +190,6 @@ class SelectionBox {
});
}
- //
- // get the current bounding box for the user's selection box
- // returns {left, top, right, bottom} in canvas model space
- //
/**
* get the current bounding box for the user's selection box
* @return {{left: number, top: number, right: number, bottom: number}}
diff --git a/lib/network/modules/SelectionHandler.js b/lib/network/modules/SelectionHandler.js
index f58b9d9ab..e9703ac10 100644
--- a/lib/network/modules/SelectionHandler.js
+++ b/lib/network/modules/SelectionHandler.js
@@ -29,6 +29,7 @@ class SelectionHandler {
edges: false,
strokeStyle: "rgb(0,0,0)",
fillStyle: "rgba(0,0,0,.0625)",
+ lineWidth: 2,
edgeAccuracy: 25
}
};
@@ -50,6 +51,19 @@ class SelectionHandler {
let fields = ['multiselect','hoverConnectedEdges','selectable','selectConnectedEdges'];
util.selectiveDeepExtend(fields,this.options, options);
+ //
+ // if selectionBox.lineWidth is in the passed-in options, perform the following assignment:
+ // selectionBox.lineWidth = max(0, floor(selectionBox.lineWidth))
+ //
+ if (options.selectionBox) {
+ if (options.selectionBox.lineWidth) {
+ options.selectionBox.lineWidth = Math.floor(options.selectionBox.lineWidth);
+ if (options.selectionBox.lineWidth < 0) {
+ options.selectionBox.lineWidth = 0;
+ }
+ }
+ }
+
util.mergeOptions(this.options, options, "selectionBox");
}
}
diff --git a/lib/network/options.js b/lib/network/options.js
index 5740d5a62..d86108106 100644
--- a/lib/network/options.js
+++ b/lib/network/options.js
@@ -170,6 +170,7 @@ let allOptions = {
edges: { boolean: bool },
strokeStyle: { string },
fillStyle: { string },
+ lineWidth: { number },
edgeAccuracy: { number },
__type__: { object, boolean: bool }
},
@@ -586,6 +587,7 @@ let configureOptions = {
edges: false,
strokeStyle: "rgb(0,0,0)",
fillStyle: "rgba(0,0,0,.0625)",
+ lineWidth: [2, 0, 8, 1],
edgeAccuracy: [25, 1, 100, 1]
},
hoverConnectedEdges: true,
From 99a1b5e4b99ae85855f5d2db719a5cd3788542f0 Mon Sep 17 00:00:00 2001
From: softwareCobbler
Date: Mon, 18 Feb 2019 18:15:45 -0500
Subject: [PATCH 10/10] Revert "fix failing test from ItemSet.js"
This reverts commit f2bec3e6a28bd0e8e48dbbfaf90c3f886dfeaa5e (ref
almende/vis PR #4256
---
lib/timeline/component/ItemSet.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js
index 5e615b7af..485c5fe27 100644
--- a/lib/timeline/component/ItemSet.js
+++ b/lib/timeline/component/ItemSet.js
@@ -541,7 +541,6 @@ ItemSet.prototype.show = function() {
/**
* Activates the popup timer to show the given popup after a fixed time.
- * @param {any} popup
*/
ItemSet.prototype.setPopupTimer = function (popup) {
this.clearPopupTimer();