From d592459bc2e00e6facd0300c5792ae647170bfc9 Mon Sep 17 00:00:00 2001 From: Alex de Mulder Date: Mon, 27 Jan 2014 14:24:32 +0100 Subject: [PATCH] added Dynamic UI icon URL --- dist/{UI => UI_icons}/downarrow.png | Bin dist/{UI => UI_icons}/leftarrow.png | Bin dist/{UI => UI_icons}/minus.png | Bin dist/{UI => UI_icons}/plus.png | Bin dist/{UI => UI_icons}/rightarrow.png | Bin dist/{UI => UI_icons}/uparrow.png | Bin dist/{UI => UI_icons}/zoomExtends.png | Bin dist/vis.js | 179 ++++++++++++++++---------- examples/graph/img/UI/downarrow.png | Bin 4460 -> 0 bytes examples/graph/img/UI/leftarrow.png | Bin 4531 -> 0 bytes examples/graph/img/UI/minus.png | Bin 4147 -> 0 bytes examples/graph/img/UI/plus.png | Bin 4341 -> 0 bytes examples/graph/img/UI/rightarrow.png | Bin 4514 -> 0 bytes examples/graph/img/UI/uparrow.png | Bin 4461 -> 0 bytes examples/graph/img/UI/zoomExtends.png | Bin 4464 -> 0 bytes src/graph/Graph.js | 72 +++++++---- src/graph/Node.js | 9 +- src/graph/SectorsMixin.js | 10 +- src/graph/SelectionMixin.js | 12 +- src/graph/UIMixin.js | 44 +++---- src/util.js | 28 ++++ 21 files changed, 226 insertions(+), 128 deletions(-) rename dist/{UI => UI_icons}/downarrow.png (100%) rename dist/{UI => UI_icons}/leftarrow.png (100%) rename dist/{UI => UI_icons}/minus.png (100%) rename dist/{UI => UI_icons}/plus.png (100%) rename dist/{UI => UI_icons}/rightarrow.png (100%) rename dist/{UI => UI_icons}/uparrow.png (100%) rename dist/{UI => UI_icons}/zoomExtends.png (100%) delete mode 100644 examples/graph/img/UI/downarrow.png delete mode 100644 examples/graph/img/UI/leftarrow.png delete mode 100644 examples/graph/img/UI/minus.png delete mode 100644 examples/graph/img/UI/plus.png delete mode 100644 examples/graph/img/UI/rightarrow.png delete mode 100644 examples/graph/img/UI/uparrow.png delete mode 100644 examples/graph/img/UI/zoomExtends.png diff --git a/dist/UI/downarrow.png b/dist/UI_icons/downarrow.png similarity index 100% rename from dist/UI/downarrow.png rename to dist/UI_icons/downarrow.png diff --git a/dist/UI/leftarrow.png b/dist/UI_icons/leftarrow.png similarity index 100% rename from dist/UI/leftarrow.png rename to dist/UI_icons/leftarrow.png diff --git a/dist/UI/minus.png b/dist/UI_icons/minus.png similarity index 100% rename from dist/UI/minus.png rename to dist/UI_icons/minus.png diff --git a/dist/UI/plus.png b/dist/UI_icons/plus.png similarity index 100% rename from dist/UI/plus.png rename to dist/UI_icons/plus.png diff --git a/dist/UI/rightarrow.png b/dist/UI_icons/rightarrow.png similarity index 100% rename from dist/UI/rightarrow.png rename to dist/UI_icons/rightarrow.png diff --git a/dist/UI/uparrow.png b/dist/UI_icons/uparrow.png similarity index 100% rename from dist/UI/uparrow.png rename to dist/UI_icons/uparrow.png diff --git a/dist/UI/zoomExtends.png b/dist/UI_icons/zoomExtends.png similarity index 100% rename from dist/UI/zoomExtends.png rename to dist/UI_icons/zoomExtends.png diff --git a/dist/vis.js b/dist/vis.js index c69c6e79..ec34e1f7 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -4,8 +4,8 @@ * * A dynamic, browser-based visualization library. * - * @version 0.4.0-SNAPSHOT - * @date 2014-01-27 + * @version @@version + * @date @@date * * @license * Copyright (C) 2011-2014 Almende B.V, http://almende.com @@ -983,6 +983,34 @@ util.option.asElement = function (value, defaultValue) { return value || defaultValue || null; }; + +/** + * Compare two numbers and return the lowest, non-negative number. + * + * @param {number} number1 + * @param {number} number2 + * @returns {number} | number1 or number2, the lowest positive number. If both negative, return -1 + * @private + */ +util._getLowestPositiveNumber = function(number1,number2) { + if (number1 >= 0) { + if (number2 >= 0) { + return (number1 < number2) ? number1 : number2; + } + else { + return number1; + } + } + else { + if (number2 >= 0) { + return number2; + } + else { + return -1; + } + } +} + /** * Event listener (singleton) */ @@ -8784,8 +8812,8 @@ function Node(properties, imagelist, grouplist, constants) { this.y = 0; this.xFixed = false; this.yFixed = false; - this.horizontalAlignLeft = true; // these are for the UI - this.verticalAlignTop = true; // these are for the UI + this.horizontalAlignLeft = true; // these are for the navigationUI + this.verticalAlignTop = true; // these are for the navigationUI this.radius = constants.nodes.radius; this.baseRadiusValue = constants.nodes.radius; this.radiusFixed = false; @@ -8891,7 +8919,7 @@ Node.prototype.setProperties = function(properties, constants) { if (properties.y !== undefined) {this.y = properties.y;} if (properties.value !== undefined) {this.value = properties.value;} - // UI properties + // navigationUI properties if (properties.horizontalAlignLeft !== undefined) {this.horizontalAlignLeft = properties.horizontalAlignLeft;} if (properties.verticalAlignTop !== undefined) {this.verticalAlignTop = properties.verticalAlignTop;} if (properties.triggerFunction !== undefined) {this.triggerFunction = properties.triggerFunction;} @@ -9146,9 +9174,6 @@ Node.prototype.isFixed = function() { */ // TODO: replace this method with calculating the kinetic energy Node.prototype.isMoving = function(vmin) { -// return (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin || -// (!this.xFixed && Math.abs(this.fx) > this.minForce) || -// (!this.yFixed && Math.abs(this.fy) > this.minForce)); if (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin) { return true; } @@ -10642,14 +10667,14 @@ var SectorMixin = { /** * This function sets the global references to nodes, edges and nodeIndices to - * those of the UI sector. + * those of the navigationUI sector. * * @private */ _switchToUISector : function() { - this.nodeIndices = this.sectors["UI"]["nodeIndices"]; - this.nodes = this.sectors["UI"]["nodes"]; - this.edges = this.sectors["UI"]["edges"]; + this.nodeIndices = this.sectors["navigationUI"]["nodeIndices"]; + this.nodes = this.sectors["navigationUI"]["nodes"]; + this.edges = this.sectors["navigationUI"]["edges"]; }, @@ -11002,7 +11027,7 @@ var SectorMixin = { /** - * This runs a function in the UI sector. + * This runs a function in the navigationUI sector. * * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors * | we don't pass the function itself because then the "this" is the window object @@ -12170,7 +12195,7 @@ var SelectionMixin = { /** - * retrieve all nodes in the UI overlapping with given object + * retrieve all nodes in the navigationUI 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 @@ -12217,7 +12242,7 @@ var SelectionMixin = { /** - * Get the top UI node at the a specific point (like a click) + * Get the top navigationUI node at the a specific point (like a click) * * @param {{x: Number, y: Number}} pointer * @return {Node | null} node @@ -12227,7 +12252,7 @@ var SelectionMixin = { var screenPositionObject = this._pointerToScreenPositionObject(pointer); var overlappingNodes = this._getAllUINodesOverlappingWith(screenPositionObject); if (this.UIvisible && overlappingNodes.length > 0) { - return this.sectors["UI"]["nodes"][overlappingNodes[overlappingNodes.length - 1]]; + return this.sectors["navigationUI"]["nodes"][overlappingNodes[overlappingNodes.length - 1]]; } else { return null; @@ -12243,7 +12268,7 @@ var SelectionMixin = { * @private */ _getNodeAt : function (pointer) { - // we first check if this is an UI element + // we first check if this is an navigationUI element var positionObject = this._pointerToPositionObject(pointer); overlappingNodes = this._getAllNodesOverlappingWith(positionObject); @@ -12360,7 +12385,7 @@ var SelectionMixin = { /** - * handles the selection part of the touch, only for UI elements; + * handles the selection part of the touch, only for navigationUI elements; * Touch is triggered before tap, also before hold. Hold triggers after a while. * This is the most responsive solution * @@ -12428,7 +12453,7 @@ var SelectionMixin = { /** - * handle the onRelease event. These functions are here for the UI module. + * handle the onRelease event. These functions are here for the navigationUI module. * * @private */ @@ -12630,7 +12655,7 @@ var SelectionMixin = { var UIMixin = { /** - * This function moves the UI if the canvas size has been changed. If the arugments + * This function moves the navigationUI if the canvas size has been changed. If the arugments * verticaAlignTop and horizontalAlignLeft are false, the correction will be made * * @private @@ -12643,9 +12668,9 @@ var UIMixin = { this.UIclientHeight = this.frame.canvas.clientHeight; var node = null; - for (var nodeId in this.sectors["UI"]["nodes"]) { - if (this.sectors["UI"]["nodes"].hasOwnProperty(nodeId)) { - node = this.sectors["UI"]["nodes"][nodeId]; + for (var nodeId in this.sectors["navigationUI"]["nodes"]) { + if (this.sectors["navigationUI"]["nodes"].hasOwnProperty(nodeId)) { + node = this.sectors["navigationUI"]["nodes"][nodeId]; if (!node.horizontalAlignLeft) { node.x -= xOffset; } @@ -12659,15 +12684,15 @@ var UIMixin = { /** - * Creation of the UI nodes. They are drawn over the rest of the nodes and are not affected by scale and translation - * they have a triggerFunction which is called on click. If the position of the UI is dependent + * Creation of the navigationUI nodes. They are drawn over the rest of the nodes and are not affected by scale and translation + * they have a triggerFunction which is called on click. If the position of the navigationUI is dependent * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false. * This means that the location will be corrected by the _relocateUI function on a size change of the canvas. * * @private */ _loadUIElements : function() { - var DIR = 'img/UI/'; + var DIR = this.constants.navigationUI.iconPath; this.UIclientWidth = this.frame.canvas.clientWidth; this.UIclientHeight = this.frame.canvas.clientHeight; if (this.UIclientWidth === undefined) { @@ -12678,7 +12703,7 @@ var UIMixin = { var intermediateOffset = 7; var UINodes = [ {id: 'UI_up', shape: 'image', image: DIR + 'uparrow.png', triggerFunction: "_moveUp", - verticalAlignTop: false, x: 45 + offset + intermediateOffset, y: this.UIclientHeight - 47 - offset}, + verticalAlignTop: false, x: 45 + offset + intermediateOffset, y: this.UIclientHeight - 45 - offset - intermediateOffset}, {id: 'UI_down', shape: 'image', image: DIR + 'downarrow.png', triggerFunction: "_moveDown", verticalAlignTop: false, x: 45 + offset + intermediateOffset, y: this.UIclientHeight - 15 - offset}, {id: 'UI_left', shape: 'image', image: DIR + 'leftarrow.png', triggerFunction: "_moveLeft", @@ -12699,7 +12724,7 @@ var UIMixin = { var nodeObj = null; for (var i = 0; i < UINodes.length; i++) { - nodeObj = this.sectors["UI"]['nodes']; + nodeObj = this.sectors["navigationUI"]['nodes']; nodeObj[UINodes[i]['id']] = new Node(UINodes[i], this.images, this.groups, this.constants); } }, @@ -12713,8 +12738,8 @@ var UIMixin = { * @private */ _highlightUIElement : function(elementId) { - if (this.sectors["UI"]["nodes"].hasOwnProperty(elementId)) { - this.sectors["UI"]["nodes"][elementId].clusterSize = 2; + if (this.sectors["navigationUI"]["nodes"].hasOwnProperty(elementId)) { + this.sectors["navigationUI"]["nodes"][elementId].clusterSize = 2; } }, @@ -12726,14 +12751,14 @@ var UIMixin = { * @private */ _unHighlightUIElement : function(elementId) { - if (this.sectors["UI"]["nodes"].hasOwnProperty(elementId)) { - this.sectors["UI"]["nodes"][elementId].clusterSize = 1; + if (this.sectors["navigationUI"]["nodes"].hasOwnProperty(elementId)) { + this.sectors["navigationUI"]["nodes"][elementId].clusterSize = 1; } }, /** - * toggle the visibility of the UI + * toggle the visibility of the navigationUI * * @private */ @@ -12747,11 +12772,11 @@ var UIMixin = { /** - * un-highlight (for lack of a better term) all UI elements + * un-highlight (for lack of a better term) all navigationUI elements * @private */ _unHighlightAll : function() { - for (var nodeId in this.sectors['UI']['nodes']) { + for (var nodeId in this.sectors['navigationUI']['nodes']) { this._unHighlightUIElement(nodeId); } }, @@ -12778,7 +12803,7 @@ var UIMixin = { */ _moveUp : function(event) { this._highlightUIElement("UI_up"); - this.yIncrement = this.constants.UI.yMovementSpeed; + this.yIncrement = this.constants.navigationUI.yMovementSpeed; this.start(); // if there is no node movement, the calculation wont be done this._preventDefault(event); }, @@ -12790,7 +12815,7 @@ var UIMixin = { */ _moveDown : function(event) { this._highlightUIElement("UI_down"); - this.yIncrement = -this.constants.UI.yMovementSpeed; + this.yIncrement = -this.constants.navigationUI.yMovementSpeed; this.start(); // if there is no node movement, the calculation wont be done this._preventDefault(event); }, @@ -12802,7 +12827,7 @@ var UIMixin = { */ _moveLeft : function(event) { this._highlightUIElement("UI_left"); - this.xIncrement = this.constants.UI.xMovementSpeed; + this.xIncrement = this.constants.navigationUI.xMovementSpeed; this.start(); // if there is no node movement, the calculation wont be done this._preventDefault(event); }, @@ -12814,7 +12839,7 @@ var UIMixin = { */ _moveRight : function(event) { this._highlightUIElement("UI_right"); - this.xIncrement = -this.constants.UI.xMovementSpeed; + this.xIncrement = -this.constants.navigationUI.xMovementSpeed; this.start(); // if there is no node movement, the calculation wont be done this._preventDefault(event); }, @@ -12826,7 +12851,7 @@ var UIMixin = { */ _zoomIn : function(event) { this._highlightUIElement("UI_plus"); - this.zoomIncrement = this.constants.UI.zoomMovementSpeed; + this.zoomIncrement = this.constants.navigationUI.zoomMovementSpeed; this.start(); // if there is no node movement, the calculation wont be done this._preventDefault(event); }, @@ -12838,7 +12863,7 @@ var UIMixin = { */ _zoomOut : function() { this._highlightUIElement("UI_min"); - this.zoomIncrement = -this.constants.UI.zoomMovementSpeed; + this.zoomIncrement = -this.constants.navigationUI.zoomMovementSpeed; this.start(); // if there is no node movement, the calculation wont be done this._preventDefault(event); }, @@ -12970,19 +12995,21 @@ function Graph (container, data, options) { activeAreaBoxSize: 100, // (px) | box area around the curser where clusters are popped open. massTransferCoefficient: 1 // (multiplier) | parent.mass += massTransferCoefficient * child.mass }, - UI: { + navigationUI: { enabled: true, initiallyVisible: true, xMovementSpeed: 10, yMovementSpeed: 10, zoomMovementSpeed: 0.02, - iconPath: 'img/UI/' + iconPath: this._getIconURL() + }, + keyboardNavigation: { + enabled: false }, minVelocity: 1.0, // px/s maxIterations: 1000 // maximum number of iteration to stabilize }; - // Node variables this.groups = new Groups(); // object with groups this.images = new Images(); // object with images @@ -12990,13 +13017,12 @@ function Graph (container, data, options) { graph._redraw(); }); - // UI variables - this.UIvisible = this.constants.UI.initiallyVisible; + // navigationUI variables + this.UIvisible = this.constants.navigationUI.initiallyVisible; this.xIncrement = 0; this.yIncrement = 0; this.zoomIncrement = 0; - console.log // create a frame and canvas this._create(); @@ -13012,7 +13038,7 @@ function Graph (container, data, options) { // load the selection system. (mandatory, required by Graph) this._loadSelectionSystem(); - // load the UI system. (mandatory, few function calls even when UI is disabled (in this.setSize) + // load the navigationUI system. (mandatory, few function calls even when navigationUI is disabled (in this.setSize) this._loadUISystem(); // other vars @@ -13083,6 +13109,28 @@ function Graph (container, data, options) { } } +/** + * get the URL where the UI icons are located + * + * @returns {string} + * @private + */ +Graph.prototype._getIconURL = function() { + var scripts = document.getElementsByTagName( 'script' ); + var scriptNamePosition, srcPosition, imagePath; + for (var i = 0; i < scripts.length; i++) { + srcPosition = scripts[i].outerHTML.search("src"); + if (srcPosition != -1) { + scriptNamePosition = util._getLowestPositiveNumber(scripts[i].outerHTML.search("vis.js"), + scripts[i].outerHTML.search("vis.min.js")); + if (scriptNamePosition != -1) { + imagePath = scripts[i].outerHTML.substring(srcPosition+5,scriptNamePosition).concat("UI_icons/"); + return imagePath; + } + } + } +}; + /** * Find the center position of the graph @@ -13145,8 +13193,8 @@ Graph.prototype.zoomToFit = function(initialZoom) { if (initialZoom == true) { if (this.constants.clustering.enabled == true && - numberOfNodes < this.constants.clustering.initialMaxNumberOfNodes) { - var zoomLevel = 38.8467 / (numberOfNodes - 14.50184) + 0.0116360476; // this is obtained from fitting a dataset from 5 points with scale levels that looked good. + numberOfNodes >= this.constants.clustering.initialMaxNumberOfNodes) { + var zoomLevel = 38.8467 / (numberOfNodes - 14.50184) + 0.0116; // this is obtained from fitting a dataset from 5 points with scale levels that looked good. } else { var zoomLevel = 42.54117319 / (numberOfNodes + 39.31966387) + 0.1944405; // this is obtained from fitting a dataset from 5 points with scale levels that looked good. @@ -13254,10 +13302,10 @@ Graph.prototype.setOptions = function (options) { } } - if (options.UI) { - for (var prop in options.UI) { - if (options.UI.hasOwnProperty(prop)) { - this.constants.UI[prop] = options.UI[prop]; + if (options.navigationUI) { + for (var prop in options.navigationUI) { + if (options.navigationUI.hasOwnProperty(prop)) { + this.constants.navigationUI[prop] = options.navigationUI[prop]; } } } @@ -13925,7 +13973,7 @@ Graph.prototype.setSize = function(width, height) { this.frame.canvas.width = this.frame.canvas.clientWidth; this.frame.canvas.height = this.frame.canvas.clientHeight; - if (this.constants.UI.enabled == true) { + if (this.constants.navigationUI.enabled == true) { this._relocateUI(); } }; @@ -14446,6 +14494,7 @@ Graph.prototype._doStabilize = function() { stable = !this._isMoving(vmin); count++; } + this.zoomToFit(); // var end = new Date(); @@ -14495,7 +14544,7 @@ Graph.prototype._calculateForces = function() { // Gravity is required to keep separated groups from floating off // the forces are reset to zero in this loop by using _setForce instead // of _addForce - var gravity = 0.20 * this.forceFactor; + var gravity = 0.10 * this.forceFactor; for (i = 0; i < this.nodeIndices.length; i++) { node = nodes[this.nodeIndices[i]]; // gravity does not apply when we are in a pocket sector @@ -14680,10 +14729,10 @@ Graph.prototype._calculateForces = function() { * @private */ Graph.prototype._isMoving = function(vmin) { - // TODO: ismoving does not work well: should check the kinetic energy, not its velocity + var vminCorrected = vmin / this.scale; var nodes = this.nodes; for (var id in nodes) { - if (nodes.hasOwnProperty(id) && nodes[id].isMoving(vmin)) { + if (nodes.hasOwnProperty(id) && nodes[id].isMoving(vminCorrected)) { return true; } } @@ -14822,7 +14871,7 @@ Graph.prototype._loadSectorSystem = function() { "formationScale": 1.0, "drawingNode": undefined}; this.sectors["frozen"] = {}; - this.sectors["UI"] = {"nodes":{}, + this.sectors["navigationUI"] = {"nodes":{}, "edges":{}, "nodeIndices":[], "formationScale": 1.0, @@ -14855,7 +14904,7 @@ Graph.prototype._loadSelectionSystem = function() { /** - * Mixin the UI (User Interface) system and initialize the parameters required + * Mixin the navigationUI (User Interface) system and initialize the parameters required * * @private */ @@ -14866,7 +14915,7 @@ Graph.prototype._loadUISystem = function() { } } - if (this.constants.UI.enabled == true) { + if (this.constants.navigationUI.enabled == true) { this._loadUIElements(); this._createKeyBinds(); @@ -14874,17 +14923,17 @@ Graph.prototype._loadUISystem = function() { } /** - * this function exists to avoid errors when not loading the UI system + * this function exists to avoid errors when not loading the navigationUI system */ Graph.prototype._relocateUI = function() { - // empty, is overloaded by UI system + // empty, is overloaded by navigationUI system } /** - * * this function exists to avoid errors when not loading the UI system + * * this function exists to avoid errors when not loading the navigationUI system */ Graph.prototype._unHighlightAll = function() { - // empty, is overloaded by the UI system + // empty, is overloaded by the navigationUI system } diff --git a/examples/graph/img/UI/downarrow.png b/examples/graph/img/UI/downarrow.png deleted file mode 100644 index e77d5e6d4157b12a5b2fa08a9fc138f0ab9eb4e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4460 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�EM>-Bgb&Rym2{we-b7Oawc(c4k4MzLw~<#m(JZ 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 Re)#|Z002ovPDHLkV1iMdkGuc? diff --git a/examples/graph/img/UI/minus.png b/examples/graph/img/UI/minus.png deleted file mode 100644 index 30698076b9ac59f0880752ddc13af3f6bf062403..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4147 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 diff --git a/examples/graph/img/UI/plus.png b/examples/graph/img/UI/plus.png deleted file mode 100644 index f7ab2a334e566b6deb9657d7d8370f591bfb81bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4341 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 diff --git a/examples/graph/img/UI/rightarrow.png b/examples/graph/img/UI/rightarrow.png deleted file mode 100644 index c3a209d8b0a58355305aae03b5520a1e070d8eab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4514 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 diff --git a/examples/graph/img/UI/zoomExtends.png b/examples/graph/img/UI/zoomExtends.png deleted file mode 100644 index 74595c6358448fbdcfbd62629084841ca7e8b8a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4464 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 this.constants.clustering.initialMaxNumberOfNodes) { + var zoomLevel = 38.8467 / (numberOfNodes - 14.50184) + 0.0116; // this is obtained from fitting a dataset from 5 points with scale levels that looked good. } else { var zoomLevel = 42.54117319 / (numberOfNodes + 39.31966387) + 0.1944405; // this is obtained from fitting a dataset from 5 points with scale levels that looked good. @@ -370,10 +393,10 @@ Graph.prototype.setOptions = function (options) { } } - if (options.UI) { - for (var prop in options.UI) { - if (options.UI.hasOwnProperty(prop)) { - this.constants.UI[prop] = options.UI[prop]; + if (options.navigationUI) { + for (var prop in options.navigationUI) { + if (options.navigationUI.hasOwnProperty(prop)) { + this.constants.navigationUI[prop] = options.navigationUI[prop]; } } } @@ -1041,7 +1064,7 @@ Graph.prototype.setSize = function(width, height) { this.frame.canvas.width = this.frame.canvas.clientWidth; this.frame.canvas.height = this.frame.canvas.clientHeight; - if (this.constants.UI.enabled == true) { + if (this.constants.navigationUI.enabled == true) { this._relocateUI(); } }; @@ -1562,6 +1585,7 @@ Graph.prototype._doStabilize = function() { stable = !this._isMoving(vmin); count++; } + this.zoomToFit(); // var end = new Date(); @@ -1611,7 +1635,7 @@ Graph.prototype._calculateForces = function() { // Gravity is required to keep separated groups from floating off // the forces are reset to zero in this loop by using _setForce instead // of _addForce - var gravity = 0.20 * this.forceFactor; + var gravity = 0.10 * this.forceFactor; for (i = 0; i < this.nodeIndices.length; i++) { node = nodes[this.nodeIndices[i]]; // gravity does not apply when we are in a pocket sector @@ -1796,10 +1820,10 @@ Graph.prototype._calculateForces = function() { * @private */ Graph.prototype._isMoving = function(vmin) { - // TODO: ismoving does not work well: should check the kinetic energy, not its velocity + var vminCorrected = vmin / this.scale; var nodes = this.nodes; for (var id in nodes) { - if (nodes.hasOwnProperty(id) && nodes[id].isMoving(vmin)) { + if (nodes.hasOwnProperty(id) && nodes[id].isMoving(vminCorrected)) { return true; } } @@ -1938,7 +1962,7 @@ Graph.prototype._loadSectorSystem = function() { "formationScale": 1.0, "drawingNode": undefined}; this.sectors["frozen"] = {}; - this.sectors["UI"] = {"nodes":{}, + this.sectors["navigationUI"] = {"nodes":{}, "edges":{}, "nodeIndices":[], "formationScale": 1.0, @@ -1971,7 +1995,7 @@ Graph.prototype._loadSelectionSystem = function() { /** - * Mixin the UI (User Interface) system and initialize the parameters required + * Mixin the navigationUI (User Interface) system and initialize the parameters required * * @private */ @@ -1982,7 +2006,7 @@ Graph.prototype._loadUISystem = function() { } } - if (this.constants.UI.enabled == true) { + if (this.constants.navigationUI.enabled == true) { this._loadUIElements(); this._createKeyBinds(); @@ -1990,17 +2014,17 @@ Graph.prototype._loadUISystem = function() { } /** - * this function exists to avoid errors when not loading the UI system + * this function exists to avoid errors when not loading the navigationUI system */ Graph.prototype._relocateUI = function() { - // empty, is overloaded by UI system + // empty, is overloaded by navigationUI system } /** - * * this function exists to avoid errors when not loading the UI system + * * this function exists to avoid errors when not loading the navigationUI system */ Graph.prototype._unHighlightAll = function() { - // empty, is overloaded by the UI system + // empty, is overloaded by the navigationUI system } diff --git a/src/graph/Node.js b/src/graph/Node.js index f5b125c0..8e49b26d 100644 --- a/src/graph/Node.js +++ b/src/graph/Node.js @@ -44,8 +44,8 @@ function Node(properties, imagelist, grouplist, constants) { this.y = 0; this.xFixed = false; this.yFixed = false; - this.horizontalAlignLeft = true; // these are for the UI - this.verticalAlignTop = true; // these are for the UI + this.horizontalAlignLeft = true; // these are for the navigationUI + this.verticalAlignTop = true; // these are for the navigationUI this.radius = constants.nodes.radius; this.baseRadiusValue = constants.nodes.radius; this.radiusFixed = false; @@ -151,7 +151,7 @@ Node.prototype.setProperties = function(properties, constants) { if (properties.y !== undefined) {this.y = properties.y;} if (properties.value !== undefined) {this.value = properties.value;} - // UI properties + // navigationUI properties if (properties.horizontalAlignLeft !== undefined) {this.horizontalAlignLeft = properties.horizontalAlignLeft;} if (properties.verticalAlignTop !== undefined) {this.verticalAlignTop = properties.verticalAlignTop;} if (properties.triggerFunction !== undefined) {this.triggerFunction = properties.triggerFunction;} @@ -406,9 +406,6 @@ Node.prototype.isFixed = function() { */ // TODO: replace this method with calculating the kinetic energy Node.prototype.isMoving = function(vmin) { -// return (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin || -// (!this.xFixed && Math.abs(this.fx) > this.minForce) || -// (!this.yFixed && Math.abs(this.fy) > this.minForce)); if (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin) { return true; } diff --git a/src/graph/SectorsMixin.js b/src/graph/SectorsMixin.js index a13cc037..53620978 100644 --- a/src/graph/SectorsMixin.js +++ b/src/graph/SectorsMixin.js @@ -72,14 +72,14 @@ var SectorMixin = { /** * This function sets the global references to nodes, edges and nodeIndices to - * those of the UI sector. + * those of the navigationUI sector. * * @private */ _switchToUISector : function() { - this.nodeIndices = this.sectors["UI"]["nodeIndices"]; - this.nodes = this.sectors["UI"]["nodes"]; - this.edges = this.sectors["UI"]["edges"]; + this.nodeIndices = this.sectors["navigationUI"]["nodeIndices"]; + this.nodes = this.sectors["navigationUI"]["nodes"]; + this.edges = this.sectors["navigationUI"]["edges"]; }, @@ -432,7 +432,7 @@ var SectorMixin = { /** - * This runs a function in the UI sector. + * This runs a function in the navigationUI sector. * * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors * | we don't pass the function itself because then the "this" is the window object diff --git a/src/graph/SelectionMixin.js b/src/graph/SelectionMixin.js index 368c19f5..e50fdb17 100644 --- a/src/graph/SelectionMixin.js +++ b/src/graph/SelectionMixin.js @@ -33,7 +33,7 @@ var SelectionMixin = { /** - * retrieve all nodes in the UI overlapping with given object + * retrieve all nodes in the navigationUI 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 @@ -80,7 +80,7 @@ var SelectionMixin = { /** - * Get the top UI node at the a specific point (like a click) + * Get the top navigationUI node at the a specific point (like a click) * * @param {{x: Number, y: Number}} pointer * @return {Node | null} node @@ -90,7 +90,7 @@ var SelectionMixin = { var screenPositionObject = this._pointerToScreenPositionObject(pointer); var overlappingNodes = this._getAllUINodesOverlappingWith(screenPositionObject); if (this.UIvisible && overlappingNodes.length > 0) { - return this.sectors["UI"]["nodes"][overlappingNodes[overlappingNodes.length - 1]]; + return this.sectors["navigationUI"]["nodes"][overlappingNodes[overlappingNodes.length - 1]]; } else { return null; @@ -106,7 +106,7 @@ var SelectionMixin = { * @private */ _getNodeAt : function (pointer) { - // we first check if this is an UI element + // we first check if this is an navigationUI element var positionObject = this._pointerToPositionObject(pointer); overlappingNodes = this._getAllNodesOverlappingWith(positionObject); @@ -223,7 +223,7 @@ var SelectionMixin = { /** - * handles the selection part of the touch, only for UI elements; + * handles the selection part of the touch, only for navigationUI elements; * Touch is triggered before tap, also before hold. Hold triggers after a while. * This is the most responsive solution * @@ -291,7 +291,7 @@ var SelectionMixin = { /** - * handle the onRelease event. These functions are here for the UI module. + * handle the onRelease event. These functions are here for the navigationUI module. * * @private */ diff --git a/src/graph/UIMixin.js b/src/graph/UIMixin.js index ba4fdeed..653c22c3 100644 --- a/src/graph/UIMixin.js +++ b/src/graph/UIMixin.js @@ -5,7 +5,7 @@ var UIMixin = { /** - * This function moves the UI if the canvas size has been changed. If the arugments + * This function moves the navigationUI if the canvas size has been changed. If the arugments * verticaAlignTop and horizontalAlignLeft are false, the correction will be made * * @private @@ -18,9 +18,9 @@ var UIMixin = { this.UIclientHeight = this.frame.canvas.clientHeight; var node = null; - for (var nodeId in this.sectors["UI"]["nodes"]) { - if (this.sectors["UI"]["nodes"].hasOwnProperty(nodeId)) { - node = this.sectors["UI"]["nodes"][nodeId]; + for (var nodeId in this.sectors["navigationUI"]["nodes"]) { + if (this.sectors["navigationUI"]["nodes"].hasOwnProperty(nodeId)) { + node = this.sectors["navigationUI"]["nodes"][nodeId]; if (!node.horizontalAlignLeft) { node.x -= xOffset; } @@ -34,15 +34,15 @@ var UIMixin = { /** - * Creation of the UI nodes. They are drawn over the rest of the nodes and are not affected by scale and translation - * they have a triggerFunction which is called on click. If the position of the UI is dependent + * Creation of the navigationUI nodes. They are drawn over the rest of the nodes and are not affected by scale and translation + * they have a triggerFunction which is called on click. If the position of the navigationUI is dependent * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false. * This means that the location will be corrected by the _relocateUI function on a size change of the canvas. * * @private */ _loadUIElements : function() { - var DIR = 'img/UI/'; + var DIR = this.constants.navigationUI.iconPath; this.UIclientWidth = this.frame.canvas.clientWidth; this.UIclientHeight = this.frame.canvas.clientHeight; if (this.UIclientWidth === undefined) { @@ -53,7 +53,7 @@ var UIMixin = { var intermediateOffset = 7; var UINodes = [ {id: 'UI_up', shape: 'image', image: DIR + 'uparrow.png', triggerFunction: "_moveUp", - verticalAlignTop: false, x: 45 + offset + intermediateOffset, y: this.UIclientHeight - 47 - offset}, + verticalAlignTop: false, x: 45 + offset + intermediateOffset, y: this.UIclientHeight - 45 - offset - intermediateOffset}, {id: 'UI_down', shape: 'image', image: DIR + 'downarrow.png', triggerFunction: "_moveDown", verticalAlignTop: false, x: 45 + offset + intermediateOffset, y: this.UIclientHeight - 15 - offset}, {id: 'UI_left', shape: 'image', image: DIR + 'leftarrow.png', triggerFunction: "_moveLeft", @@ -74,7 +74,7 @@ var UIMixin = { var nodeObj = null; for (var i = 0; i < UINodes.length; i++) { - nodeObj = this.sectors["UI"]['nodes']; + nodeObj = this.sectors["navigationUI"]['nodes']; nodeObj[UINodes[i]['id']] = new Node(UINodes[i], this.images, this.groups, this.constants); } }, @@ -88,8 +88,8 @@ var UIMixin = { * @private */ _highlightUIElement : function(elementId) { - if (this.sectors["UI"]["nodes"].hasOwnProperty(elementId)) { - this.sectors["UI"]["nodes"][elementId].clusterSize = 2; + if (this.sectors["navigationUI"]["nodes"].hasOwnProperty(elementId)) { + this.sectors["navigationUI"]["nodes"][elementId].clusterSize = 2; } }, @@ -101,14 +101,14 @@ var UIMixin = { * @private */ _unHighlightUIElement : function(elementId) { - if (this.sectors["UI"]["nodes"].hasOwnProperty(elementId)) { - this.sectors["UI"]["nodes"][elementId].clusterSize = 1; + if (this.sectors["navigationUI"]["nodes"].hasOwnProperty(elementId)) { + this.sectors["navigationUI"]["nodes"][elementId].clusterSize = 1; } }, /** - * toggle the visibility of the UI + * toggle the visibility of the navigationUI * * @private */ @@ -122,11 +122,11 @@ var UIMixin = { /** - * un-highlight (for lack of a better term) all UI elements + * un-highlight (for lack of a better term) all navigationUI elements * @private */ _unHighlightAll : function() { - for (var nodeId in this.sectors['UI']['nodes']) { + for (var nodeId in this.sectors['navigationUI']['nodes']) { this._unHighlightUIElement(nodeId); } }, @@ -153,7 +153,7 @@ var UIMixin = { */ _moveUp : function(event) { this._highlightUIElement("UI_up"); - this.yIncrement = this.constants.UI.yMovementSpeed; + this.yIncrement = this.constants.navigationUI.yMovementSpeed; this.start(); // if there is no node movement, the calculation wont be done this._preventDefault(event); }, @@ -165,7 +165,7 @@ var UIMixin = { */ _moveDown : function(event) { this._highlightUIElement("UI_down"); - this.yIncrement = -this.constants.UI.yMovementSpeed; + this.yIncrement = -this.constants.navigationUI.yMovementSpeed; this.start(); // if there is no node movement, the calculation wont be done this._preventDefault(event); }, @@ -177,7 +177,7 @@ var UIMixin = { */ _moveLeft : function(event) { this._highlightUIElement("UI_left"); - this.xIncrement = this.constants.UI.xMovementSpeed; + this.xIncrement = this.constants.navigationUI.xMovementSpeed; this.start(); // if there is no node movement, the calculation wont be done this._preventDefault(event); }, @@ -189,7 +189,7 @@ var UIMixin = { */ _moveRight : function(event) { this._highlightUIElement("UI_right"); - this.xIncrement = -this.constants.UI.xMovementSpeed; + this.xIncrement = -this.constants.navigationUI.xMovementSpeed; this.start(); // if there is no node movement, the calculation wont be done this._preventDefault(event); }, @@ -201,7 +201,7 @@ var UIMixin = { */ _zoomIn : function(event) { this._highlightUIElement("UI_plus"); - this.zoomIncrement = this.constants.UI.zoomMovementSpeed; + this.zoomIncrement = this.constants.navigationUI.zoomMovementSpeed; this.start(); // if there is no node movement, the calculation wont be done this._preventDefault(event); }, @@ -213,7 +213,7 @@ var UIMixin = { */ _zoomOut : function() { this._highlightUIElement("UI_min"); - this.zoomIncrement = -this.constants.UI.zoomMovementSpeed; + this.zoomIncrement = -this.constants.navigationUI.zoomMovementSpeed; this.start(); // if there is no node movement, the calculation wont be done this._preventDefault(event); }, diff --git a/src/util.js b/src/util.js index 9036691e..e4feacc2 100644 --- a/src/util.js +++ b/src/util.js @@ -671,3 +671,31 @@ util.option.asElement = function (value, defaultValue) { return value || defaultValue || null; }; + + +/** + * Compare two numbers and return the lowest, non-negative number. + * + * @param {number} number1 + * @param {number} number2 + * @returns {number} | number1 or number2, the lowest positive number. If both negative, return -1 + * @private + */ +util._getLowestPositiveNumber = function(number1,number2) { + if (number1 >= 0) { + if (number2 >= 0) { + return (number1 < number2) ? number1 : number2; + } + else { + return number1; + } + } + else { + if (number2 >= 0) { + return number2; + } + else { + return -1; + } + } +}