From 2591b8abae6d3e8fc3fc1aafe2115a4dada23b96 Mon Sep 17 00:00:00 2001 From: Alex de Mulder Date: Wed, 19 Feb 2014 14:47:42 +0100 Subject: [PATCH 1/3] added icons for new examples. minor tweaks (code styling) --- .../18_fully_random_nodes_clustering.png | Bin 0 -> 19472 bytes .../graph/19_scale_free_graph_clustering.png | Bin 0 -> 19437 bytes img/gallery/graph/20_navigation.png | Bin 0 -> 19038 bytes img/gallery/graph/21_data_manipulation.png | Bin 0 -> 20328 bytes img/gallery/graph/22_les_miserables.png | Bin 0 -> 25530 bytes index.html | 23 ++++++++++++++++++ src/graph/Graph.js | 3 +-- 7 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 img/gallery/graph/18_fully_random_nodes_clustering.png create mode 100644 img/gallery/graph/19_scale_free_graph_clustering.png create mode 100644 img/gallery/graph/20_navigation.png create mode 100644 img/gallery/graph/21_data_manipulation.png create mode 100644 img/gallery/graph/22_les_miserables.png diff --git a/img/gallery/graph/18_fully_random_nodes_clustering.png b/img/gallery/graph/18_fully_random_nodes_clustering.png new file mode 100644 index 0000000000000000000000000000000000000000..6356994e58febc40e293b23cc7f5ce359ba8644e GIT binary patch literal 19472 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 literal 0 HcmV?d00001 diff --git a/img/gallery/graph/19_scale_free_graph_clustering.png b/img/gallery/graph/19_scale_free_graph_clustering.png new file mode 100644 index 0000000000000000000000000000000000000000..70505e2b2e5ed8f58e33f214314cb12ae54116cc GIT binary patch literal 19437 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 literal 0 HcmV?d00001 diff --git a/img/gallery/graph/20_navigation.png b/img/gallery/graph/20_navigation.png new file mode 100644 index 0000000000000000000000000000000000000000..3cc4dc06ccb4c582db43917ea8c3041ab376de28 GIT binary patch literal 19038 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 AmjD0& literal 0 HcmV?d00001 diff --git a/img/gallery/graph/21_data_manipulation.png b/img/gallery/graph/21_data_manipulation.png new file mode 100644 index 0000000000000000000000000000000000000000..26e9f1d30512106b30deddb20ddfb299aab31ee5 GIT binary patch literal 20328 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 P|AnWermsp+u@Ct##;MLD literal 0 HcmV?d00001 diff --git a/img/gallery/graph/22_les_miserables.png b/img/gallery/graph/22_les_miserables.png new file mode 100644 index 0000000000000000000000000000000000000000..e09225c4ab199312776eb37bd24d062b90affc81 GIT binary patch literal 25530 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 info + + +
diff --git a/src/graph/Graph.js b/src/graph/Graph.js index c27a9674..cf2234fb 100644 --- a/src/graph/Graph.js +++ b/src/graph/Graph.js @@ -40,11 +40,10 @@ function Graph (container, data, options) { image: undefined, widthMin: 16, // px widthMax: 64, // px - fixed:false, + fixed: false, fontColor: 'black', fontSize: 14, // px fontFace: 'verdana', -// fontFace: 'arial', color: { border: '#2B7CE9', background: '#97C2FC', From 8a3c45eab9fbb298b5a73cd5303c4dadf556abd6 Mon Sep 17 00:00:00 2001 From: Alex de Mulder Date: Thu, 20 Feb 2014 16:56:02 +0100 Subject: [PATCH 2/3] added hierarchical layout --- Jakefile.js | 2 + docs/graph.html | 84 +++++- examples/graph/17_network_info.html | 10 +- .../graph/19_scale_free_graph_clustering.html | 2 +- examples/graph/23_hierarchical_layout.html | 113 ++++++++ .../24_hierarchical_layout_userdefined.html | 139 +++++++++ examples/graph/index.html | 2 + img/gallery/graph/23_hierarchical_layout.png | Bin 0 -> 19986 bytes .../24_hierarchical_layout_predefined.png | Bin 0 -> 18435 bytes index.html | 34 ++- src/graph/Graph.js | 83 ++++-- src/graph/Node.js | 6 +- src/graph/graphMixins/ClusterMixin.js | 4 +- .../graphMixins/HierarchicalLayoutMixin.js | 263 ++++++++++++++++++ src/graph/graphMixins/ManipulationMixin.js | 2 +- src/graph/graphMixins/MixinLoader.js | 27 +- src/graph/graphMixins/physics/BarnesHut.js | 2 +- .../physics/HierarchialRepulsion.js | 64 +++++ src/graph/graphMixins/physics/PhysicsMixin.js | 6 +- 19 files changed, 791 insertions(+), 52 deletions(-) create mode 100644 examples/graph/23_hierarchical_layout.html create mode 100644 examples/graph/24_hierarchical_layout_userdefined.html create mode 100644 img/gallery/graph/23_hierarchical_layout.png create mode 100644 img/gallery/graph/24_hierarchical_layout_predefined.png create mode 100644 src/graph/graphMixins/HierarchicalLayoutMixin.js create mode 100644 src/graph/graphMixins/physics/HierarchialRepulsion.js diff --git a/Jakefile.js b/Jakefile.js index a36a934c..ad17bc12 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -83,8 +83,10 @@ task('build', {async: true}, function () { './src/graph/Groups.js', './src/graph/Images.js', './src/graph/graphMixins/physics/PhysicsMixin.js', + './src/graph/graphMixins/physics/HierarchialRepulsion.js', './src/graph/graphMixins/physics/BarnesHut.js', './src/graph/graphMixins/physics/Repulsion.js', + './src/graph/graphMixins/HierarchicalLayoutMixin.js', './src/graph/graphMixins/ManipulationMixin.js', './src/graph/graphMixins/SectorsMixin.js', './src/graph/graphMixins/ClusterMixin.js', diff --git a/docs/graph.html b/docs/graph.html index 461a2841..bac13b3f 100644 --- a/docs/graph.html +++ b/docs/graph.html @@ -58,6 +58,7 @@
  • Clustering
  • Navigation controls
  • Keyboard navigation
  • +
  • Hierarchical layout
  • Methods
  • @@ -291,10 +292,10 @@ var nodes = [ - fixed + allowedToMove Boolean - false - If fixed is true, then the node will not move from its supplied position. + true + If allowedToMove is false, then the node will not move from its supplied position. If only an x position has been supplied, it is only fixed in the x-direction. The same holds for y. If both x and y have been defined, the node will not move. @@ -334,8 +335,15 @@ var nodes = [ string no Url of an image. Only applicable when the shape of the node is - image. - + image. + + + + level + number + -1 + This level is used in the hierarchical layout. If this is not selected, the level does not do anything. + radius @@ -836,10 +844,10 @@ var options = { Default border color of the node when selected. - fixed + allowedToMove Boolean false - If fixed is true, then the node will not move from its supplied position. + If allowedToMove is false, then the node will not move from its supplied position. If only an x position has been supplied, it is only fixed in the x-direction. The same holds for y. If both x and y have been defined, the node will not move. @@ -875,6 +883,12 @@ var options = { Default image url for the nodes. only applicable to shape image. + + level + number + -1 + This level is used in the hierarchical layout. If this is not selected, the level does not do anything. + widthMin Number @@ -1329,13 +1343,11 @@ var options: { var options: { dataManipulation: true, onAdd: function(data,callback) { - // fixed must be false because we define a set x and y position. - // If fixed is not false, the node cannot move. /** data = {id: random unique id, * label: new, * x: x position of click (canvas space), * y: y position of click (canvas space), - * fixed: false + * allowedToMove: true * }; */ var newData = {..}; // alter the data as you want. @@ -1658,6 +1670,58 @@ var options: { + +

    Hierarchical layout

    +

    + The graph can be used to display nodes in a hierarchical way. This can be determined automatically, based on the amount of edges connected to each node, or defined by the user. + If the user wants to manually determine the hierarchy, each node has to be supplied with a level (from 0 being heighest to n). The automatic method + is shown in example 23 and the user-defined method is shown in example 24. +

    + +
    +// simple use of the hierarchical layout
    +var options: {
    +    hierarchicalLayout: true
    +}
    +
    +// advanced configuration for keyboard controls
    +var options: {
    +    hierarchicalLayout: {
    +      enabled:false,
    +      levelSeparation: 150,
    +      nodeSpacing: 100
    +    }
    +}
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDefaultDescription
    enabledBooleanfalseEnable or disable the hierarchical layout. +
    levelSeparationNumber150This defines the space between levels (in the Y-direction).
    nodeSpacingNumber100This defines the space between nodes in the same level (in the X-direction).

    Methods

    Graph supports the following methods. diff --git a/examples/graph/17_network_info.html b/examples/graph/17_network_info.html index ca97d75a..0b7e033f 100644 --- a/examples/graph/17_network_info.html +++ b/examples/graph/17_network_info.html @@ -96,11 +96,11 @@ var x = - mygraph.clientWidth / 2 + 50; var y = - mygraph.clientHeight / 2 + 50; var step = 70; - nodes.push({id: 1000, x: x, y: y, label: 'Internet', group: 'internet', value: 1, fixed:true}); - nodes.push({id: 1001, x: x, y: y + step, label: 'Switch', group: 'switch', value: 1, fixed:true}); - nodes.push({id: 1002, x: x, y: y + 2 * step, label: 'Server', group: 'server', value: 1, fixed:true}); - nodes.push({id: 1003, x: x, y: y + 3 * step, label: 'Computer', group: 'desktop', value: 1, fixed:true}); - nodes.push({id: 1004, x: x, y: y + 4 * step, label: 'Smartphone', group: 'mobile', value: 1, fixed:true}); + nodes.push({id: 1000, x: x, y: y, label: 'Internet', group: 'internet', value: 1}); + nodes.push({id: 1001, x: x, y: y + step, label: 'Switch', group: 'switch', value: 1}); + nodes.push({id: 1002, x: x, y: y + 2 * step, label: 'Server', group: 'server', value: 1}); + nodes.push({id: 1003, x: x, y: y + 3 * step, label: 'Computer', group: 'desktop', value: 1}); + nodes.push({id: 1004, x: x, y: y + 4 * step, label: 'Smartphone', group: 'mobile', value: 1}); // create a graph var container = document.getElementById('mygraph'); diff --git a/examples/graph/19_scale_free_graph_clustering.html b/examples/graph/19_scale_free_graph_clustering.html index 640e2207..ef3de8cd 100644 --- a/examples/graph/19_scale_free_graph_clustering.html +++ b/examples/graph/19_scale_free_graph_clustering.html @@ -107,7 +107,7 @@

    Clustering - Scale-Free-Graph

    - This example shows therandomly generated scale-free-graph set of nodes and connected edges from example 2. + This example shows the randomly generated scale-free-graph set of nodes and connected edges from example 2. By clicking the checkbox you can turn clustering on and off. If you increase the number of nodes to a value higher than 100, automatic clustering is used before the initial draw (assuming the checkbox is checked).
    diff --git a/examples/graph/23_hierarchical_layout.html b/examples/graph/23_hierarchical_layout.html new file mode 100644 index 00000000..5923e21e --- /dev/null +++ b/examples/graph/23_hierarchical_layout.html @@ -0,0 +1,113 @@ + + + + Graph | Random nodes + + + + + + + + + +

    Hierarchical Layout - Scale-Free-Graph

    +
    + This example shows the randomly generated scale-free-graph set of nodes and connected edges from example 2. + In this example, hierarchical layout has been enabled and the vertical levels are determine automatically. +
    +
    +
    + + + +
    +
    + +
    + +

    + + diff --git a/examples/graph/24_hierarchical_layout_userdefined.html b/examples/graph/24_hierarchical_layout_userdefined.html new file mode 100644 index 00000000..0605485a --- /dev/null +++ b/examples/graph/24_hierarchical_layout_userdefined.html @@ -0,0 +1,139 @@ + + + + Graph | Random nodes + + + + + + + + + +

    Hierarchical Layout - User-defined

    +
    + This example shows a user-defined hierarchical layout. If the user defines levels for nodes but does not do so for all nodes, an alert will show up and hierarchical layout will be disabled. Either all or none can be defined. +
    +
    + +
    + +

    + + diff --git a/examples/graph/index.html b/examples/graph/index.html index 4b38888d..db69b9ca 100644 --- a/examples/graph/index.html +++ b/examples/graph/index.html @@ -34,6 +34,8 @@

    20_navigation.html

    21_data_manipulation.html

    22_les_miserables.html

    +

    23_hierarchical_layout.html

    +

    24_hierarchical_layout_predefined.html

    graphviz_gallery.html

    diff --git a/img/gallery/graph/23_hierarchical_layout.png b/img/gallery/graph/23_hierarchical_layout.png new file mode 100644 index 0000000000000000000000000000000000000000..7d5035ed2e586b0e23cd99fcdbe4c95a50cb81b9 GIT binary patch literal 19986 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 literal 0 HcmV?d00001 diff --git a/img/gallery/graph/24_hierarchical_layout_predefined.png b/img/gallery/graph/24_hierarchical_layout_predefined.png new file mode 100644 index 0000000000000000000000000000000000000000..3499e9898d689ee9fd70a2d59c669b966f3d1700 GIT binary patch literal 18435 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 FzX5Y#_D28! literal 0 HcmV?d00001 diff --git a/index.html b/index.html index c58075da..8e7be2a1 100644 --- a/index.html +++ b/index.html @@ -271,26 +271,46 @@ The source code of the examples can be found in the + + + + +
    diff --git a/src/graph/Graph.js b/src/graph/Graph.js index cf2234fb..e517e99f 100644 --- a/src/graph/Graph.js +++ b/src/graph/Graph.js @@ -44,6 +44,7 @@ function Graph (container, data, options) { fontColor: 'black', fontSize: 14, // px fontFace: 'verdana', + level: -1, color: { border: '#2B7CE9', background: '#97C2FC', @@ -77,18 +78,26 @@ function Graph (container, data, options) { enabled: true, theta: 1 / 0.6, // inverted to save time during calculation gravitationalConstant: -2000, - centralGravity: 0.1, + centralGravity: 0.3, springLength: 100, springConstant: 0.05, damping: 0.09 }, repulsion: { centralGravity: 0.1, - springLength: 50, + springLength: 200, springConstant: 0.05, nodeDistance: 100, damping: 0.09 }, + hierarchicalRepulsion: { + enabled: false, + centralGravity: 0.0, + springLength: 100, + springConstant: 0.01, + nodeDistance: 60, + damping: 0.09 + }, damping: null, centralGravity: null, springLength: null, @@ -127,6 +136,11 @@ function Graph (container, data, options) { enabled: false, initiallyVisible: false }, + hierarchicalLayout: { + enabled:false, + levelSeparation: 150, + nodeSpacing: 100 + }, smoothCurves: true, maxVelocity: 10, minVelocity: 0.1, // px/s @@ -142,7 +156,6 @@ function Graph (container, data, options) { graph._redraw(); }); - // keyboard navigation variables this.xIncrement = 0; this.yIncrement = 0; @@ -159,6 +172,9 @@ function Graph (container, data, options) { this._loadClusterSystem(); // load the selection system. (mandatory, required by Graph) this._loadSelectionSystem(); + // load the selection system. (mandatory, required by Graph) + this._loadHierarchySystem(); + // apply options this.setOptions(options); @@ -220,10 +236,17 @@ function Graph (container, data, options) { this.timer = undefined; // Scheduling function. Is definded in this.start(); // load data (the disable start variable will be the same as the enabled clustering) - this.setData(data,this.constants.clustering.enabled); + this.setData(data,this.constants.clustering.enabled || this.constants.hierarchicalLayout.enabled); + + // hierarchical layout + if (this.constants.hierarchicalLayout.enabled == true) { + this._setupHierarchicalLayout(); + } + else { + // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here. + this.zoomToFit(true,this.constants.clustering.enabled); + } - // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here. - this.zoomToFit(true,this.constants.clustering.enabled); // if clustering is disabled, the simulation will have started in the setData function if (this.constants.clustering.enabled) { @@ -231,6 +254,9 @@ function Graph (container, data, options) { } } + + + /** * Get the script path where the vis.js library is located * @@ -261,12 +287,14 @@ Graph.prototype._getScriptPath = function() { */ Graph.prototype._getRange = function() { var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node; - for (var i = 0; i < this.nodeIndices.length; i++) { - node = this.nodes[this.nodeIndices[i]]; - if (minX > (node.x - node.width)) {minX = node.x - node.width;} - if (maxX < (node.x + node.width)) {maxX = node.x + node.width;} - if (minY > (node.y - node.height)) {minY = node.y - node.height;} - if (maxY < (node.y + node.height)) {maxY = node.y + node.height;} + for (var nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + node = this.nodes[nodeId]; + if (minX > (node.x - node.width)) {minX = node.x - node.width;} + if (maxX < (node.x + node.width)) {maxX = node.x + node.width;} + if (minY > (node.y - node.height)) {minY = node.y - node.height;} + if (maxY < (node.y + node.height)) {maxY = node.y + node.height;} + } } return {minX: minX, maxX: maxX, minY: minY, maxY: maxY}; }; @@ -310,11 +338,11 @@ Graph.prototype.zoomToFit = function(initialZoom, disableStart) { initialZoom = false; } - var numberOfNodes = this.nodeIndices.length; var range = this._getRange(); var zoomLevel; if (initialZoom == true) { + var numberOfNodes = this.nodeIndices.length; if (this.constants.smoothCurves == true) { if (this.constants.clustering.enabled == true && numberOfNodes >= this.constants.clustering.initialMaxNodes) { @@ -352,6 +380,7 @@ Graph.prototype.zoomToFit = function(initialZoom, disableStart) { this._setScale(zoomLevel); this._centerGraph(range); if (disableStart == false || disableStart === undefined) { + this.moving = true; this.start(); } }; @@ -469,6 +498,18 @@ Graph.prototype.setOptions = function (options) { } } + if (options.hierarchicalLayout) { + this.constants.hierarchicalLayout.enabled = true; + for (prop in options.hierarchicalLayout) { + if (options.hierarchicalLayout.hasOwnProperty(prop)) { + this.constants.hierarchicalLayout[prop] = options.hierarchicalLayout[prop]; + } + } + } + else if (options.hierarchicalLayout !== undefined) { + this.constants.hierarchicalLayout.enabled = false; + } + if (options.clustering) { this.constants.clustering.enabled = true; for (prop in options.clustering) { @@ -719,7 +760,6 @@ Graph.prototype._createKeyBinds = function() { this.mousetrap.bind("pagedown",this._zoomOut.bind(me),"keydown"); this.mousetrap.bind("pagedown",this._stopZoom.bind(me), "keyup"); } -// this.mousetrap.bind("b",this._toggleBarnesHut.bind(me)); if (this.constants.dataManipulation.enabled == true) { this.mousetrap.bind("escape",this._createManipulatorBar.bind(me)); @@ -1725,7 +1765,7 @@ Graph.prototype._isMoving = function(vmin) { * @private */ Graph.prototype._discreteStepNodes = function() { - var interval = 0.5; + var interval = 0.65; var nodes = this.nodes; var nodeId; @@ -1776,7 +1816,6 @@ Graph.prototype._physicsTick = function() { Graph.prototype._animationStep = function() { // reset the timer so a new scheduled animation step can be set this.timer = undefined; - // handle the keyboad movement this._handleNavigation(); @@ -1854,7 +1893,11 @@ Graph.prototype.toggleFreeze = function() { -Graph.prototype._configureSmoothCurves = function() { +Graph.prototype._configureSmoothCurves = function(disableStart) { + if (disableStart === undefined) { + disableStart = true; + } + if (this.constants.smoothCurves == true) { this._createBezierNodes(); } @@ -1869,8 +1912,10 @@ Graph.prototype._configureSmoothCurves = function() { } } this._updateCalculationNodes(); - this.moving = true; - this.start(); + if (!disableStart) { + this.moving = true; + this.start(); + } }; Graph.prototype._createBezierNodes = function() { diff --git a/src/graph/Node.js b/src/graph/Node.js index c6700d1a..f955b2e2 100644 --- a/src/graph/Node.js +++ b/src/graph/Node.js @@ -53,6 +53,7 @@ function Node(properties, imagelist, grouplist, constants) { this.radiusFixed = false; this.radiusMin = constants.nodes.radiusMin; this.radiusMax = constants.nodes.radiusMax; + this.level = -1; this.imagelist = imagelist; this.grouplist = grouplist; @@ -144,6 +145,7 @@ Node.prototype.setProperties = function(properties, constants) { if (properties.x !== undefined) {this.x = properties.x;} if (properties.y !== undefined) {this.y = properties.y;} if (properties.value !== undefined) {this.value = properties.value;} + if (properties.level !== undefined) {this.level = properties.level;} // physics @@ -189,8 +191,8 @@ Node.prototype.setProperties = function(properties, constants) { } } - this.xFixed = this.xFixed || (properties.x !== undefined && properties.fixed); - this.yFixed = this.yFixed || (properties.y !== undefined && properties.fixed); + this.xFixed = this.xFixed || (properties.x !== undefined && !properties.allowedToMove); + this.yFixed = this.yFixed || (properties.y !== undefined && !properties.allowedToMove); this.radiusFixed = this.radiusFixed || (properties.radius !== undefined); if (this.shape == 'image') { diff --git a/src/graph/graphMixins/ClusterMixin.js b/src/graph/graphMixins/ClusterMixin.js index e0de527a..e20fa85c 100644 --- a/src/graph/graphMixins/ClusterMixin.js +++ b/src/graph/graphMixins/ClusterMixin.js @@ -966,11 +966,11 @@ var ClusterMixin = { } } - /* Debug Override */ +// /* Debug Override */ // for (nodeId in this.nodes) { // if (this.nodes.hasOwnProperty(nodeId)) { // node = this.nodes[nodeId]; -// node.label = String(node.fx).concat(",",node.fy); +// node.label = String(node.level); // } // } diff --git a/src/graph/graphMixins/HierarchicalLayoutMixin.js b/src/graph/graphMixins/HierarchicalLayoutMixin.js new file mode 100644 index 00000000..5c1497c4 --- /dev/null +++ b/src/graph/graphMixins/HierarchicalLayoutMixin.js @@ -0,0 +1,263 @@ +var HierarchicalLayoutMixin = { + + + /** + * This is the main function to layout the nodes in a hierarchical way. + * It checks if the node details are supplied correctly + * + * @private + */ + _setupHierarchicalLayout : function() { + if (this.constants.hierarchicalLayout.enabled == true) { + + // get the size of the largest hubs and check if the user has defined a level for a node. + var hubsize = 0; + var node, nodeId; + var definedLevel = false; + var undefinedLevel = false; + + for (nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + node = this.nodes[nodeId]; + if (node.level != -1) { + definedLevel = true; + } + else { + undefinedLevel = true; + } + if (hubsize < node.edges.length) { + hubsize = node.edges.length; + } + } + } + + // if the user defined some levels but not all, alert and run without hierarchical layout + if (undefinedLevel == true && definedLevel == true) { + alert("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.") + this.zoomToFit(true,this.constants.clustering.enabled); + if (!this.constants.clustering.enabled) { + this.start(); + } + } + else { + // setup the system to use hierarchical method. + this._changeConstants(); + + // define levels if undefined by the users. Based on hubsize + if (undefinedLevel == true) { + this._determineLevels(hubsize); + } + // check the distribution of the nodes per level. + var distribution = this._getDistribution(); + + // place the nodes on the canvas. This also stablilizes the system. + this._placeNodesByHierarchy(distribution); + + // start the simulation. + this.start(); + } + } + }, + + + /** + * This function places the nodes on the canvas based on the hierarchial distribution. + * + * @param {Object} distribution | obtained by the function this._getDistribution() + * @private + */ + _placeNodesByHierarchy : function(distribution) { + var nodeId, node; + + // start placing all the level 0 nodes first. Then recursively position their branches. + for (nodeId in distribution[0].nodes) { + if (distribution[0].nodes.hasOwnProperty(nodeId)) { + node = distribution[0].nodes[nodeId]; + if (node.xFixed) { + node.x = distribution[0].minPos; + distribution[0].minPos += distribution[0].nodeSpacing; + node.xFixed = false; + } + this._placeBranchNodes(node.edges,node.id,distribution,node.level); + } + } + + // give the nodes a defined width so the zoomToFit can be used. This size is arbitrary. + for (nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + node = this.nodes[nodeId]; + node.width = 100; + node.height = 100; + } + } + + // stabilize the system after positioning. This function calls zoomToFit. + this._doStabilize(); + + // reset the arbitrary width and height we gave the nodes. + for (nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + this.nodes[nodeId]._reset(); + } + } + }, + + + /** + * This function get the distribution of levels based on hubsize + * + * @returns {Object} + * @private + */ + _getDistribution : function() { + var distribution = {}; + var nodeId, node; + + // we fix Y because the hierarchy is vertical, we fix X so we do not give a node an x position for a second time. + // the fix of X is removed after the x value has been set. + for (nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + node = this.nodes[nodeId]; + node.xFixed = true; + node.yFixed = true; + node.y = this.constants.hierarchicalLayout.levelSeparation*node.level; + if (!distribution.hasOwnProperty(node.level)) { + distribution[node.level] = {amount: 0, nodes: {}, minPos:0, nodeSpacing:0}; + } + distribution[node.level].amount += 1; + distribution[node.level].nodes[node.id] = node; + } + } + + // determine the largest amount of nodes of all levels + var maxCount = 0; + for (var level in distribution) { + if (distribution.hasOwnProperty(level)) { + if (maxCount < distribution[level].amount) { + maxCount = distribution[level].amount; + } + } + } + + // set the initial position and spacing of each nodes accordingly + for (var level in distribution) { + if (distribution.hasOwnProperty(level)) { + distribution[level].nodeSpacing = (maxCount + 1) * this.constants.hierarchicalLayout.nodeSpacing; + distribution[level].nodeSpacing /= (distribution[level].amount + 1); + distribution[level].minPos = distribution[level].nodeSpacing - (0.5 * (distribution[level].amount + 1) * distribution[level].nodeSpacing); + } + } + + return distribution; + }, + + + /** + * this function allocates nodes in levels based on the recursive branching from the largest hubs. + * + * @param hubsize + * @private + */ + _determineLevels : function(hubsize) { + var nodeId, node; + + // determine hubs + for (nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + node = this.nodes[nodeId]; + if (node.edges.length == hubsize) { + node.level = 0; + } + } + } + + // branch from hubs + for (nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + node = this.nodes[nodeId]; + if (node.level == 0) { + this._setLevel(1,node.edges,node.id); + } + } + } + }, + + + /** + * Since hierarchical layout does not support: + * - smooth curves (based on the physics), + * - clustering (based on dynamic node counts) + * + * We disable both features so there will be no problems. + * + * @private + */ + _changeConstants : function() { + this.constants.clustering.enabled = false; + this.constants.physics.barnesHut.enabled = false; + this.constants.physics.hierarchicalRepulsion.enabled = true; + this._loadSelectedForceSolver(); + this.constants.smoothCurves = false; + this._configureSmoothCurves(); + }, + + + /** + * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes + * on a X position that ensures there will be no overlap. + * + * @param edges + * @param parentId + * @param distribution + * @param parentLevel + * @private + */ + _placeBranchNodes : function(edges, parentId, distribution, parentLevel) { + for (var i = 0; i < edges.length; i++) { + var childNode = null; + if (edges[i].toId == parentId) { + childNode = edges[i].from; + } + else { + childNode = edges[i].to; + } + + // if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here. + if (childNode.xFixed && childNode.level > parentLevel) { + childNode.xFixed = false; + childNode.x = distribution[childNode.level].minPos; + distribution[childNode.level].minPos += distribution[childNode.level].nodeSpacing; + if (childNode.edges.length > 1) { + this._placeBranchNodes(childNode.edges,childNode.id,distribution,childNode.level); + } + } + } + }, + + + /** + * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level. + * + * @param level + * @param edges + * @param parentId + * @private + */ + _setLevel : function(level, edges, parentId) { + for (var i = 0; i < edges.length; i++) { + var childNode = null; + if (edges[i].toId == parentId) { + childNode = edges[i].from; + } + else { + childNode = edges[i].to; + } + if (childNode.level == -1 || childNode.level > level) { + childNode.level = level; + if (edges.length > 1) { + this._setLevel(level+1, childNode.edges, childNode.id); + } + } + } + } +}; \ No newline at end of file diff --git a/src/graph/graphMixins/ManipulationMixin.js b/src/graph/graphMixins/ManipulationMixin.js index d1dfc0c1..cc3ca3f8 100644 --- a/src/graph/graphMixins/ManipulationMixin.js +++ b/src/graph/graphMixins/ManipulationMixin.js @@ -288,7 +288,7 @@ var manipulationMixin = { _addNode : function() { if (this._selectionIsEmpty() && this.editMode == true) { var positionObject = this._pointerToPositionObject(this.pointerPosition); - var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",fixed:false}; + var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",allowedToMove:true}; if (this.triggerFunctions.add) { if (this.triggerFunctions.add.length == 2) { var me = this; diff --git a/src/graph/graphMixins/MixinLoader.js b/src/graph/graphMixins/MixinLoader.js index f025b08a..e31dd846 100644 --- a/src/graph/graphMixins/MixinLoader.js +++ b/src/graph/graphMixins/MixinLoader.js @@ -55,24 +55,35 @@ var graphMixinLoaders = { // this overloads the this._calculateNodeForces if (this.constants.physics.barnesHut.enabled == true) { this._clearMixin(repulsionMixin); + this._clearMixin(hierarchalRepulsionMixin); this.constants.physics.centralGravity = this.constants.physics.barnesHut.centralGravity; this.constants.physics.springLength = this.constants.physics.barnesHut.springLength; this.constants.physics.springConstant = this.constants.physics.barnesHut.springConstant; this.constants.physics.damping = this.constants.physics.barnesHut.damping; - this.constants.physics.springGrowthPerMass = this.constants.physics.barnesHut.springGrowthPerMass; this._loadMixin(barnesHutMixin); } + else if (this.constants.physics.hierarchicalRepulsion.enabled == true) { + this._clearMixin(barnesHutMixin); + this._clearMixin(repulsionMixin); + + this.constants.physics.centralGravity = this.constants.physics.hierarchicalRepulsion.centralGravity; + this.constants.physics.springLength = this.constants.physics.hierarchicalRepulsion.springLength; + this.constants.physics.springConstant = this.constants.physics.hierarchicalRepulsion.springConstant; + this.constants.physics.damping = this.constants.physics.hierarchicalRepulsion.damping; + + this._loadMixin(hierarchalRepulsionMixin); + } else { this._clearMixin(barnesHutMixin); + this._clearMixin(hierarchalRepulsionMixin); this.barnesHutTree = undefined; this.constants.physics.centralGravity = this.constants.physics.repulsion.centralGravity; this.constants.physics.springLength = this.constants.physics.repulsion.springLength; this.constants.physics.springConstant = this.constants.physics.repulsion.springConstant; this.constants.physics.damping = this.constants.physics.repulsion.damping; - this.constants.physics.springGrowthPerMass = this.constants.physics.repulsion.springGrowthPerMass; this._loadMixin(repulsionMixin); } @@ -214,6 +225,16 @@ var graphMixinLoaders = { if (this.constants.navigation.enabled == true) { this._loadNavigationElements(); } - } + }, + + + /** + * Mixin the hierarchical layout system. + * + * @private + */ + _loadHierarchySystem : function() { + this._loadMixin(HierarchicalLayoutMixin); + } } diff --git a/src/graph/graphMixins/physics/BarnesHut.js b/src/graph/graphMixins/physics/BarnesHut.js index 9e658c8c..5a06e26d 100644 --- a/src/graph/graphMixins/physics/BarnesHut.js +++ b/src/graph/graphMixins/physics/BarnesHut.js @@ -56,7 +56,7 @@ var barnesHutMixin = { if (distance * parentBranch.calcSize > this.constants.physics.barnesHut.theta) { // duplicate code to reduce function calls to speed up program if (distance == 0) { - distance = 0.5*Math.random(); + distance = 0.1*Math.random(); dx = distance; } var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.mass / (distance * distance * distance); diff --git a/src/graph/graphMixins/physics/HierarchialRepulsion.js b/src/graph/graphMixins/physics/HierarchialRepulsion.js new file mode 100644 index 00000000..bc6b55c8 --- /dev/null +++ b/src/graph/graphMixins/physics/HierarchialRepulsion.js @@ -0,0 +1,64 @@ +/** + * Created by Alex on 2/10/14. + */ + +var hierarchalRepulsionMixin = { + + + /** + * Calculate the forces the nodes apply on eachother based on a repulsion field. + * This field is linearly approximated. + * + * @private + */ + _calculateNodeForces : function() { + var dx, dy, distance, fx, fy, combinedClusterSize, + repulsingForce, node1, node2, i, j; + + var nodes = this.calculationNodes; + var nodeIndices = this.calculationNodeIndices; + + // approximation constants + var b = 5; + var a_base = 0.5*-b; + + + // repulsing forces between nodes + var nodeDistance = this.constants.physics.repulsion.nodeDistance; + var minimumDistance = nodeDistance; + + // we loop from i over all but the last entree in the array + // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j + for (i = 0; i < nodeIndices.length-1; i++) { + + node1 = nodes[nodeIndices[i]]; + for (j = i+1; j < nodeIndices.length; j++) { + node2 = nodes[nodeIndices[j]]; + + dx = node2.x - node1.x; + dy = node2.y - node1.y; + distance = Math.sqrt(dx * dx + dy * dy); + + var a = a_base / minimumDistance; + if (distance < 2*minimumDistance) { + repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)) + + // normalize force with + if (distance == 0) { + distance = 0.01; + } + else { + repulsingForce = repulsingForce/distance; + } + fx = dx * repulsingForce; + fy = dy * repulsingForce; + + node1.fx -= fx; + node1.fy -= fy; + node2.fx += fx; + node2.fy += fy; + } + } + } + } +} \ No newline at end of file diff --git a/src/graph/graphMixins/physics/PhysicsMixin.js b/src/graph/graphMixins/physics/PhysicsMixin.js index c5513761..ad24599c 100644 --- a/src/graph/graphMixins/physics/PhysicsMixin.js +++ b/src/graph/graphMixins/physics/PhysicsMixin.js @@ -122,7 +122,7 @@ var physicsMixin = { node = nodes[this.calculationNodeIndices[i]]; node.damping = this.constants.physics.damping; // possibly add function to alter damping properties of clusters. // gravity does not apply when we are in a pocket sector - if (this._sector() == "default") { + if (this._sector() == "default" && gravity != 0) { dx = -node.x; dy = -node.y; distance = Math.sqrt(dx*dx + dy*dy); @@ -164,6 +164,10 @@ var physicsMixin = { dy = (edge.from.y - edge.to.y); length = Math.sqrt(dx * dx + dy * dy); + if (length == 0) { + length = 0.01; + } + springForce = this.constants.physics.springConstant * (edgeLength - length) / length; fx = dx * springForce; From d32a3dfca41b3f5a6d1f3d9fe99c1dad24af241d Mon Sep 17 00:00:00 2001 From: Alex de Mulder Date: Thu, 20 Feb 2014 17:02:06 +0100 Subject: [PATCH 3/3] small doc change. --- docs/graph.html | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/graph.html b/docs/graph.html index bac13b3f..d58a6c11 100644 --- a/docs/graph.html +++ b/docs/graph.html @@ -1676,6 +1676,7 @@ var options: { The graph can be used to display nodes in a hierarchical way. This can be determined automatically, based on the amount of edges connected to each node, or defined by the user. If the user wants to manually determine the hierarchy, each node has to be supplied with a level (from 0 being heighest to n). The automatic method is shown in example 23 and the user-defined method is shown in example 24. + This layout method does not support smooth curves or clustering. It automatically turns these features off.