diff --git a/HISTORY.md b/HISTORY.md index f59ca9f8..93077a0d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -12,6 +12,12 @@ http://visjs.org - Fixed range where the `end` of the first is equal to the `start` of the second sometimes being stacked instead of put besides each other when `item.margin=0`. +### Network (formerly named Graph) + +- Expanded smoothCurves options for improved support for large clusters. +- Added multiple types of smoothCurve drawing for greatly improved performance. +- Option for inherited edge colors from connected nodes. +- Option to disable the drawing of nodes or edges on drag. ## 2014-07-07, version 3.0.0 diff --git a/docs/network.html b/docs/network.html index e2ffb70b..eec39fef 100644 --- a/docs/network.html +++ b/docs/network.html @@ -672,6 +672,14 @@ When using a DataSet, the network is automatically updating to changes in the Da physics.[method].springLength The resting length of the edge when modeled as a spring. By default the springLength determined by the physics is used. By using this setting you can make certain edges have different resting lengths. + + + inheritColor + String | Boolean + false + Possible values: "to","from", true, false. If this value is set to false, the edge color information is used. If the value is set to true or "from", + the color data from the borders of the "from" node is used. If this value is "to", the color data from the borders of the "to" node is used. + title string | function @@ -874,7 +882,22 @@ var options = { Toggle if the nodes can be dragged. This will not affect the dragging of the network. - + + hideNodesOnDrag + Boolean + false + + Toggle if the nodes are drawn during a drag. This can greatly improve performance if you have many nodes. + + + + hideEdgesOnDrag + Boolean + false + + Toggle if the edges are drawn during a drag. This can greatly improve performance if you have many edges. + + navigation Object @@ -895,11 +918,31 @@ var options = { smoothCurves + Boolean || object + object + If true, edges are drawn as smooth curves. This is more computationally intensive since the edge now is a quadratic Bezier curve. This can be further configured by the options below. + + + + smoothCurves.dynamic Boolean true - If true, edges are drawn as smooth curves. This is more computationally intensive since the edge now is a quadratic Bezier curve with control points on both nodes and an invisible node in the center of the edge. This support node is also handed by the physics simulation. + By default, the edges are dynamic. This means there are support nodes placed in the middle of the edge. This support node is also handed by the physics simulation. If false, the smoothness will be based on the + relative positions of the to and from nodes. This is computationally cheaper but there is no self organisation. + + + smoothCurves.adaptive + Boolean + true + This option only affects NONdynamic smooth curves. Adaptivity allows the smoothness to transition nicely if there is node movement. The downside is that the maximum roundness is less if adaptivity is enabled. Adaptivity can be disabled for static network. + Sudden switching between orientations can happen on an animated network. + + + smoothCurves.roundness + Number + 0.5 + This only affects NONdynamic smooth curves. The roundness can be tweaked with the parameter. The value range is from 0 to 1 with a maximum roundness at 0.5. - selectable Boolean @@ -1223,6 +1266,14 @@ var options = { The resting length of the edge when modeled as a spring. By default the springLength determined by the physics is used. By using this setting you can make certain edges have different resting lengths. + + inheritColor + String | Boolean + false + Possible values: "to","from", true, false. If this value is set to false, the edge color information is used. If the value is set to true or "from", + the color data from the borders of the "from" node is used. If this value is "to", the color data from the borders of the "to" node is used. + + style String diff --git a/examples/network/02_random_nodes.html b/examples/network/02_random_nodes.html index 9a8b1c00..4681ddb8 100644 --- a/examples/network/02_random_nodes.html +++ b/examples/network/02_random_nodes.html @@ -81,12 +81,12 @@ }, stabilize: false }; - network = new vis.Network(container, data, options); + network = new vis.Network(container, data, options); - // add event listeners - network.on('select', function(params) { - document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes; - }); + // add event listeners + network.on('select', function(params) { + document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes; + }); } diff --git a/examples/network/23_hierarchical_layout.html b/examples/network/23_hierarchical_layout.html index b4724ff8..bdde2b54 100644 --- a/examples/network/23_hierarchical_layout.html +++ b/examples/network/23_hierarchical_layout.html @@ -82,6 +82,7 @@ edges: { }, stabilize: false, + smoothCurves: false, hierarchicalLayout: { direction: directionInput.value } diff --git a/examples/network/24_hierarchical_layout_userdefined.html b/examples/network/24_hierarchical_layout_userdefined.html index a8df871e..50981a16 100644 --- a/examples/network/24_hierarchical_layout_userdefined.html +++ b/examples/network/24_hierarchical_layout_userdefined.html @@ -20,6 +20,7 @@ var nodes = null; var edges = null; var network = null; + var directionInput = document.getElementById("direction"); function draw() { nodes = []; @@ -113,7 +114,9 @@ }; var options = { - hierarchicalLayout:true + hierarchicalLayout: { + direction: directionInput.value + } }; network = new vis.Network(container, data, options); @@ -122,6 +125,7 @@ document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes; }); } + @@ -129,11 +133,40 @@

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. + If the smooth curves appear to be inverted, the direction of the edge is not in the same direction as the network.
+ + + + +

