12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247 |
- let util = require('../../util');
- let Hammer = require('../../module/hammer');
- let hammerUtil = require('../../hammerUtil');
- /**
- * Clears the toolbar div element of children
- *
- * @private
- */
- class ManipulationSystem {
- /**
- * @param {Object} body
- * @param {Canvas} canvas
- * @param {SelectionHandler} selectionHandler
- */
- constructor(body, canvas, selectionHandler) {
- this.body = body;
- this.canvas = canvas;
- this.selectionHandler = selectionHandler;
- this.editMode = false;
- this.manipulationDiv = undefined;
- this.editModeDiv = undefined;
- this.closeDiv = undefined;
- this.manipulationHammers = [];
- this.temporaryUIFunctions = {};
- this.temporaryEventFunctions = [];
- this.touchTime = 0;
- this.temporaryIds = {nodes: [], edges:[]};
- this.guiEnabled = false;
- this.inMode = false;
- this.selectedControlNode = undefined;
- this.options = {};
- this.defaultOptions = {
- enabled: false,
- initiallyActive: false,
- addNode: true,
- addEdge: true,
- editNode: undefined,
- editEdge: true,
- deleteNode: true,
- deleteEdge: true,
- controlNodeStyle:{
- shape:'dot',
- size:6,
- color: {background: '#ff0000', border: '#3c3c3c', highlight: {background: '#07f968', border: '#3c3c3c'}},
- borderWidth: 2,
- borderWidthSelected: 2
- }
- };
- util.extend(this.options, this.defaultOptions);
- this.body.emitter.on('destroy', () => {this._clean();});
- this.body.emitter.on('_dataChanged',this._restore.bind(this));
- this.body.emitter.on('_resetData', this._restore.bind(this));
- }
- /**
- * If something changes in the data during editing, switch back to the initial datamanipulation state and close all edit modes.
- * @private
- */
- _restore() {
- if (this.inMode !== false) {
- if (this.options.initiallyActive === true) {
- this.enableEditMode();
- }
- else {
- this.disableEditMode();
- }
- }
- }
- /**
- * Set the Options
- *
- * @param {Object} options
- * @param {Object} allOptions
- * @param {Object} globalOptions
- */
- setOptions(options, allOptions, globalOptions) {
- if (allOptions !== undefined) {
- if (allOptions.locale !== undefined) {this.options.locale = allOptions.locale} else {this.options.locale = globalOptions.locale;}
- if (allOptions.locales !== undefined) {this.options.locales = allOptions.locales} else {this.options.locales = globalOptions.locales;}
- }
- if (options !== undefined) {
- if (typeof options === 'boolean') {
- this.options.enabled = options;
- }
- else {
- this.options.enabled = true;
- util.deepExtend(this.options, options);
- }
- if (this.options.initiallyActive === true) {
- this.editMode = true;
- }
- this._setup();
- }
- }
- /**
- * Enable or disable edit-mode. Draws the DOM required and cleans up after itself.
- *
- * @private
- */
- toggleEditMode() {
- if (this.editMode === true) {
- this.disableEditMode();
- }
- else {
- this.enableEditMode();
- }
- }
- /**
- * Enables Edit Mode
- */
- enableEditMode() {
- this.editMode = true;
- this._clean();
- if (this.guiEnabled === true) {
- this.manipulationDiv.style.display = 'block';
- this.closeDiv.style.display = 'block';
- this.editModeDiv.style.display = 'none';
- this.showManipulatorToolbar();
- }
- }
- /**
- * Disables Edit Mode
- */
- disableEditMode() {
- this.editMode = false;
- this._clean();
- if (this.guiEnabled === true) {
- this.manipulationDiv.style.display = 'none';
- this.closeDiv.style.display = 'none';
- this.editModeDiv.style.display = 'block';
- this._createEditButton();
- }
- }
- /**
- * Creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar.
- *
- * @private
- */
- showManipulatorToolbar() {
- // restore the state of any bound functions or events, remove control nodes, restore physics
- this._clean();
- // reset global variables
- this.manipulationDOM = {};
- // if the gui is enabled, draw all elements.
- if (this.guiEnabled === true) {
- // a _restore will hide these menus
- this.editMode = true;
- this.manipulationDiv.style.display = 'block';
- this.closeDiv.style.display = 'block';
- let selectedNodeCount = this.selectionHandler._getSelectedNodeCount();
- let selectedEdgeCount = this.selectionHandler._getSelectedEdgeCount();
- let selectedTotalCount = selectedNodeCount + selectedEdgeCount;
- let locale = this.options.locales[this.options.locale];
- let needSeperator = false;
- if (this.options.addNode !== false) {
- this._createAddNodeButton(locale);
- needSeperator = true;
- }
- if (this.options.addEdge !== false) {
- if (needSeperator === true) {
- this._createSeperator(1);
- } else {
- needSeperator = true;
- }
- this._createAddEdgeButton(locale);
- }
- if (selectedNodeCount === 1 && typeof this.options.editNode === 'function') {
- if (needSeperator === true) {
- this._createSeperator(2);
- } else {
- needSeperator = true;
- }
- this._createEditNodeButton(locale);
- }
- else if (selectedEdgeCount === 1 && selectedNodeCount === 0 && this.options.editEdge !== false) {
- if (needSeperator === true) {
- this._createSeperator(3);
- } else {
- needSeperator = true;
- }
- this._createEditEdgeButton(locale);
- }
- // remove buttons
- if (selectedTotalCount !== 0) {
- if (selectedNodeCount > 0 && this.options.deleteNode !== false) {
- if (needSeperator === true) {
- this._createSeperator(4);
- }
- this._createDeleteButton(locale);
- }
- else if (selectedNodeCount === 0 && this.options.deleteEdge !== false) {
- if (needSeperator === true) {
- this._createSeperator(4);
- }
- this._createDeleteButton(locale);
- }
- }
- // bind the close button
- this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this));
- // refresh this bar based on what has been selected
- this._temporaryBindEvent('select', this.showManipulatorToolbar.bind(this));
- }
- // redraw to show any possible changes
- this.body.emitter.emit('_redraw');
- }
- /**
- * Create the toolbar for adding Nodes
- */
- addNodeMode() {
- // when using the gui, enable edit mode if it wasnt already.
- if (this.editMode !== true) {
- this.enableEditMode();
- }
- // restore the state of any bound functions or events, remove control nodes, restore physics
- this._clean();
- this.inMode = 'addNode';
- if (this.guiEnabled === true) {
- let locale = this.options.locales[this.options.locale];
- this.manipulationDOM = {};
- this._createBackButton(locale);
- this._createSeperator();
- this._createDescription(locale['addDescription'] || this.options.locales['en']['addDescription']);
- // bind the close button
- this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this));
- }
- this._temporaryBindEvent('click', this._performAddNode.bind(this));
- }
- /**
- * call the bound function to handle the editing of the node. The node has to be selected.
- */
- editNode() {
- // when using the gui, enable edit mode if it wasnt already.
- if (this.editMode !== true) {
- this.enableEditMode();
- }
- // restore the state of any bound functions or events, remove control nodes, restore physics
- this._clean();
- let node = this.selectionHandler._getSelectedNode();
- if (node !== undefined) {
- this.inMode = 'editNode';
- if (typeof this.options.editNode === 'function') {
- if (node.isCluster !== true) {
- let data = util.deepExtend({}, node.options, false);
- data.x = node.x;
- data.y = node.y;
- if (this.options.editNode.length === 2) {
- this.options.editNode(data, (finalizedData) => {
- if (finalizedData !== null && finalizedData !== undefined && this.inMode === 'editNode') { // if for whatever reason the mode has changes (due to dataset change) disregard the callback) {
- this.body.data.nodes.getDataSet().update(finalizedData);
- }
- this.showManipulatorToolbar();
- });
- }
- else {
- throw new Error('The function for edit does not support two arguments (data, callback)');
- }
- }
- else {
- alert(this.options.locales[this.options.locale]['editClusterError'] || this.options.locales['en']['editClusterError']);
- }
- }
- else {
- throw new Error('No function has been configured to handle the editing of nodes.');
- }
- }
- else {
- this.showManipulatorToolbar();
- }
- }
- /**
- * create the toolbar to connect nodes
- */
- addEdgeMode() {
- // when using the gui, enable edit mode if it wasnt already.
- if (this.editMode !== true) {
- this.enableEditMode();
- }
- // restore the state of any bound functions or events, remove control nodes, restore physics
- this._clean();
- this.inMode = 'addEdge';
- if (this.guiEnabled === true) {
- let locale = this.options.locales[this.options.locale];
- this.manipulationDOM = {};
- this._createBackButton(locale);
- this._createSeperator();
- this._createDescription(locale['edgeDescription'] || this.options.locales['en']['edgeDescription']);
- // bind the close button
- this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this));
- }
- // temporarily overload functions
- this._temporaryBindUI('onTouch', this._handleConnect.bind(this));
- this._temporaryBindUI('onDragEnd', this._finishConnect.bind(this));
- this._temporaryBindUI('onDrag', this._dragControlNode.bind(this));
- this._temporaryBindUI('onRelease', this._finishConnect.bind(this));
- this._temporaryBindUI('onDragStart',this._dragStartEdge.bind(this));
- this._temporaryBindUI('onHold', () => {});
- }
- /**
- * create the toolbar to edit edges
- */
- editEdgeMode() {
- // when using the gui, enable edit mode if it wasn't already.
- if (this.editMode !== true) {
- this.enableEditMode();
- }
- // restore the state of any bound functions or events, remove control nodes, restore physics
- this._clean();
- this.inMode = 'editEdge';
- if (typeof this.options.editEdge === 'object' && typeof this.options.editEdge.editWithoutDrag === "function") {
- this.edgeBeingEditedId = this.selectionHandler.getSelectedEdges()[0];
- if (this.edgeBeingEditedId !== undefined) {
- var edge = this.body.edges[this.edgeBeingEditedId];
- this._performEditEdge(edge.from, edge.to);
- return;
- }
- }
- if (this.guiEnabled === true) {
- let locale = this.options.locales[this.options.locale];
- this.manipulationDOM = {};
- this._createBackButton(locale);
- this._createSeperator();
- this._createDescription(locale['editEdgeDescription'] || this.options.locales['en']['editEdgeDescription']);
- // bind the close button
- this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this));
- }
- this.edgeBeingEditedId = this.selectionHandler.getSelectedEdges()[0];
- if (this.edgeBeingEditedId !== undefined) {
- let edge = this.body.edges[this.edgeBeingEditedId];
- // create control nodes
- let controlNodeFrom = this._getNewTargetNode(edge.from.x, edge.from.y);
- let controlNodeTo = this._getNewTargetNode(edge.to.x, edge.to.y);
- this.temporaryIds.nodes.push(controlNodeFrom.id);
- this.temporaryIds.nodes.push(controlNodeTo.id);
- this.body.nodes[controlNodeFrom.id] = controlNodeFrom;
- this.body.nodeIndices.push(controlNodeFrom.id);
- this.body.nodes[controlNodeTo.id] = controlNodeTo;
- this.body.nodeIndices.push(controlNodeTo.id);
- // temporarily overload UI functions, cleaned up automatically because of _temporaryBindUI
- this._temporaryBindUI('onTouch', this._controlNodeTouch.bind(this)); // used to get the position
- this._temporaryBindUI('onTap', () => {}); // disabled
- this._temporaryBindUI('onHold', () => {}); // disabled
- this._temporaryBindUI('onDragStart', this._controlNodeDragStart.bind(this));// used to select control node
- this._temporaryBindUI('onDrag', this._controlNodeDrag.bind(this)); // used to drag control node
- this._temporaryBindUI('onDragEnd', this._controlNodeDragEnd.bind(this)); // used to connect or revert control nodes
- this._temporaryBindUI('onMouseMove', () => {}); // disabled
- // create function to position control nodes correctly on movement
- // automatically cleaned up because we use the temporary bind
- this._temporaryBindEvent('beforeDrawing', (ctx) => {
- let positions = edge.edgeType.findBorderPositions(ctx);
- if (controlNodeFrom.selected === false) {
- controlNodeFrom.x = positions.from.x;
- controlNodeFrom.y = positions.from.y;
- }
- if (controlNodeTo.selected === false) {
- controlNodeTo.x = positions.to.x;
- controlNodeTo.y = positions.to.y;
- }
- });
- this.body.emitter.emit('_redraw');
- }
- else {
- this.showManipulatorToolbar();
- }
- }
- /**
- * delete everything in the selection
- */
- deleteSelected() {
- // when using the gui, enable edit mode if it wasnt already.
- if (this.editMode !== true) {
- this.enableEditMode();
- }
- // restore the state of any bound functions or events, remove control nodes, restore physics
- this._clean();
- this.inMode = 'delete';
- let selectedNodes = this.selectionHandler.getSelectedNodes();
- let selectedEdges = this.selectionHandler.getSelectedEdges();
- let deleteFunction = undefined;
- if (selectedNodes.length > 0) {
- for (let i = 0; i < selectedNodes.length; i++) {
- if (this.body.nodes[selectedNodes[i]].isCluster === true) {
- alert(this.options.locales[this.options.locale]['deleteClusterError'] || this.options.locales['en']['deleteClusterError']);
- return;
- }
- }
- if (typeof this.options.deleteNode === 'function') {
- deleteFunction = this.options.deleteNode;
- }
- }
- else if (selectedEdges.length > 0) {
- if (typeof this.options.deleteEdge === 'function') {
- deleteFunction = this.options.deleteEdge;
- }
- }
- if (typeof deleteFunction === 'function') {
- let data = {nodes: selectedNodes, edges: selectedEdges};
- if (deleteFunction.length === 2) {
- deleteFunction(data, (finalizedData) => {
- if (finalizedData !== null && finalizedData !== undefined && this.inMode === 'delete') { // if for whatever reason the mode has changes (due to dataset change) disregard the callback) {
- this.body.data.edges.getDataSet().remove(finalizedData.edges);
- this.body.data.nodes.getDataSet().remove(finalizedData.nodes);
- this.body.emitter.emit('startSimulation');
- this.showManipulatorToolbar();
- }
- else {
- this.body.emitter.emit('startSimulation');
- this.showManipulatorToolbar();
- }
- });
- }
- else {
- throw new Error('The function for delete does not support two arguments (data, callback)')
- }
- }
- else {
- this.body.data.edges.getDataSet().remove(selectedEdges);
- this.body.data.nodes.getDataSet().remove(selectedNodes);
- this.body.emitter.emit('startSimulation');
- this.showManipulatorToolbar();
- }
- }
- //********************************************** PRIVATE ***************************************//
- /**
- * draw or remove the DOM
- * @private
- */
- _setup() {
- if (this.options.enabled === true) {
- // Enable the GUI
- this.guiEnabled = true;
- this._createWrappers();
- if (this.editMode === false) {
- this._createEditButton();
- }
- else {
- this.showManipulatorToolbar();
- }
- }
- else {
- this._removeManipulationDOM();
- // disable the gui
- this.guiEnabled = false;
- }
- }
- /**
- * create the div overlays that contain the DOM
- * @private
- */
- _createWrappers() {
- // load the manipulator HTML elements. All styling done in css.
- if (this.manipulationDiv === undefined) {
- this.manipulationDiv = document.createElement('div');
- this.manipulationDiv.className = 'vis-manipulation';
- if (this.editMode === true) {
- this.manipulationDiv.style.display = 'block';
- }
- else {
- this.manipulationDiv.style.display = 'none';
- }
- this.canvas.frame.appendChild(this.manipulationDiv);
- }
- // container for the edit button.
- if (this.editModeDiv === undefined) {
- this.editModeDiv = document.createElement('div');
- this.editModeDiv.className = 'vis-edit-mode';
- if (this.editMode === true) {
- this.editModeDiv.style.display = 'none';
- }
- else {
- this.editModeDiv.style.display = 'block';
- }
- this.canvas.frame.appendChild(this.editModeDiv);
- }
- // container for the close div button
- if (this.closeDiv === undefined) {
- this.closeDiv = document.createElement('div');
- this.closeDiv.className = 'vis-close';
- this.closeDiv.style.display = this.manipulationDiv.style.display;
- this.canvas.frame.appendChild(this.closeDiv);
- }
- }
- /**
- * generate a new target node. Used for creating new edges and editing edges
- *
- * @param {number} x
- * @param {number} y
- * @returns {Node}
- * @private
- */
- _getNewTargetNode(x,y) {
- let controlNodeStyle = util.deepExtend({}, this.options.controlNodeStyle);
- controlNodeStyle.id = 'targetNode' + util.randomUUID();
- controlNodeStyle.hidden = false;
- controlNodeStyle.physics = false;
- controlNodeStyle.x = x;
- controlNodeStyle.y = y;
- // we have to define the bounding box in order for the nodes to be drawn immediately
- let node = this.body.functions.createNode(controlNodeStyle);
- node.shape.boundingBox = {left: x, right:x, top:y, bottom:y};
- return node;
- }
- /**
- * Create the edit button
- */
- _createEditButton() {
- // restore everything to it's original state (if applicable)
- this._clean();
- // reset the manipulationDOM
- this.manipulationDOM = {};
- // empty the editModeDiv
- util.recursiveDOMDelete(this.editModeDiv);
- // create the contents for the editMode button
- let locale = this.options.locales[this.options.locale];
- let button = this._createButton('editMode', 'vis-button vis-edit vis-edit-mode', locale['edit'] || this.options.locales['en']['edit']);
- this.editModeDiv.appendChild(button);
- // bind a hammer listener to the button, calling the function toggleEditMode.
- this._bindHammerToDiv(button, this.toggleEditMode.bind(this));
- }
- /**
- * this function cleans up after everything this module does. Temporary elements, functions and events are removed, physics restored, hammers removed.
- * @private
- */
- _clean() {
- // not in mode
- this.inMode = false;
- // _clean the divs
- if (this.guiEnabled === true) {
- util.recursiveDOMDelete(this.editModeDiv);
- util.recursiveDOMDelete(this.manipulationDiv);
- // removes all the bindings and overloads
- this._cleanManipulatorHammers();
- }
- // remove temporary nodes and edges
- this._cleanupTemporaryNodesAndEdges();
- // restore overloaded UI functions
- this._unbindTemporaryUIs();
- // remove the temporaryEventFunctions
- this._unbindTemporaryEvents();
- // restore the physics if required
- this.body.emitter.emit('restorePhysics');
- }
- /**
- * Each dom element has it's own hammer. They are stored in this.manipulationHammers. This cleans them up.
- * @private
- */
- _cleanManipulatorHammers() {
- // _clean hammer bindings
- if (this.manipulationHammers.length != 0) {
- for (let i = 0; i < this.manipulationHammers.length; i++) {
- this.manipulationHammers[i].destroy();
- }
- this.manipulationHammers = [];
- }
- }
- /**
- * Remove all DOM elements created by this module.
- * @private
- */
- _removeManipulationDOM() {
- // removes all the bindings and overloads
- this._clean();
- // empty the manipulation divs
- util.recursiveDOMDelete(this.manipulationDiv);
- util.recursiveDOMDelete(this.editModeDiv);
- util.recursiveDOMDelete(this.closeDiv);
- // remove the manipulation divs
- if (this.manipulationDiv) {this.canvas.frame.removeChild(this.manipulationDiv);}
- if (this.editModeDiv) {this.canvas.frame.removeChild(this.editModeDiv);}
- if (this.closeDiv) {this.canvas.frame.removeChild(this.closeDiv);}
- // set the references to undefined
- this.manipulationDiv = undefined;
- this.editModeDiv = undefined;
- this.closeDiv = undefined;
- }
- /**
- * create a seperator line. the index is to differentiate in the manipulation dom
- * @param {number} [index=1]
- * @private
- */
- _createSeperator(index = 1) {
- this.manipulationDOM['seperatorLineDiv' + index] = document.createElement('div');
- this.manipulationDOM['seperatorLineDiv' + index].className = 'vis-separator-line';
- this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv' + index]);
- }
- // ---------------------- DOM functions for buttons --------------------------//
- /**
- *
- * @param {Locale} locale
- * @private
- */
- _createAddNodeButton(locale) {
- let button = this._createButton('addNode', 'vis-button vis-add', locale['addNode'] || this.options.locales['en']['addNode']);
- this.manipulationDiv.appendChild(button);
- this._bindHammerToDiv(button, this.addNodeMode.bind(this));
- }
- /**
- *
- * @param {Locale} locale
- * @private
- */
- _createAddEdgeButton(locale) {
- let button = this._createButton('addEdge', 'vis-button vis-connect', locale['addEdge'] || this.options.locales['en']['addEdge']);
- this.manipulationDiv.appendChild(button);
- this._bindHammerToDiv(button, this.addEdgeMode.bind(this));
- }
- /**
- *
- * @param {Locale} locale
- * @private
- */
- _createEditNodeButton(locale) {
- let button = this._createButton('editNode', 'vis-button vis-edit', locale['editNode'] || this.options.locales['en']['editNode']);
- this.manipulationDiv.appendChild(button);
- this._bindHammerToDiv(button, this.editNode.bind(this));
- }
- /**
- *
- * @param {Locale} locale
- * @private
- */
- _createEditEdgeButton(locale) {
- let button = this._createButton('editEdge', 'vis-button vis-edit', locale['editEdge'] || this.options.locales['en']['editEdge']);
- this.manipulationDiv.appendChild(button);
- this._bindHammerToDiv(button, this.editEdgeMode.bind(this));
- }
- /**
- *
- * @param {Locale} locale
- * @private
- */
- _createDeleteButton(locale) {
- var deleteBtnClass;
- if (this.options.rtl) {
- deleteBtnClass = 'vis-button vis-delete-rtl';
- } else {
- deleteBtnClass = 'vis-button vis-delete';
- }
- let button = this._createButton('delete', deleteBtnClass, locale['del'] || this.options.locales['en']['del']);
- this.manipulationDiv.appendChild(button);
- this._bindHammerToDiv(button, this.deleteSelected.bind(this));
- }
- /**
- *
- * @param {Locale} locale
- * @private
- */
- _createBackButton(locale) {
- let button = this._createButton('back', 'vis-button vis-back', locale['back'] || this.options.locales['en']['back']);
- this.manipulationDiv.appendChild(button);
- this._bindHammerToDiv(button, this.showManipulatorToolbar.bind(this));
- }
- /**
- *
- * @param {number|string} id
- * @param {string} className
- * @param {label} label
- * @param {string} labelClassName
- * @returns {HTMLElement}
- * @private
- */
- _createButton(id, className, label, labelClassName = 'vis-label') {
- this.manipulationDOM[id+'Div'] = document.createElement('div');
- this.manipulationDOM[id+'Div'].className = className;
- this.manipulationDOM[id+'Label'] = document.createElement('div');
- this.manipulationDOM[id+'Label'].className = labelClassName;
- this.manipulationDOM[id+'Label'].innerHTML = label;
- this.manipulationDOM[id+'Div'].appendChild(this.manipulationDOM[id+'Label']);
- return this.manipulationDOM[id+'Div'];
- }
- /**
- *
- * @param {Label} label
- * @private
- */
- _createDescription(label) {
- this.manipulationDiv.appendChild(
- this._createButton('description', 'vis-button vis-none', label)
- );
- }
- // -------------------------- End of DOM functions for buttons ------------------------------//
- /**
- * this binds an event until cleanup by the clean functions.
- * @param {Event} event The event
- * @param {function} newFunction
- * @private
- */
- _temporaryBindEvent(event, newFunction) {
- this.temporaryEventFunctions.push({event:event, boundFunction:newFunction});
- this.body.emitter.on(event, newFunction);
- }
- /**
- * this overrides an UI function until cleanup by the clean function
- * @param {string} UIfunctionName
- * @param {function} newFunction
- * @private
- */
- _temporaryBindUI(UIfunctionName, newFunction) {
- if (this.body.eventListeners[UIfunctionName] !== undefined) {
- this.temporaryUIFunctions[UIfunctionName] = this.body.eventListeners[UIfunctionName];
- this.body.eventListeners[UIfunctionName] = newFunction;
- }
- else {
- throw new Error('This UI function does not exist. Typo? You tried: ' + UIfunctionName + ' possible are: ' + JSON.stringify(Object.keys(this.body.eventListeners)));
- }
- }
- /**
- * Restore the overridden UI functions to their original state.
- *
- * @private
- */
- _unbindTemporaryUIs() {
- for (let functionName in this.temporaryUIFunctions) {
- if (this.temporaryUIFunctions.hasOwnProperty(functionName)) {
- this.body.eventListeners[functionName] = this.temporaryUIFunctions[functionName];
- delete this.temporaryUIFunctions[functionName];
- }
- }
- this.temporaryUIFunctions = {};
- }
- /**
- * Unbind the events created by _temporaryBindEvent
- * @private
- */
- _unbindTemporaryEvents() {
- for (let i = 0; i < this.temporaryEventFunctions.length; i++) {
- let eventName = this.temporaryEventFunctions[i].event;
- let boundFunction = this.temporaryEventFunctions[i].boundFunction;
- this.body.emitter.off(eventName, boundFunction);
- }
- this.temporaryEventFunctions = [];
- }
- /**
- * Bind an hammer instance to a DOM element.
- *
- * @param {Element} domElement
- * @param {function} boundFunction
- */
- _bindHammerToDiv(domElement, boundFunction) {
- let hammer = new Hammer(domElement, {});
- hammerUtil.onTouch(hammer, boundFunction);
- this.manipulationHammers.push(hammer);
- }
- /**
- * Neatly clean up temporary edges and nodes
- * @private
- */
- _cleanupTemporaryNodesAndEdges() {
- // _clean temporary edges
- for (let i = 0; i < this.temporaryIds.edges.length; i++) {
- this.body.edges[this.temporaryIds.edges[i]].disconnect();
- delete this.body.edges[this.temporaryIds.edges[i]];
- let indexTempEdge = this.body.edgeIndices.indexOf(this.temporaryIds.edges[i]);
- if (indexTempEdge !== -1) {this.body.edgeIndices.splice(indexTempEdge,1);}
- }
- // _clean temporary nodes
- for (let i = 0; i < this.temporaryIds.nodes.length; i++) {
- delete this.body.nodes[this.temporaryIds.nodes[i]];
- let indexTempNode = this.body.nodeIndices.indexOf(this.temporaryIds.nodes[i]);
- if (indexTempNode !== -1) {this.body.nodeIndices.splice(indexTempNode,1);}
- }
- this.temporaryIds = {nodes: [], edges: []};
- }
- // ------------------------------------------ EDIT EDGE FUNCTIONS -----------------------------------------//
- /**
- * the touch is used to get the position of the initial click
- * @param {Event} event The event
- * @private
- */
- _controlNodeTouch(event) {
- this.selectionHandler.unselectAll();
- this.lastTouch = this.body.functions.getPointer(event.center);
- this.lastTouch.translation = util.extend({},this.body.view.translation); // copy the object
- }
- /**
- * the drag start is used to mark one of the control nodes as selected.
- * @param {Event} event The event
- * @private
- */
- _controlNodeDragStart(event) { // eslint-disable-line no-unused-vars
- let pointer = this.lastTouch;
- let pointerObj = this.selectionHandler._pointerToPositionObject(pointer);
- let from = this.body.nodes[this.temporaryIds.nodes[0]];
- let to = this.body.nodes[this.temporaryIds.nodes[1]];
- let edge = this.body.edges[this.edgeBeingEditedId];
- this.selectedControlNode = undefined;
- let fromSelect = from.isOverlappingWith(pointerObj);
- let toSelect = to.isOverlappingWith(pointerObj);
- if (fromSelect === true) {
- this.selectedControlNode = from;
- edge.edgeType.from = from;
- }
- else if (toSelect === true) {
- this.selectedControlNode = to;
- edge.edgeType.to = to;
- }
- // we use the selection to find the node that is being dragged. We explicitly select it here.
- if (this.selectedControlNode !== undefined) {
- this.selectionHandler.selectObject(this.selectedControlNode)
- }
- this.body.emitter.emit('_redraw');
- }
- /**
- * dragging the control nodes or the canvas
- * @param {Event} event The event
- * @private
- */
- _controlNodeDrag(event) {
- this.body.emitter.emit('disablePhysics');
- let pointer = this.body.functions.getPointer(event.center);
- let pos = this.canvas.DOMtoCanvas(pointer);
- if (this.selectedControlNode !== undefined) {
- this.selectedControlNode.x = pos.x;
- this.selectedControlNode.y = pos.y;
- }
- else {
- // if the drag was not started properly because the click started outside the network div, start it now.
- let diffX = pointer.x - this.lastTouch.x;
- let diffY = pointer.y - this.lastTouch.y;
- this.body.view.translation = {x:this.lastTouch.translation.x + diffX, y:this.lastTouch.translation.y + diffY};
- }
- this.body.emitter.emit('_redraw');
- }
- /**
- * connecting or restoring the control nodes.
- * @param {Event} event The event
- * @private
- */
- _controlNodeDragEnd(event) {
- let pointer = this.body.functions.getPointer(event.center);
- let pointerObj = this.selectionHandler._pointerToPositionObject(pointer);
- let edge = this.body.edges[this.edgeBeingEditedId];
- // if the node that was dragged is not a control node, return
- if (this.selectedControlNode === undefined) {
- return;
- }
- // we use the selection to find the node that is being dragged. We explicitly DEselect the control node here.
- this.selectionHandler.unselectAll();
- let overlappingNodeIds = this.selectionHandler._getAllNodesOverlappingWith(pointerObj);
- let node = undefined;
- for (let i = overlappingNodeIds.length-1; i >= 0; i--) {
- if (overlappingNodeIds[i] !== this.selectedControlNode.id) {
- node = this.body.nodes[overlappingNodeIds[i]];
- break;
- }
- }
- // perform the connection
- if (node !== undefined && this.selectedControlNode !== undefined) {
- if (node.isCluster === true) {
- alert(this.options.locales[this.options.locale]['createEdgeError'] || this.options.locales['en']['createEdgeError'])
- }
- else {
- let from = this.body.nodes[this.temporaryIds.nodes[0]];
- if (this.selectedControlNode.id === from.id) {
- this._performEditEdge(node.id, edge.to.id);
- }
- else {
- this._performEditEdge(edge.from.id, node.id);
- }
- }
- }
- else {
- edge.updateEdgeType();
- this.body.emitter.emit('restorePhysics');
- }
- this.body.emitter.emit('_redraw');
- }
- // ------------------------------------ END OF EDIT EDGE FUNCTIONS -----------------------------------------//
- // ------------------------------------------- ADD EDGE FUNCTIONS -----------------------------------------//
- /**
- * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
- * to walk the user through the process.
- *
- * @param {Event} event
- * @private
- */
- _handleConnect(event) {
- // check to avoid double fireing of this function.
- if (new Date().valueOf() - this.touchTime > 100) {
- this.lastTouch = this.body.functions.getPointer(event.center);
- this.lastTouch.translation = util.extend({},this.body.view.translation); // copy the object
- let pointer = this.lastTouch;
- let node = this.selectionHandler.getNodeAt(pointer);
- if (node !== undefined) {
- if (node.isCluster === true) {
- alert(this.options.locales[this.options.locale]['createEdgeError'] || this.options.locales['en']['createEdgeError'])
- }
- else {
- // create a node the temporary line can look at
- let targetNode = this._getNewTargetNode(node.x,node.y);
- this.body.nodes[targetNode.id] = targetNode;
- this.body.nodeIndices.push(targetNode.id);
- // create a temporary edge
- let connectionEdge = this.body.functions.createEdge({
- id: 'connectionEdge' + util.randomUUID(),
- from: node.id,
- to: targetNode.id,
- physics: false,
- smooth: {
- enabled: true,
- type: 'continuous',
- roundness: 0.5
- }
- });
- this.body.edges[connectionEdge.id] = connectionEdge;
- this.body.edgeIndices.push(connectionEdge.id);
- this.temporaryIds.nodes.push(targetNode.id);
- this.temporaryIds.edges.push(connectionEdge.id);
- }
- }
- this.touchTime = new Date().valueOf();
- }
- }
- /**
- *
- * @param {Event} event
- * @private
- */
- _dragControlNode(event) {
- let pointer = this.body.functions.getPointer(event.center);
- if (this.temporaryIds.nodes[0] !== undefined) {
- let targetNode = this.body.nodes[this.temporaryIds.nodes[0]]; // there is only one temp node in the add edge mode.
- targetNode.x = this.canvas._XconvertDOMtoCanvas(pointer.x);
- targetNode.y = this.canvas._YconvertDOMtoCanvas(pointer.y);
- this.body.emitter.emit('_redraw');
- }
- else {
- let diffX = pointer.x - this.lastTouch.x;
- let diffY = pointer.y - this.lastTouch.y;
- this.body.view.translation = {x:this.lastTouch.translation.x + diffX, y:this.lastTouch.translation.y + diffY};
- }
- }
- /**
- * Connect the new edge to the target if one exists, otherwise remove temp line
- * @param {Event} event The event
- * @private
- */
- _finishConnect(event) {
- let pointer = this.body.functions.getPointer(event.center);
- let pointerObj = this.selectionHandler._pointerToPositionObject(pointer);
- // remember the edge id
- let connectFromId = undefined;
- if (this.temporaryIds.edges[0] !== undefined) {
- connectFromId = this.body.edges[this.temporaryIds.edges[0]].fromId;
- }
- // get the overlapping node but NOT the temporary node;
- let overlappingNodeIds = this.selectionHandler._getAllNodesOverlappingWith(pointerObj);
- let node = undefined;
- for (let i = overlappingNodeIds.length-1; i >= 0; i--) {
- // if the node id is NOT a temporary node, accept the node.
- if (this.temporaryIds.nodes.indexOf(overlappingNodeIds[i]) === -1) {
- node = this.body.nodes[overlappingNodeIds[i]];
- break;
- }
- }
- // clean temporary nodes and edges.
- this._cleanupTemporaryNodesAndEdges();
- // perform the connection
- if (node !== undefined) {
- if (node.isCluster === true) {
- alert(this.options.locales[this.options.locale]['createEdgeError'] || this.options.locales['en']['createEdgeError']);
- }
- else {
- if (this.body.nodes[connectFromId] !== undefined && this.body.nodes[node.id] !== undefined) {
- this._performAddEdge(connectFromId, node.id);
- }
- }
- }
- // No need to do _generateclickevent('dragEnd') here, the regular dragEnd event fires.
- this.body.emitter.emit('_redraw');
- }
- /**
- *
- * @param {Event} event
- * @private
- */
- _dragStartEdge(event) {
- let pointer = this.lastTouch;
- this.selectionHandler._generateClickEvent('dragStart', event, pointer, undefined, true);
- }
- // --------------------------------------- END OF ADD EDGE FUNCTIONS -------------------------------------//
- // ------------------------------ Performing all the actual data manipulation ------------------------//
- /**
- * Adds a node on the specified location
- *
- * @param {Object} clickData
- * @private
- */
- _performAddNode(clickData) {
- let defaultData = {
- id: util.randomUUID(),
- x: clickData.pointer.canvas.x,
- y: clickData.pointer.canvas.y,
- label: 'new'
- };
- if (typeof this.options.addNode === 'function') {
- if (this.options.addNode.length === 2) {
- this.options.addNode(defaultData, (finalizedData) => {
- if (finalizedData !== null && finalizedData !== undefined && this.inMode === 'addNode') { // if for whatever reason the mode has changes (due to dataset change) disregard the callback
- this.body.data.nodes.getDataSet().add(finalizedData);
- this.showManipulatorToolbar();
- }
- });
- }
- else {
- this.showManipulatorToolbar();
- throw new Error('The function for add does not support two arguments (data,callback)');
- }
- }
- else {
- this.body.data.nodes.getDataSet().add(defaultData);
- this.showManipulatorToolbar();
- }
- }
- /**
- * connect two nodes with a new edge.
- *
- * @param {Node.id} sourceNodeId
- * @param {Node.id} targetNodeId
- * @private
- */
- _performAddEdge(sourceNodeId, targetNodeId) {
- let defaultData = {from: sourceNodeId, to: targetNodeId};
- if (typeof this.options.addEdge === 'function') {
- if (this.options.addEdge.length === 2) {
- this.options.addEdge(defaultData, (finalizedData) => {
- if (finalizedData !== null && finalizedData !== undefined && this.inMode === 'addEdge') { // if for whatever reason the mode has changes (due to dataset change) disregard the callback
- this.body.data.edges.getDataSet().add(finalizedData);
- this.selectionHandler.unselectAll();
- this.showManipulatorToolbar();
- }
- });
- }
- else {
- throw new Error('The function for connect does not support two arguments (data,callback)');
- }
- }
- else {
- this.body.data.edges.getDataSet().add(defaultData);
- this.selectionHandler.unselectAll();
- this.showManipulatorToolbar();
- }
- }
- /**
- * connect two nodes with a new edge.
- *
- * @param {Node.id} sourceNodeId
- * @param {Node.id} targetNodeId
- * @private
- */
- _performEditEdge(sourceNodeId, targetNodeId) {
- let defaultData = {id: this.edgeBeingEditedId, from: sourceNodeId, to: targetNodeId, label: this.body.data.edges._data[this.edgeBeingEditedId].label };
- let eeFunct = this.options.editEdge;
- if (typeof eeFunct === 'object') {
- eeFunct = eeFunct.editWithoutDrag;
- }
- if (typeof eeFunct === 'function') {
- if (eeFunct.length === 2) {
- eeFunct(defaultData, (finalizedData) => {
- if (finalizedData === null || finalizedData === undefined || this.inMode !== 'editEdge') { // if for whatever reason the mode has changes (due to dataset change) disregard the callback) {
- this.body.edges[defaultData.id].updateEdgeType();
- this.body.emitter.emit('_redraw');
- this.showManipulatorToolbar();
- }
- else {
- this.body.data.edges.getDataSet().update(finalizedData);
- this.selectionHandler.unselectAll();
- this.showManipulatorToolbar();
- }
- });
- }
- else {
- throw new Error('The function for edit does not support two arguments (data, callback)');
- }
- }
- else {
- this.body.data.edges.getDataSet().update(defaultData);
- this.selectionHandler.unselectAll();
- this.showManipulatorToolbar();
- }
- }
- }
- export default ManipulationSystem;
|