diff --git a/docs/network/interaction.html b/docs/network/interaction.html index ad3deb159..0c8ba638a 100644 --- a/docs/network/interaction.html +++ b/docs/network/interaction.html @@ -66,6 +66,14 @@

Options

bindToWindow: true }, multiselect: false, + selectionBox: { + enabled: false, + nodes: true, + edges: false, + strokeStyle: "rgb(0,0,0)", + fillStyle: "rgba(0,0,0,.0625)", + edgeAccuracy: 25 + } navigationButtons: false, selectable: true, selectConnectedEdges: true, @@ -98,13 +106,25 @@

Options

hideNodesOnDrag Boolean false 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.enabledBoolean false Toggle the usage of the selectionBox shortcuts + selectionBox.nodesBoolean true Whether the selection box will select nodes when the user releases the box. + selectionBox.edgesBoolean false Whether the selection box will select edges when the user releases the box. + selectionBox.strokeStyleString 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.fillStyleString 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.lineWidthNumber (integer) >= 0 2Width 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.edgeAccuracyNumber 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 BooleantrueWhen true, the nodes and edges can be selected by the user. selectConnectedEdges BooleantrueWhen true, on selecting a node, its connecting edges are highlighted. diff --git a/examples/network/other/selectionBox.html b/examples/network/other/selectionBox.html new file mode 100644 index 000000000..e44363546 --- /dev/null +++ b/examples/network/other/selectionBox.html @@ -0,0 +1,98 @@ + + + + Network | selectionBox option + + + + + + + + + + + + + + +

+ 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: +

+

+
+
+ +