+ diff --git a/examples/network/26_staticSmoothCurves.html b/examples/network/26_staticSmoothCurves.html new file mode 100644 index 00000000..c12606af --- /dev/null +++ b/examples/network/26_staticSmoothCurves.html @@ -0,0 +1,75 @@ + + + + Network | Static smooth curves + + + + + + + + +

Static smooth curves

+
+ All the smooth curves in the examples so far have been using dynamic smooth curves. This means that each curve has a + support node which takes part in the physics simulation. For large networks or dense clusters, this may not be the ideal + solution. To solve this, static smooth curves have been added. The static smooth curves are based only on the positions of the connected + nodes. There are multiple ways to determine the way this curve is drawn. This example shows the effect of the different + types.

+ Drag the nodes around each other to see how the smooth curves are drawn for each setting. For animated system, we + recommend only the continuous mode. In the next example you can see the effect of these methods on a large network. Keep in mind + that the direction (the from and to) of the curve matters. +

+
+ +Smooth curve type: + +
+ + + + + diff --git a/examples/network/27_world_cup_network.html b/examples/network/27_world_cup_network.html new file mode 100644 index 00000000..3b305871 --- /dev/null +++ b/examples/network/27_world_cup_network.html @@ -0,0 +1,10089 @@ + + + + Network | Static smooth curves - World Cup Network + + + + + + + + + +

Static smooth curves - World Cup Network

+
+ The static smooth curves are based only on the positions of the connected nodes. + There are multiple ways to determine the way this curve is drawn. + This example shows the effect of the different types on a large network. +

+ Also shown in this example is the inheritColor option of the edges as well as the roundness factor.
+ The roundness factor is only functional with "continuous", "discrete" and "diagonalCross". +

+ To improve performance, the physics have been disabled with: +
{barnesHut: {gravitationalConstant: 0, centralGravity: 0, springConstant: 0}}
and we have enabled + the toggle hideEdgesOnDrag. +

