/** * Created by Alex on 2/27/2015. */ var Node = require("../../Node"); class SelectionHandler { constructor(body) { this.body = body; this.selectionObj = {nodes:[], edges:[]}; this.options = { select: true, selectConnectedEdges: true } } setCanvas(canvas) { this.canvas = canvas; } /** * handles the selection part of the tap; * * @param {Object} pointer * @private */ selectOnPoint(pointer) { if (this.options.select === true) { if (this._getSelectedObjectCount() > 0) {this._unselectAll();} this.selectObject(pointer); this._generateClickEvent(pointer); this.body.emitter.emit("_requestRedraw"); } } _generateClickEvent(pointer) { var properties = this.getSelection(); properties['pointer'] = { DOM: {x: pointer.x, y: pointer.y}, canvas: this.canvas.DOMtoCanvas(pointer) } this.body.emitter.emit("click", properties); } selectObject(pointer) { var obj = this._getNodeAt(pointer); if (obj != null) { if (this.options.selectConnectedEdges === true) { this._selectConnectedEdges(obj); } } else { obj = this._getEdgeAt(pointer); } if (obj !== null) { obj.select(); this._addToSelection(obj); this.body.emitter.emit('selected', this.getSelection()) } return obj; } /** * * @param object * @param overlappingNodes * @private */ _getNodesOverlappingWith(object, overlappingNodes) { var nodes = this.body.nodes; for (var nodeId in nodes) { if (nodes.hasOwnProperty(nodeId)) { if (nodes[nodeId].isOverlappingWith(object)) { overlappingNodes.push(nodeId); } } } } /** * retrieve all nodes overlapping with given object * @param {Object} object An object with parameters left, top, right, bottom * @return {Number[]} An array with id's of the overlapping nodes * @private */ _getAllNodesOverlappingWith(object) { var overlappingNodes = []; this._getNodesOverlappingWith(object,overlappingNodes); return overlappingNodes; } /** * Return a position object in canvasspace from a single point in screenspace * * @param pointer * @returns {{left: number, top: number, right: number, bottom: number}} * @private */ _pointerToPositionObject(pointer) { var canvasPos = this.canvas.DOMtoCanvas(pointer); return { left: canvasPos.x, top: canvasPos.y, right: canvasPos.x, bottom: canvasPos.y }; } /** * Get the top node at the a specific point (like a click) * * @param {{x: Number, y: Number}} pointer * @return {Node | null} node * @private */ _getNodeAt(pointer) { // we first check if this is an navigation controls element var positionObject = this._pointerToPositionObject(pointer); var overlappingNodes = this._getAllNodesOverlappingWith(positionObject); // if there are overlapping nodes, select the last one, this is the // one which is drawn on top of the others if (overlappingNodes.length > 0) { return this.body.nodes[overlappingNodes[overlappingNodes.length - 1]]; } else { return null; } } /** * retrieve all edges overlapping with given object, selector is around center * @param {Object} object An object with parameters left, top, right, bottom * @return {Number[]} An array with id's of the overlapping nodes * @private */ _getEdgesOverlappingWith(object, overlappingEdges) { var edges = this.body.edges; for (var edgeId in edges) { if (edges.hasOwnProperty(edgeId)) { if (edges[edgeId].isOverlappingWith(object)) { overlappingEdges.push(edgeId); } } } } /** * retrieve all nodes overlapping with given object * @param {Object} object An object with parameters left, top, right, bottom * @return {Number[]} An array with id's of the overlapping nodes * @private */ _getAllEdgesOverlappingWith(object) { var overlappingEdges = []; this._getEdgesOverlappingWith(object,overlappingEdges); return overlappingEdges; } /** * Place holder. To implement change the _getNodeAt to a _getObjectAt. Have the _getObjectAt call * _getNodeAt and _getEdgesAt, then priortize the selection to user preferences. * * @param pointer * @returns {null} * @private */ _getEdgeAt(pointer) { var positionObject = this._pointerToPositionObject(pointer); var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject); if (overlappingEdges.length > 0) { return this.body.edges[overlappingEdges[overlappingEdges.length - 1]]; } else { return null; } } /** * Add object to the selection array. * * @param obj * @private */ _addToSelection(obj) { if (obj instanceof Node) { this.selectionObj.nodes[obj.id] = obj; } else { this.selectionObj.edges[obj.id] = obj; } } /** * Add object to the selection array. * * @param obj * @private */ _addToHover(obj) { if (obj instanceof Node) { this.hoverObj.nodes[obj.id] = obj; } else { this.hoverObj.edges[obj.id] = obj; } } /** * Remove a single option from selection. * * @param {Object} obj * @private */ _removeFromSelection(obj) { if (obj instanceof Node) { delete this.selectionObj.nodes[obj.id]; } else { delete this.selectionObj.edges[obj.id]; } } /** * Unselect all. The selectionObj is useful for this. * * @param {Boolean} [doNotTrigger] | ignore trigger * @private */ _unselectAll(doNotTrigger = false) { for(var nodeId in this.selectionObj.nodes) { if(this.selectionObj.nodes.hasOwnProperty(nodeId)) { this.selectionObj.nodes[nodeId].unselect(); } } for(var edgeId in this.selectionObj.edges) { if(this.selectionObj.edges.hasOwnProperty(edgeId)) { this.selectionObj.edges[edgeId].unselect(); } } this.selectionObj = {nodes:{},edges:{}}; if (doNotTrigger == false) { this.body.emitter.emit('select', this.getSelection()); } } /** * return the number of selected nodes * * @returns {number} * @private */ _getSelectedNodeCount() { var count = 0; for (var nodeId in this.selectionObj.nodes) { if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { count += 1; } } return count; } /** * return the selected node * * @returns {number} * @private */ _getSelectedNode() { for (var nodeId in this.selectionObj.nodes) { if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { return this.selectionObj.nodes[nodeId]; } } return null; } /** * return the selected edge * * @returns {number} * @private */ _getSelectedEdge() { for (var edgeId in this.selectionObj.edges) { if (this.selectionObj.edges.hasOwnProperty(edgeId)) { return this.selectionObj.edges[edgeId]; } } return null; } /** * return the number of selected edges * * @returns {number} * @private */ _getSelectedEdgeCount() { var count = 0; for (var edgeId in this.selectionObj.edges) { if (this.selectionObj.edges.hasOwnProperty(edgeId)) { count += 1; } } return count; } /** * return the number of selected objects. * * @returns {number} * @private */ _getSelectedObjectCount() { var count = 0; for(var nodeId in this.selectionObj.nodes) { if(this.selectionObj.nodes.hasOwnProperty(nodeId)) { count += 1; } } for(var edgeId in this.selectionObj.edges) { if(this.selectionObj.edges.hasOwnProperty(edgeId)) { count += 1; } } return count; } /** * Check if anything is selected * * @returns {boolean} * @private */ _selectionIsEmpty() { for(var nodeId in this.selectionObj.nodes) { if(this.selectionObj.nodes.hasOwnProperty(nodeId)) { return false; } } for(var edgeId in this.selectionObj.edges) { if(this.selectionObj.edges.hasOwnProperty(edgeId)) { return false; } } return true; } /** * check if one of the selected nodes is a cluster. * * @returns {boolean} * @private */ _clusterInSelection() { for(var nodeId in this.selectionObj.nodes) { if(this.selectionObj.nodes.hasOwnProperty(nodeId)) { if (this.selectionObj.nodes[nodeId].clusterSize > 1) { return true; } } } return false; } /** * select the edges connected to the node that is being selected * * @param {Node} node * @private */ _selectConnectedEdges(node) { for (var i = 0; i < node.edges.length; i++) { var edge = node.edges[i]; edge.select(); this._addToSelection(edge); } } /** * select the edges connected to the node that is being selected * * @param {Node} node * @private */ _hoverConnectedEdges(node) { for (var i = 0; i < node.edges.length; i++) { var edge = node.edges[i]; edge.hover = true; this._addToHover(edge); } } /** * unselect the edges connected to the node that is being selected * * @param {Node} node * @private */ _unselectConnectedEdges(node) { for (var i = 0; i < node.edges.length; i++) { var edge = node.edges[i]; edge.unselect(); this._removeFromSelection(edge); } } /** * This is called when someone clicks on a node. either select or deselect it. * If there is an existing selection and we don't want to append to it, clear the existing selection * * @param {Node || Edge} object * @private */ _blurObject(object) { if (object.hover == true) { object.hover = false; this.body.emitter.emit("blurNode",{node:object.id}); } } /** * This is called when someone clicks on a node. either select or deselect it. * If there is an existing selection and we don't want to append to it, clear the existing selection * * @param {Node || Edge} object * @private */ _hoverObject(object) { if (object.hover == false) { object.hover = true; this._addToHover(object); if (object instanceof Node) { this.body.emitter.emit("hoverNode",{node:object.id}); } } if (object instanceof Node) { this._hoverConnectedEdges(object); } } /** * handles the selection part of the double tap and opens a cluster if needed * * @param {Object} pointer * @private */ _handleDoubleTap(pointer) { var node = this._getNodeAt(pointer); if (node != null && node !== undefined) { // we reset the areaCenter here so the opening of the node will occur this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x), "y" : this._YconvertDOMtoCanvas(pointer.y)}; this.openCluster(node); } var properties = this.getSelection(); properties['pointer'] = { DOM: {x: pointer.x, y: pointer.y}, canvas: {x: this._XconvertDOMtoCanvas(pointer.x), y: this._YconvertDOMtoCanvas(pointer.y)} } this.body.emitter.emit("doubleClick", properties); } /** * Handle the onHold selection part * * @param pointer * @private */ _handleOnHold(pointer) { var node = this._getNodeAt(pointer); if (node != null) { this._selectObject(node,true); } else { var edge = this._getEdgeAt(pointer); if (edge != null) { this._selectObject(edge,true); } } this._requestRedraw(); } /** * * retrieve the currently selected objects * @return {{nodes: Array., edges: Array.}} selection */ getSelection() { var nodeIds = this.getSelectedNodes(); var edgeIds = this.getSelectedEdges(); return {nodes:nodeIds, edges:edgeIds}; } /** * * retrieve the currently selected nodes * @return {String[]} selection An array with the ids of the * selected nodes. */ getSelectedNodes() { var idArray = []; if (this.options.select == true) { for (var nodeId in this.selectionObj.nodes) { if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { idArray.push(nodeId); } } } return idArray } /** * * retrieve the currently selected edges * @return {Array} selection An array with the ids of the * selected nodes. */ getSelectedEdges() { var idArray = []; if (this.options.select == true) { for (var edgeId in this.selectionObj.edges) { if (this.selectionObj.edges.hasOwnProperty(edgeId)) { idArray.push(edgeId); } } } return idArray; } /** * select zero or more nodes with the option to highlight edges * @param {Number[] | String[]} selection An array with the ids of the * selected nodes. * @param {boolean} [highlightEdges] */ selectNodes(selection, highlightEdges) { var i, iMax, id; if (!selection || (selection.length == undefined)) throw 'Selection must be an array with ids'; // first unselect any selected node this._unselectAll(true); for (i = 0, iMax = selection.length; i < iMax; i++) { id = selection[i]; var node = this.body.nodes[id]; if (!node) { throw new RangeError('Node with id "' + id + '" not found'); } this._selectObject(node,true,true,highlightEdges,true); } this.redraw(); } /** * select zero or more edges * @param {Number[] | String[]} selection An array with the ids of the * selected nodes. */ selectEdges(selection) { var i, iMax, id; if (!selection || (selection.length == undefined)) throw 'Selection must be an array with ids'; // first unselect any selected node this._unselectAll(true); for (i = 0, iMax = selection.length; i < iMax; i++) { id = selection[i]; var edge = this.body.edges[id]; if (!edge) { throw new RangeError('Edge with id "' + id + '" not found'); } this._selectObject(edge,true,true,false,true); } this.redraw(); } /** * Validate the selection: remove ids of nodes which no longer exist * @private */ _updateSelection() { for (var nodeId in this.selectionObj.nodes) { if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { if (!this.body.nodes.hasOwnProperty(nodeId)) { delete this.selectionObj.nodes[nodeId]; } } } for (var edgeId in this.selectionObj.edges) { if (this.selectionObj.edges.hasOwnProperty(edgeId)) { if (!this.body.edges.hasOwnProperty(edgeId)) { delete this.selectionObj.edges[edgeId]; } } } } } export {SelectionHandler};