+ + diff --git a/lib/network/Network.js b/lib/network/Network.js index f0f3a240c..905daf244 100644 --- a/lib/network/Network.js +++ b/lib/network/Network.js @@ -27,6 +27,7 @@ var {printStyle} = require('./../shared/Validator'); var {allOptions, configureOptions} = require('./options.js'); var KamadaKawai = require("./modules/KamadaKawai.js").default; +var SelectionBox = require('./modules/SelectionBox').default; /** * Create a network visualization, displaying nodes and edges. @@ -120,9 +121,10 @@ function Network(container, data, options) { this.groups = new Groups(); // object with groups this.canvas = new Canvas(this.body); // DOM handler this.selectionHandler = new SelectionHandler(this.body, this.canvas); // Selection handler - this.interactionHandler = new InteractionHandler(this.body, this.canvas, this.selectionHandler); // Interaction handler handles all the hammer bindings (that are bound by canvas), key + this.selectionBox = new SelectionBox(this.body, this.selectionHandler); + this.interactionHandler = new InteractionHandler(this.body, this.canvas, this.selectionHandler, this.selectionBox); // Interaction handler handles all the hammer bindings (that are bound by canvas), key this.view = new View(this.body, this.canvas); // camera handler, does animations and zooms - this.renderer = new CanvasRenderer(this.body, this.canvas); // renderer, starts renderloop, has events that modules can hook into + this.renderer = new CanvasRenderer(this.body, this.canvas, this.selectionBox); // renderer, starts renderloop, has events that modules can hook into this.physics = new PhysicsEngine(this.body); // physics engine, does all the simulations this.layoutEngine = new LayoutEngine(this.body); // layout engine for inital layout and hierarchical layout this.clustering = new ClusterEngine(this.body); // clustering api diff --git a/lib/network/modules/CanvasRenderer.js b/lib/network/modules/CanvasRenderer.js index 56b9e5183..75e6d1bd7 100644 --- a/lib/network/modules/CanvasRenderer.js +++ b/lib/network/modules/CanvasRenderer.js @@ -50,11 +50,13 @@ class CanvasRenderer { /** * @param {Object} body * @param {Canvas} canvas + * @param {SelectionBox} selectionBox */ - constructor(body, canvas) { + constructor(body, canvas, selectionBox) { _initRequestAnimationFrame(); this.body = body; this.canvas = canvas; + this.selectionBox = selectionBox; this.redrawRequested = false; this.renderTimer = undefined; @@ -137,7 +139,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 @@ -273,6 +275,12 @@ class CanvasRenderer { this._drawNodes(ctx, hidden); } + + // if it should draw selection box + if (this.selectionBox.isActive()) { + this._drawSelectionBox(ctx); + } + ctx.beginPath(); this.body.emitter.emit("afterDrawing", ctx); ctx.closePath(); @@ -286,6 +294,31 @@ class CanvasRenderer { } } + /** + * Draws the selectionBox + * @param {CanvasRenderingContext2D} ctx + * @private + */ + _drawSelectionBox(ctx) { + ctx.save(); + ctx.beginPath(); + { + ctx.lineWidth = this.selectionBox.options.lineWidth / this.body.view.scale; + ctx.strokeStyle = this.selectionBox.options.strokeStyle; + ctx.fillStyle = this.selectionBox.options.fillStyle; + + // 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(); + } /** * Redraw all nodes diff --git a/lib/network/modules/InteractionHandler.js b/lib/network/modules/InteractionHandler.js index cbdc49013..447dcc7cf 100644 --- a/lib/network/modules/InteractionHandler.js +++ b/lib/network/modules/InteractionHandler.js @@ -2,7 +2,6 @@ let util = require('../../util'); var NavigationHandler = require('./components/NavigationHandler').default; var Popup = require('./../../shared/Popup').default; - /** * Handler for interactions */ @@ -11,12 +10,14 @@ class InteractionHandler { * @param {Object} body * @param {Canvas} canvas * @param {SelectionHandler} selectionHandler + * @param {SelectionBox} selectionBox */ - constructor(body, canvas, selectionHandler) { + constructor(body, canvas, selectionHandler, selectionBox) { this.body = body; this.canvas = canvas; this.selectionHandler = selectionHandler; this.navigationHandler = new NavigationHandler(body,canvas); + this.selectionBox = selectionBox; // bind the events from hammer to functions in this object this.body.eventListeners.onTap = this.onTap.bind(this); @@ -61,6 +62,14 @@ class InteractionHandler { this.bindEventListeners() } + /** + * @returns {boolean} if the selectionBox option is enabled + * @private + */ + _selectionBoxOption() { + return this.selectionHandler.options.selectionBox.enabled; + } + /** * Binds event listeners */ @@ -78,7 +87,19 @@ class InteractionHandler { setOptions(options) { if (options !== undefined) { // extend all but the values in fields - let fields = ['hideEdgesOnDrag','hideNodesOnDrag','keyboard','multiselect','selectable','selectConnectedEdges']; + // (there is an overlap between the options "interaction" subobject, with options there taken by three seperate components: + // CanvasRenderer, InteractionHandler and SelectionHandler + // it might be good at some point to break the options and program logic up + // so there is a more direct mapping between the structure of the options in options.js and Network's program logic + let fields = [ + 'hideEdgesOnDrag' /* see CanvasRenderer */, + 'hideNodesOnDrag' /* see CanvasRenderer */, + 'keyboard' /* nested object extended below */, + 'multiselect' /* see SelectionHandler */, + 'selectable' /* see SelectionHandler */, + 'selectConnectedEdges' /* see SelectionHandler */, + 'selectionBox' /* see SelectionHandler */ + ]; util.selectiveNotDeepExtend(fields, this.options, options); // merge the keyboard options in. @@ -95,11 +116,10 @@ class InteractionHandler { this.navigationHandler.setOptions(this.options); } - /** * 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) { @@ -109,17 +129,24 @@ class InteractionHandler { }; } - /** * On start of a touch gesture, store the pointer + * note for selectionBox functionality -- this Hammer event consumes the "mousedown" DOM event, so we deal with it here * @param {Event} event The event * @private */ onTouch(event) { if (new Date().valueOf() - this.touchTime > 50) { + if (this._selectionBoxOption()) { + if (event.srcEvent.ctrlKey) { + this.selectionBox.activate(event.srcEvent); + } + } + this.drag.pointer = this.getPointer(event.center); this.drag.pinched = false; this.pinch.scale = this.body.view.scale; + // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame) this.touchTime = new Date().valueOf(); } @@ -168,6 +195,7 @@ class InteractionHandler { } + /** * handle the release of the screen * @@ -176,6 +204,9 @@ class InteractionHandler { */ onRelease(event) { if (new Date().valueOf() - this.touchTime > 10) { + if (this.selectionBox.isActive()) { + this.selectionBox.release(); + } let pointer = this.getPointer(event.center); this.selectionHandler._generateClickEvent('release', event, pointer); // to avoid double fireing of this event because we have two hammer instances. (on canvas and on frame) @@ -351,6 +382,19 @@ class InteractionHandler { return; } + if (this.selectionBox.isActive()) { + /*console.log(event.srcEvent.offsetX, ",", event.srcEvent.offsetY); + console.log(event.srcEvent); + let p = this.canvas.DOMtoCanvas({ + x: event.srcEvent.offsetX, + y: event.srcEvent.offsetY + });*/ + + this.selectionBox.updateBoundingBox(event.srcEvent) + + return; + } + // remove the focus on node if it is focussed on by the focusOnNode this.body.emitter.emit('unlockNode'); diff --git a/lib/network/modules/SelectionBox.js b/lib/network/modules/SelectionBox.js new file mode 100644 index 000000000..aeeabba5c --- /dev/null +++ b/lib/network/modules/SelectionBox.js @@ -0,0 +1,245 @@ +/** + * 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; + + this.options = this.selectionHandler.options.selectionBox; + this.active = false; + } + + /** + * @returns {boolean} if user is currently drawing a selection box + */ + isActive() { + return this.active; + } + + /** + * 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; + this.height = 0; + this.active = true; + } + + /** + * reset the selection box state + * @private + */ + _deactivate() { + this.x = 0; + this.y = 0; + this.width = 0; + this.height = 0; + this.active = false; + } + + + /** + * 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) { + // run the edges first, so that if "this.selectionHandler.options.selectConnectedEdges" is set, + // we don't _unset_ edges that _get_ set by our node selection logic + let selectedEdgeIds = this.selectionHandler.getAllEdgesWithinBoundingBox(boundingBox); + for (let edgeId of selectedEdgeIds) { + let edge = this.body.edges[edgeId]; + if (edge.isSelected()) { + this.selectionHandler.deselectObject(edge); + } + else { + this.selectionHandler.selectObject(edge); + } + } + } + if (this.selectionHandler.options.selectionBox.nodes) { + let selectedNodeIds = this.selectionHandler.getAllNodesWithinBoundingBox(boundingBox); + for (let nodeId of selectedNodeIds) { + let node = this.body.nodes[nodeId]; + if (node.isSelected()) { + this.selectionHandler.deselectObject(node); + } + else { + this.selectionHandler.selectObject(node); + } + } + } + this._deactivate(); + } + + /** + * 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' : 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}} + */ + _consumeMouseEvent(ev) { + let frameRect = this.selectionHandler.canvas.frame.getBoundingClientRect(); + let x, y; + + // + // X-Axis + // + + // 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) + Math.ceil(this.options.lineWidth / 2); + } + else { + x = Math.ceil(this.options.lineWidth / 2); + } + } + // 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 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 - Math.ceil(this.options.lineWidth / 2); + } + else { + x = frameRect.right - frameRect.left - Math.ceil(this.options.lineWidth / 2); + } + } + // 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 canvas + else { + x = ev.clientX - frameRect.x; + } + + // + // Y-Axis + // + + // 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) + Math.ceil(this.options.lineWidth / 2); + } + else { + y = Math.ceil(this.options.lineWidth / 2); + } + } + // mouse above canvas + else if (ev.clientY - (this.options.lineWidth / 2) < frameRect.top) { + y = Math.ceil(this.options.lineWidth / 2); + } + // 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 - Math.ceil(this.options.lineWidth / 2); + } + else { + y = frameRect.bottom - frameRect.top - Math.ceil(this.options.lineWidth / 2); + } + } + // 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 canvas + 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 + * @return {{left: number, top: number, right: number, bottom: number}} + */ + getBoundingBox() { + let result = { + left: 0, + top: 0, + right: 0, + bottom: 0 + } + + if (this.width < 0) { + result.left = this.x + this.width; + result.right = this.x; + } + else { + result.left = this.x; + result.right = this.x + this.width; + } + + if (this.height < 0) { + result.top = this.y + this.height; + result.bottom = this.y; + } + else { + result.top = this.y; + result.bottom = this.y + this.height; + } + + return result; + } + + /** + * 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 53c617041..e9703ac10 100644 --- a/lib/network/modules/SelectionHandler.js +++ b/lib/network/modules/SelectionHandler.js @@ -22,8 +22,18 @@ class SelectionHandler { multiselect: false, selectable: true, selectConnectedEdges: true, - hoverConnectedEdges: true + hoverConnectedEdges: true, + selectionBox: { + enabled: false, + nodes: true, + edges: false, + strokeStyle: "rgb(0,0,0)", + fillStyle: "rgba(0,0,0,.0625)", + lineWidth: 2, + edgeAccuracy: 25 + } }; + util.extend(this.options, this.defaultOptions); this.body.emitter.on("_dataChanged", () => { @@ -40,6 +50,21 @@ class SelectionHandler { if (options !== undefined) { 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"); } } @@ -202,6 +227,14 @@ class SelectionHandler { return overlappingNodes; } + /** + * + * @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 @@ -277,6 +310,22 @@ class SelectionHandler { return overlappingEdges; } + /** + * 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 {any[]} Array with edge id's + */ + 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(boundingBox, this.options.selectionBox.edgeAccuracy)) { + result.push(edgeId); + } + } + return result; + } + /** * Get the edges nearest to the passed point (like a click) @@ -828,7 +877,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 2b3d2046c..938e56062 100644 --- a/lib/network/modules/components/Edge.js +++ b/lib/network/modules/components/Edge.js @@ -52,6 +52,30 @@ 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; + for (let i = 0; i < points; i++) { + if (i == points - 1) { + percentage = 1; + } + let p = this.edgeType.getPoint(percentage); + if (util.pointWithinBoundingBox(p, boundingBox)) { + return true; + } + percentage += step; + } + return false; + } + /** * Set or overwrite options for the edge @@ -674,12 +698,11 @@ class Edge { return (dist < distMax); } else { - return false + 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/modules/components/NavigationHandler.js b/lib/network/modules/components/NavigationHandler.js index 73bec0b43..7672d340a 100644 --- a/lib/network/modules/components/NavigationHandler.js +++ b/lib/network/modules/components/NavigationHandler.js @@ -20,7 +20,6 @@ class NavigationHandler { this.touchTime = 0; this.activated = false; - this.body.emitter.on("activate", () => {this.activated = true; this.configureKeyboardBindings();}); this.body.emitter.on("deactivate", () => {this.activated = false; this.configureKeyboardBindings();}); this.body.emitter.on("destroy", () => {if (this.keycharm !== undefined) {this.keycharm.destroy();}}); diff --git a/lib/network/options.js b/lib/network/options.js index ff614a5df..d86108106 100644 --- a/lib/network/options.js +++ b/lib/network/options.js @@ -164,6 +164,16 @@ let allOptions = { navigationButtons: { boolean: bool }, selectable: { boolean: bool }, selectConnectedEdges: { boolean: bool }, + selectionBox: { + enabled: { boolean: bool }, + nodes: { boolean: bool }, + edges: { boolean: bool }, + strokeStyle: { string }, + fillStyle: { string }, + lineWidth: { number }, + edgeAccuracy: { number }, + __type__: { object, boolean: bool } + }, hoverConnectedEdges: { boolean: bool }, tooltipDelay: { number }, zoomView: { boolean: bool }, @@ -571,6 +581,15 @@ let configureOptions = { navigationButtons: false, selectable: true, selectConnectedEdges: true, + selectionBox: { + enabled: false, + nodes: true, + 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, tooltipDelay: [300, 0, 1000, 25], zoomView: true, diff --git a/lib/util.js b/lib/util.js index 41549de0f..080acfed8 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1313,7 +1313,7 @@ exports.mergeOptions = function (mergeTarget, options, option, globalOptions = { var globalOption = globalPassed? globalOptions[option]: undefined; var globalEnabled = globalOption? globalOption.enabled: undefined; - + ///////////////////////////////////////// // Main routine ///////////////////////////////////////// @@ -1566,3 +1566,13 @@ exports.topMost = function (pile, accessors) { } return candidate; }; + +exports.pointWithinBoundingBox = function(point, boundingBox) { + if (point.x >= boundingBox.left && point.x <= boundingBox.right && + point.y >= boundingBox.top && point.y <= boundingBox.bottom) { + return true; + } + else { + return false; + } +} \ No newline at end of file