+
+ +Smooth curve type: +
+Roundness (0..1): +
+Hide edges on drag: +Hide nodes on drag: + +
+ + + + + + diff --git a/lib/network/Edge.js b/lib/network/Edge.js index c5fdf072..fa723608 100644 --- a/lib/network/Edge.js +++ b/lib/network/Edge.js @@ -41,8 +41,10 @@ function Edge (properties, network, constants) { this.customLength = false; this.selected = false; this.hover = false; - this.smooth = constants.smoothCurves; + this.smoothCurves = constants.smoothCurves; + this.dynamicSmoothCurves = constants.dynamicSmoothCurves; this.arrowScaleFactor = constants.edges.arrowScaleFactor; + this.inheritColor = constants.edges.inheritColor; this.from = null; // a node this.to = null; // a node @@ -114,6 +116,8 @@ Edge.prototype.setProperties = function(properties, constants) { // scale the arrow if (properties.arrowScaleFactor !== undefined) {this.arrowScaleFactor = properties.arrowScaleFactor;} + if (properties.inheritColor !== undefined) {this.inheritColor = properties.inheritColor;} + // Added to support dashed lines // David Jordan // 2012-08-08 @@ -258,6 +262,28 @@ Edge.prototype.isOverlappingWith = function(obj) { } }; +Edge.prototype._getColor = function() { + var colorObj = this.color; + if (this.inheritColor == "to") { + colorObj = { + highlight: this.to.color.highlight.border, + hover: this.to.color.hover.border, + color: this.to.color.border + }; + } + else if (this.inheritColor == "from" || this.inheritColor == true) { + colorObj = { + highlight: this.from.color.highlight.border, + hover: this.from.color.hover.border, + color: this.from.color.border + }; + } + + if (this.selected == true) {return colorObj.highlight;} + else if (this.hover == true) {return colorObj.hover;} + else {return colorObj.color;} +} + /** * Redraw a edge as a line @@ -268,21 +294,19 @@ Edge.prototype.isOverlappingWith = function(obj) { */ Edge.prototype._drawLine = function(ctx) { // set style - if (this.selected == true) {ctx.strokeStyle = this.color.highlight;} - else if (this.hover == true) {ctx.strokeStyle = this.color.hover;} - else {ctx.strokeStyle = this.color.color;} - ctx.lineWidth = this._getLineWidth(); + ctx.strokeStyle = this._getColor(); + ctx.lineWidth = this._getLineWidth(); if (this.from != this.to) { // draw line - this._line(ctx); + var via = this._line(ctx); // draw label var point; if (this.label) { - if (this.smooth == true) { - var midpointX = 0.5*(0.5*(this.from.x + this.via.x) + 0.5*(this.to.x + this.via.x)); - var midpointY = 0.5*(0.5*(this.from.y + this.via.y) + 0.5*(this.to.y + this.via.y)); + if (this.smoothCurves.enabled == true && via != null) { + var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x)); + var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y)); point = {x:midpointX, y:midpointY}; } else { @@ -332,6 +356,154 @@ Edge.prototype._getLineWidth = function() { } }; +Edge.prototype._getViaCoordinates = function () { + var xVia = null; + var yVia = null; + var factor = this.smoothCurves.roundness; + var type = this.smoothCurves.type; + if (factor == 0) { + return {x:null,y:null}; + } + + var dx = Math.abs(this.from.x - this.to.x); + var dy = Math.abs(this.from.y - this.to.y); + if (type == 'discrete' || type == 'diagonalCross') { + if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { + if (this.from.y > this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dy; + yVia = this.from.y - factor * dy; + } + else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dy; + yVia = this.from.y - factor * dy; + } + } + else if (this.from.y < this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dy; + yVia = this.from.y + factor * dy; + } + else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dy; + yVia = this.from.y + factor * dy; + } + } + if (type == "discrete") { + xVia = dx < factor * dy ? this.from.x : xVia; + } + } + else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { + if (this.from.y > this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dx; + yVia = this.from.y - factor * dx; + } + else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dx; + yVia = this.from.y - factor * dx; + } + } + else if (this.from.y < this.to.y) { + if (this.from.x < this.to.x) { + xVia = this.from.x + factor * dx; + yVia = this.from.y + factor * dx; + } + else if (this.from.x > this.to.x) { + xVia = this.from.x - factor * dx; + yVia = this.from.y + factor * dx; + } + } + if (type == "discrete") { + yVia = dy < factor * dx ? this.from.y : yVia; + } + } + } + else if (type == "straightCross") { + if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { + xVia = this.from.x; + yVia = this.to.y + } + else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { + xVia = this.to.x; + yVia = this.from.y; + } + } + else if (type == 'horizontal') { + xVia = this.to.x; + yVia = this.from.y; + } + else if (type == 'vertical') { + xVia = this.from.x; + yVia = this.to.y; + } + else { // continuous + if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { + if (this.from.y > this.to.y) { + if (this.from.x < this.to.x) { +// console.log(1) + xVia = this.from.x + factor * dy; + yVia = this.from.y - factor * dy; + xVia = this.to.x < xVia ? this.to.x : xVia; + } + else if (this.from.x > this.to.x) { +// console.log(2) + xVia = this.from.x - factor * dy; + yVia = this.from.y - factor * dy; + xVia = this.to.x > xVia ? this.to.x :xVia; + } + } + else if (this.from.y < this.to.y) { + if (this.from.x < this.to.x) { +// console.log(3) + xVia = this.from.x + factor * dy; + yVia = this.from.y + factor * dy; + xVia = this.to.x < xVia ? this.to.x : xVia; + } + else if (this.from.x > this.to.x) { +// console.log(4, this.from.x, this.to.x) + xVia = this.from.x - factor * dy; + yVia = this.from.y + factor * dy; + xVia = this.to.x > xVia ? this.to.x : xVia; + } + } + } + else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { + if (this.from.y > this.to.y) { + if (this.from.x < this.to.x) { +// console.log(5) + xVia = this.from.x + factor * dx; + yVia = this.from.y - factor * dx; + yVia = this.to.y > yVia ? this.to.y : yVia; + } + else if (this.from.x > this.to.x) { +// console.log(6) + xVia = this.from.x - factor * dx; + yVia = this.from.y - factor * dx; + yVia = this.to.y > yVia ? this.to.y : yVia; + } + } + else if (this.from.y < this.to.y) { + if (this.from.x < this.to.x) { +// console.log(7) + xVia = this.from.x + factor * dx; + yVia = this.from.y + factor * dx; + yVia = this.to.y < yVia ? this.to.y : yVia; + } + else if (this.from.x > this.to.x) { +// console.log(8) + xVia = this.from.x - factor * dx; + yVia = this.from.y + factor * dx; + yVia = this.to.y < yVia ? this.to.y : yVia; + } + } + } + } + + + return {x:xVia, y:yVia}; +} + /** * Draw a line between two nodes * @param {CanvasRenderingContext2D} ctx @@ -341,13 +513,33 @@ Edge.prototype._line = function (ctx) { // draw a straight line ctx.beginPath(); ctx.moveTo(this.from.x, this.from.y); - if (this.smooth == true) { + if (this.smoothCurves.enabled == true) { + if (this.smoothCurves.dynamic == false) { + var via = this._getViaCoordinates(); + if (via.x == null) { + ctx.lineTo(this.to.x, this.to.y); + ctx.stroke(); + return null; + } + else { +// this.via.x = via.x; +// this.via.y = via.y; + ctx.quadraticCurveTo(via.x,via.y,this.to.x, this.to.y); + ctx.stroke(); + return via; + } + } + else { ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y); + ctx.stroke(); + return this.via; + } } else { ctx.lineTo(this.to.x, this.to.y); + ctx.stroke(); + return null; } - ctx.stroke(); }; /** @@ -411,11 +603,9 @@ Edge.prototype._drawDashLine = function(ctx) { ctx.lineWidth = this._getLineWidth(); + var via = null; // only firefox and chrome support this method, else we use the legacy one. if (ctx.mozDash !== undefined || ctx.setLineDash !== undefined) { - ctx.beginPath(); - ctx.moveTo(this.from.x, this.from.y); - // configure the dash pattern var pattern = [0]; if (this.dash.length !== undefined && this.dash.gap !== undefined) { @@ -436,13 +626,7 @@ Edge.prototype._drawDashLine = function(ctx) { } // draw the line - if (this.smooth == true) { - ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y); - } - else { - ctx.lineTo(this.to.x, this.to.y); - } - ctx.stroke(); + via = this._line(ctx); // restore the dash settings. if (typeof ctx.setLineDash !== 'undefined') { //Chrome @@ -479,9 +663,9 @@ Edge.prototype._drawDashLine = function(ctx) { // draw label if (this.label) { var point; - if (this.smooth == true) { - var midpointX = 0.5*(0.5*(this.from.x + this.via.x) + 0.5*(this.to.x + this.via.x)); - var midpointY = 0.5*(0.5*(this.from.y + this.via.y) + 0.5*(this.to.y + this.via.y)); + if (this.smoothCurves.enabled == true && via != null) { + var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x)); + var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y)); point = {x:midpointX, y:midpointY}; } else { @@ -538,14 +722,14 @@ Edge.prototype._drawArrowCenter = function(ctx) { if (this.from != this.to) { // draw line - this._line(ctx); + var via = this._line(ctx); var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x)); var length = (10 + 5 * this.width) * this.arrowScaleFactor; // draw an arrow halfway the line - if (this.smooth == true) { - var midpointX = 0.5*(0.5*(this.from.x + this.via.x) + 0.5*(this.to.x + this.via.x)); - var midpointY = 0.5*(0.5*(this.from.y + this.via.y) + 0.5*(this.to.y + this.via.y)); + if (this.smoothCurves.enabled == true && via != null) { + var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x)); + var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y)); point = {x:midpointX, y:midpointY}; } else { @@ -625,20 +809,27 @@ Edge.prototype._drawArrow = function(ctx) { var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x; var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y; + var via; + if (this.smoothCurves.dynamic == true && this.smoothCurves.enabled == true ) { + via = this.via; + } + else if (this.smoothCurves.enabled == true) { + via = this._getViaCoordinates(); + } - if (this.smooth == true) { - angle = Math.atan2((this.to.y - this.via.y), (this.to.x - this.via.x)); - dx = (this.to.x - this.via.x); - dy = (this.to.y - this.via.y); + if (this.smoothCurves.enabled == true && via.x != null) { + angle = Math.atan2((this.to.y - via.y), (this.to.x - via.x)); + dx = (this.to.x - via.x); + dy = (this.to.y - via.y); edgeSegmentLength = Math.sqrt(dx * dx + dy * dy); } var toBorderDist = this.to.distanceToBorder(ctx, angle); var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength; var xTo,yTo; - if (this.smooth == true) { - xTo = (1 - toBorderPoint) * this.via.x + toBorderPoint * this.to.x; - yTo = (1 - toBorderPoint) * this.via.y + toBorderPoint * this.to.y; + if (this.smoothCurves.enabled == true && via.x != null) { + xTo = (1 - toBorderPoint) * via.x + toBorderPoint * this.to.x; + yTo = (1 - toBorderPoint) * via.y + toBorderPoint * this.to.y; } else { xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x; @@ -647,8 +838,8 @@ Edge.prototype._drawArrow = function(ctx) { ctx.beginPath(); ctx.moveTo(xFrom,yFrom); - if (this.smooth == true) { - ctx.quadraticCurveTo(this.via.x,this.via.y,xTo, yTo); + if (this.smoothCurves.enabled == true && via.x != null) { + ctx.quadraticCurveTo(via.x,via.y,xTo, yTo); } else { ctx.lineTo(xTo, yTo); @@ -664,9 +855,9 @@ Edge.prototype._drawArrow = function(ctx) { // draw label if (this.label) { var point; - if (this.smooth == true) { - var midpointX = 0.5*(0.5*(this.from.x + this.via.x) + 0.5*(this.to.x + this.via.x)); - var midpointY = 0.5*(0.5*(this.from.y + this.via.y) + 0.5*(this.to.y + this.via.y)); + if (this.smoothCurves.enabled == true && via != null) { + var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x)); + var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y)); point = {x:midpointX, y:midpointY}; } else { @@ -736,13 +927,23 @@ Edge.prototype._drawArrow = function(ctx) { */ Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point if (this.from != this.to) { - if (this.smooth == true) { + if (this.smoothCurves.enabled == true) { + var xVia, yVia; + if (this.smoothCurves.enabled == true && this.smoothCurves.dynamic == true) { + xVia = this.via.x; + yVia = this.via.y; + } + else { + var via = this._getViaCoordinates(); + xVia = via.x; + yVia = via.y; + } var minDistance = 1e9; var i,t,x,y,dx,dy; for (i = 0; i < 10; i++) { t = 0.1*i; - x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*this.via.x + Math.pow(t,2)*x2; - y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*this.via.y + Math.pow(t,2)*y2; + x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*xVia + Math.pow(t,2)*x2; + y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*yVia + Math.pow(t,2)*y2; dx = Math.abs(x3-x); dy = Math.abs(y3-y); minDistance = Math.min(minDistance,Math.sqrt(dx*dx + dy*dy)); @@ -943,20 +1144,27 @@ Edge.prototype.getControlNodePositions = function(ctx) { var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x; var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y; + var via; + if (this.smoothCurves.dynamic == true && this.smoothCurves.enabled == true) { + via = this.via; + } + else if (this.smoothCurves.enabled == true) { + via = this._getViaCoordinates(); + } - if (this.smooth == true) { - angle = Math.atan2((this.to.y - this.via.y), (this.to.x - this.via.x)); - dx = (this.to.x - this.via.x); - dy = (this.to.y - this.via.y); + if (this.smoothCurves.enabled == true && via.x != null) { + angle = Math.atan2((this.to.y - via.y), (this.to.x - via.x)); + dx = (this.to.x - via.x); + dy = (this.to.y - via.y); edgeSegmentLength = Math.sqrt(dx * dx + dy * dy); } var toBorderDist = this.to.distanceToBorder(ctx, angle); var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength; var xTo,yTo; - if (this.smooth == true) { - xTo = (1 - toBorderPoint) * this.via.x + toBorderPoint * this.to.x; - yTo = (1 - toBorderPoint) * this.via.y + toBorderPoint * this.to.y; + if (this.smoothCurves.enabled == true && via.x != null) { + xTo = (1 - toBorderPoint) * via.x + toBorderPoint * this.to.x; + yTo = (1 - toBorderPoint) * via.y + toBorderPoint * this.to.y; } else { xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x; diff --git a/lib/network/Groups.js b/lib/network/Groups.js index 90e21d3c..84bb4185 100644 --- a/lib/network/Groups.js +++ b/lib/network/Groups.js @@ -14,16 +14,16 @@ function Groups() { * default constants for group colors */ Groups.DEFAULT = [ - {border: "#2B7CE9", background: "#97C2FC", highlight: {border: "#2B7CE9", background: "#D2E5FF"}}, // blue - {border: "#FFA500", background: "#FFFF00", highlight: {border: "#FFA500", background: "#FFFFA3"}}, // yellow - {border: "#FA0A10", background: "#FB7E81", highlight: {border: "#FA0A10", background: "#FFAFB1"}}, // red - {border: "#41A906", background: "#7BE141", highlight: {border: "#41A906", background: "#A1EC76"}}, // green - {border: "#E129F0", background: "#EB7DF4", highlight: {border: "#E129F0", background: "#F0B3F5"}}, // magenta - {border: "#7C29F0", background: "#AD85E4", highlight: {border: "#7C29F0", background: "#D3BDF0"}}, // purple - {border: "#C37F00", background: "#FFA807", highlight: {border: "#C37F00", background: "#FFCA66"}}, // orange - {border: "#4220FB", background: "#6E6EFD", highlight: {border: "#4220FB", background: "#9B9BFD"}}, // darkblue - {border: "#FD5A77", background: "#FFC0CB", highlight: {border: "#FD5A77", background: "#FFD1D9"}}, // pink - {border: "#4AD63A", background: "#C2FABC", highlight: {border: "#4AD63A", background: "#E6FFE3"}} // mint + {border: "#2B7CE9", background: "#97C2FC", highlight: {border: "#2B7CE9", background: "#D2E5FF"}, hover: {border: "#2B7CE9", background: "#D2E5FF"}}, // blue + {border: "#FFA500", background: "#FFFF00", highlight: {border: "#FFA500", background: "#FFFFA3"}, hover: {border: "#FFA500", background: "#FFFFA3"}}, // yellow + {border: "#FA0A10", background: "#FB7E81", highlight: {border: "#FA0A10", background: "#FFAFB1"}, hover: {border: "#FA0A10", background: "#FFAFB1"}}, // red + {border: "#41A906", background: "#7BE141", highlight: {border: "#41A906", background: "#A1EC76"}, hover: {border: "#41A906", background: "#A1EC76"}}, // green + {border: "#E129F0", background: "#EB7DF4", highlight: {border: "#E129F0", background: "#F0B3F5"}, hover: {border: "#E129F0", background: "#F0B3F5"}}, // magenta + {border: "#7C29F0", background: "#AD85E4", highlight: {border: "#7C29F0", background: "#D3BDF0"}, hover: {border: "#7C29F0", background: "#D3BDF0"}}, // purple + {border: "#C37F00", background: "#FFA807", highlight: {border: "#C37F00", background: "#FFCA66"}, hover: {border: "#C37F00", background: "#FFCA66"}}, // orange + {border: "#4220FB", background: "#6E6EFD", highlight: {border: "#4220FB", background: "#9B9BFD"}, hover: {border: "#4220FB", background: "#9B9BFD"}}, // darkblue + {border: "#FD5A77", background: "#FFC0CB", highlight: {border: "#FD5A77", background: "#FFD1D9"}, hover: {border: "#FD5A77", background: "#FFD1D9"}}, // pink + {border: "#4AD63A", background: "#C2FABC", highlight: {border: "#4AD63A", background: "#E6FFE3"}, hover: {border: "#4AD63A", background: "#E6FFE3"}} // mint ]; diff --git a/lib/network/Network.js b/lib/network/Network.js index f61ed05e..1e0283ba 100644 --- a/lib/network/Network.js +++ b/lib/network/Network.js @@ -56,9 +56,9 @@ function Network (container, data, options) { // set constant values this.constants = { nodes: { - radiusMin: 5, - radiusMax: 20, - radius: 5, + radiusMin: 10, + radiusMax: 30, + radius: 10, shape: 'ellipse', image: undefined, widthMin: 16, // px @@ -106,7 +106,8 @@ function Network (container, data, options) { length: 10, gap: 5, altLength: undefined - } + }, + inheritColor: false // to, from, false, true (== from) }, configurePhysics:false, physics: { @@ -178,7 +179,13 @@ function Network (container, data, options) { direction: "UD" // UD, DU, LR, RL }, freezeForStabilization: false, - smoothCurves: true, + smoothCurves: { + enabled: true, + dynamic: true, + type: "continuous", + roundness: 0.5 + }, + dynamicSmoothCurves: true, maxVelocity: 10, minVelocity: 0.1, // px/s stabilizationIterations: 1000, // maximum number of iteration to stabilize @@ -213,10 +220,12 @@ function Network (container, data, options) { dragNetwork: true, dragNodes: true, zoomable: true, - hover: false + hover: false, + hideEdgesOnDrag: false, + hideNodesOnDrag: false }; this.hoverObj = {nodes:{},edges:{}}; - + this.controlNodesActive = false; // Node variables var network = this; @@ -549,7 +558,6 @@ Network.prototype.setOptions = function (options) { if (options.height !== undefined) {this.height = options.height;} if (options.stabilize !== undefined) {this.stabilize = options.stabilize;} if (options.selectable !== undefined) {this.selectable = options.selectable;} - if (options.smoothCurves !== undefined) {this.constants.smoothCurves = options.smoothCurves;} if (options.freezeForStabilization !== undefined) {this.constants.freezeForStabilization = options.freezeForStabilization;} if (options.configurePhysics !== undefined){this.constants.configurePhysics = options.configurePhysics;} if (options.stabilizationIterations !== undefined) {this.constants.stabilizationIterations = options.stabilizationIterations;} @@ -557,6 +565,8 @@ Network.prototype.setOptions = function (options) { if (options.dragNodes !== undefined) {this.constants.dragNodes = options.dragNodes;} if (options.zoomable !== undefined) {this.constants.zoomable = options.zoomable;} if (options.hover !== undefined) {this.constants.hover = options.hover;} + if (options.hideEdgesOnDrag !== undefined) {this.constants.hideEdgesOnDrag = options.hideEdgesOnDrag;} + if (options.hideNodesOnDrag !== undefined) {this.constants.hideNodesOnDrag = options.hideNodesOnDrag;} // TODO: deprecated since version 3.0.0. Cleanup some day if (options.dragGraph !== undefined) { @@ -622,6 +632,20 @@ Network.prototype.setOptions = function (options) { } } + if (options.smoothCurves !== undefined) { + if (typeof options.smoothCurves == 'boolean') { + this.constants.smoothCurves.enabled = options.smoothCurves; + } + else { + this.constants.smoothCurves.enabled = true; + for (prop in options.smoothCurves) { + if (options.smoothCurves.hasOwnProperty(prop)) { + this.constants.smoothCurves[prop] = options.smoothCurves[prop]; + } + } + } + } + if (options.hierarchicalLayout) { this.constants.hierarchicalLayout.enabled = true; for (prop in options.hierarchicalLayout) { @@ -693,7 +717,6 @@ Network.prototype.setOptions = function (options) { } } - if (options.edges.color !== undefined) { if (util.isString(options.edges.color)) { this.constants.edges.color = {}; @@ -1023,10 +1046,11 @@ Network.prototype._handleOnDrag = function(event) { this._setTranslation( this.drag.translation.x + diffX, - this.drag.translation.y + diffY); + this.drag.translation.y + diffY + ); this._redraw(); - this.moving = true; - this.start(); +// this.moving = true; +// this.start(); } } }; @@ -1045,6 +1069,7 @@ Network.prototype._onDragEnd = function () { s.node.yFixed = s.yFixed; }); } + this._redraw(); }; /** @@ -1740,10 +1765,19 @@ Network.prototype._redraw = function() { "y": this._YconvertDOMtoCanvas(this.frame.canvas.clientHeight) }; + this._doInAllSectors("_drawAllSectorNodes",ctx); - this._doInAllSectors("_drawEdges",ctx); - this._doInAllSectors("_drawNodes",ctx,false); - this._doInAllSectors("_drawControlNodes",ctx); + if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideEdgesOnDrag == false) { + this._doInAllSectors("_drawEdges",ctx); + } + + if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideNodesOnDrag == false) { + this._doInAllSectors("_drawNodes",ctx,false); + } + + if (this.controlNodesActive == true) { + this._doInAllSectors("_drawControlNodes",ctx); + } // this._doInSupportSector("_drawNodes",ctx,true); // this._drawTree(ctx,"#F00F0F"); @@ -2055,7 +2089,7 @@ Network.prototype._discreteStepNodes = function() { this.moving = true; } else { - this.moving = this._isMoving(vminCorrected); + this.moving = this._isMoving(vminCorrected) || this.constants.configurePhysics; } } }; @@ -2104,10 +2138,12 @@ Network.prototype._animationStep = function() { timeRequired = Date.now() - calculationTime; maxSteps++; } + // start the rendering process var renderTime = Date.now(); this._redraw(); this.renderTime = Date.now() - renderTime; + }; if (typeof window !== 'undefined') { @@ -2191,8 +2227,7 @@ Network.prototype._configureSmoothCurves = function(disableStart) { if (disableStart === undefined) { disableStart = true; } - - if (this.constants.smoothCurves == true) { + if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) { this._createBezierNodes(); } else { @@ -2220,7 +2255,7 @@ Network.prototype._configureSmoothCurves = function(disableStart) { * @private */ Network.prototype._createBezierNodes = function() { - if (this.constants.smoothCurves == true) { + if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) { for (var edgeId in this.edges) { if (this.edges.hasOwnProperty(edgeId)) { var edge = this.edges[edgeId]; diff --git a/lib/network/mixins/HierarchicalLayoutMixin.js b/lib/network/mixins/HierarchicalLayoutMixin.js index 4be8aab5..78f75373 100644 --- a/lib/network/mixins/HierarchicalLayoutMixin.js +++ b/lib/network/mixins/HierarchicalLayoutMixin.js @@ -23,6 +23,17 @@ exports._setupHierarchicalLayout = function() { else { this.constants.hierarchicalLayout.levelSeparation = Math.abs(this.constants.hierarchicalLayout.levelSeparation); } + + if (this.constants.hierarchicalLayout.direction == "RL" || this.constants.hierarchicalLayout.direction == "LR") { + if (this.constants.smoothCurves.enabled == true) { + this.constants.smoothCurves.type = "vertical"; + } + } + else { + if (this.constants.smoothCurves.enabled == true) { + this.constants.smoothCurves.type = "horizontal"; + } + } // 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; @@ -210,7 +221,9 @@ exports._changeConstants = function() { this.constants.physics.barnesHut.enabled = false; this.constants.physics.hierarchicalRepulsion.enabled = true; this._loadSelectedForceSolver(); - this.constants.smoothCurves = false; + if (this.constants.smoothCurves.enabled == true) { + this.constants.smoothCurves.dynamic = false; + } this._configureSmoothCurves(); }; diff --git a/lib/network/mixins/ManipulationMixin.js b/lib/network/mixins/ManipulationMixin.js index 2413ec3f..be34011a 100644 --- a/lib/network/mixins/ManipulationMixin.js +++ b/lib/network/mixins/ManipulationMixin.js @@ -63,10 +63,12 @@ exports._createManipulatorBar = function() { if (this.boundFunction) { this.off('select', this.boundFunction); } + if (this.edgeBeingEdited !== undefined) { this.edgeBeingEdited._disableControlNodes(); this.edgeBeingEdited = undefined; this.selectedControlNode = null; + this.controlNodesActive = false; } // restore overloaded functions @@ -226,6 +228,7 @@ exports._createAddEdgeToolbar = function() { exports._createEditEdgeToolbar = function() { // clear the toolbar this._clearManipulatorBar(); + this.controlNodesActive = true; if (this.boundFunction) { this.off('select', this.boundFunction); diff --git a/lib/network/mixins/physics/PhysicsMixin.js b/lib/network/mixins/physics/PhysicsMixin.js index 1ab07620..18c4de5e 100644 --- a/lib/network/mixins/physics/PhysicsMixin.js +++ b/lib/network/mixins/physics/PhysicsMixin.js @@ -95,15 +95,17 @@ exports._calculateForces = function () { this._calculateGravitationalForces(); this._calculateNodeForces(); - if (this.constants.smoothCurves == true) { - this._calculateSpringForcesWithSupport(); - } - else { - if (this.constants.physics.hierarchicalRepulsion.enabled == true) { - this._calculateHierarchicalSpringForces(); + if (this.constants.springConstant > 0) { + if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) { + this._calculateSpringForcesWithSupport(); } else { - this._calculateSpringForces(); + if (this.constants.physics.hierarchicalRepulsion.enabled == true) { + this._calculateHierarchicalSpringForces(); + } + else { + this._calculateSpringForces(); + } } } }; @@ -118,7 +120,7 @@ exports._calculateForces = function () { * @private */ exports._updateCalculationNodes = function () { - if (this.constants.smoothCurves == true) { + if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) { this.calculationNodes = {}; this.calculationNodeIndices = []; @@ -457,7 +459,7 @@ exports._loadPhysicsConfiguration = function () { graph_toggleSmooth.onclick = graphToggleSmoothCurves.bind(this); graph_repositionNodes.onclick = graphRepositionNodes.bind(this); graph_generateOptions.onclick = graphGenerateOptions.bind(this); - if (this.constants.smoothCurves == true) { + if (this.constants.smoothCurves == true && this.constants.dynamicSmoothCurves == false) { graph_toggleSmooth.style.background = "#A4FF56"; } else { @@ -498,9 +500,9 @@ exports._overWriteGraphConstants = function (constantsVariableName, value) { * this function is bound to the toggle smooth curves button. That is also why it is not in the prototype. */ function graphToggleSmoothCurves () { - this.constants.smoothCurves = !this.constants.smoothCurves; + this.constants.smoothCurves.enabled = !this.constants.smoothCurves.enabled; var graph_toggleSmooth = document.getElementById("graph_toggleSmooth"); - if (this.constants.smoothCurves == true) {graph_toggleSmooth.style.background = "#A4FF56";} + if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";} else {graph_toggleSmooth.style.background = "#FF8532";} this._configureSmoothCurves(false); @@ -552,10 +554,10 @@ function graphGenerateOptions () { } options += '}}' } - if (this.constants.smoothCurves != this.backupConstants.smoothCurves) { + if (this.constants.smoothCurves.enabled != this.backupConstants.smoothCurves.enabled) { if (optionsSpecific.length == 0) {options = "var options = {";} else {options += ", "} - options += "smoothCurves: " + this.constants.smoothCurves; + options += "smoothCurves: " + this.constants.smoothCurves.enabled; } if (options != "No options are required, default values used.") { options += '};' @@ -653,6 +655,7 @@ function switchConfigurations () { this.constants.hierarchicalLayout.enabled = true; this.constants.physics.hierarchicalRepulsion.enabled = true; this.constants.physics.barnesHut.enabled = false; + this.constants.smoothCurves.enabled = false; this._setupHierarchicalLayout(); } } @@ -663,7 +666,7 @@ function switchConfigurations () { } this._loadSelectedForceSolver(); var graph_toggleSmooth = document.getElementById("graph_toggleSmooth"); - if (this.constants.smoothCurves == true) {graph_toggleSmooth.style.background = "#A4FF56";} + if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";} else {graph_toggleSmooth.style.background = "#FF8532";} this.moving = true; this.start();