Browse Source

clustering now using new visibility model, ripped edges apart, arrows not yet functional

flowchartTest
Alex de Mulder 9 years ago
parent
commit
2152bd64ce
37 changed files with 3004 additions and 2556 deletions
  1. +1704
    -1343
      dist/vis.js
  2. +11
    -9
      examples/network/01_basic_usage.html
  3. +5
    -4
      examples/network/39_newClustering.html
  4. +1
    -9
      lib/network/Network.js
  5. +37
    -63
      lib/network/modules/Clustering.js
  6. +1
    -1
      lib/network/modules/EdgesHandler.js
  7. +3
    -3
      lib/network/modules/InteractionHandler.js
  8. +42
    -38
      lib/network/modules/NodesHandler.js
  9. +7
    -12
      lib/network/modules/PhysicsEngine.js
  10. +63
    -782
      lib/network/modules/components/Edge.js
  11. +23
    -16
      lib/network/modules/components/Node.js
  12. +116
    -0
      lib/network/modules/components/edges/bezierEdgeDynamic.js
  13. +240
    -0
      lib/network/modules/components/edges/bezierEdgeStatic.js
  14. +66
    -0
      lib/network/modules/components/edges/straightEdge.js
  15. +351
    -0
      lib/network/modules/components/edges/util/baseEdge.js
  16. +116
    -0
      lib/network/modules/components/edges/util/bezierBaseEdge.js
  17. +16
    -0
      lib/network/modules/components/nodes/cluster.js
  18. +0
    -26
      lib/network/modules/components/nodes/empty.js
  19. +0
    -126
      lib/network/modules/components/nodes/nodeUtil.js
  20. +4
    -13
      lib/network/modules/components/nodes/shapes/box.js
  21. +7
    -17
      lib/network/modules/components/nodes/shapes/circle.js
  22. +2
    -9
      lib/network/modules/components/nodes/shapes/circularImage.js
  23. +4
    -13
      lib/network/modules/components/nodes/shapes/database.js
  24. +3
    -7
      lib/network/modules/components/nodes/shapes/dot.js
  25. +4
    -13
      lib/network/modules/components/nodes/shapes/ellipse.js
  26. +20
    -0
      lib/network/modules/components/nodes/shapes/empty.js
  27. +2
    -6
      lib/network/modules/components/nodes/shapes/icon.js
  28. +2
    -6
      lib/network/modules/components/nodes/shapes/image.js
  29. +4
    -8
      lib/network/modules/components/nodes/shapes/square.js
  30. +3
    -7
      lib/network/modules/components/nodes/shapes/star.js
  31. +3
    -9
      lib/network/modules/components/nodes/shapes/text.js
  32. +3
    -7
      lib/network/modules/components/nodes/shapes/triangle.js
  33. +3
    -7
      lib/network/modules/components/nodes/shapes/triangleDown.js
  34. +28
    -0
      lib/network/modules/components/nodes/util/baseNode.js
  35. +55
    -0
      lib/network/modules/components/nodes/util/drawUtil.js
  36. +52
    -0
      lib/network/modules/components/nodes/util/shapeUtil.js
  37. +3
    -2
      lib/network/modules/components/physics/SpringSolver.js

+ 1704
- 1343
dist/vis.js
File diff suppressed because it is too large
View File


+ 11
- 9
examples/network/01_basic_usage.html View File

@ -22,19 +22,20 @@
<script type="text/javascript">
// create an array with nodes
var nodes = [
{id: 1, label: 'Node 1', shape:'text'},
{id: 2, label: 'Node 2', shape:'square'},
{id: 3, label: 'Node 3', shape:'triangle'},
{id: 4, label: 'Node 4', shape:'star'},
{id: 5, label: 'Node 5', shape:'triangleDown'}
{id: 1, label: 'Node 1'},
{id: 2, label: 'Node 2'},
{id: 3, label: 'Node 3'},
{id: 4, label: 'Node 4'},
{id: 5, label: 'Node 5'}
];
// create an array with edges
var edges = [
{from: 1, to: 3, label:'bla1'},
{from: 1, to: 2, label:'bla2'},
{from: 2, to: 4, label:'bla4'},
{from: 2, to: 5, label:'bla5', font:{align:'top'}}
{from: 1, to: 3},
{from: 1, to: 1},
{from: 1, to: 2, arrows:{from:true}},
{from: 2, to: 4},
{from: 2, to: 5}
];
// create a network
@ -45,6 +46,7 @@
};
var options = {}//{physics:{stabilization:false}};
var network = new vis.Network(container, data, options);
network.on("selected",function () {alert("1")})
</script>
</body>

+ 5
- 4
examples/network/39_newClustering.html View File

@ -63,7 +63,7 @@
processClusterProperties: function (properties, childNodes, childEdges) {
return properties;
},
clusterNodeProperties: {id:'bla', borderWidth:8},
clusterNodeProperties: {id:'bla', borderWidth:2},
}
var clusterOptionsByData = {
@ -73,11 +73,11 @@
processClusterProperties: function (properties, childNodes, childEdges) {
return properties;
},
clusterNodeProperties: {id:'bla', borderWidth:8}
clusterNodeProperties: {id:'bla', borderWidth:2}
}
// network.clusterByNodeData(clusterOptionsByData)
network.clustering.clusterOutliers({clusterNodeProperties: {borderWidth:8}})
network.clustering.clusterOutliers({clusterNodeProperties: {shape:'database',borderWidth:3}})
// network.clusterByConnection(2, clusterOptions);
// network.clusterByConnection(9, {
@ -87,7 +87,8 @@
// },
// clusterNodeProperties: {id:'bla2', label:"bla2", borderWidth:8}
// });
network.on("select", function(params) {
network.body.emitter.on("select", function(params) {
console.log("here1234")
if (params.nodes.length == 1) {
if (network.clustering.isCluster(params.nodes[0]) == true) {
network.clustering.openCluster(params.nodes[0])

+ 1
- 9
lib/network/Network.js View File

@ -213,19 +213,11 @@ Network.prototype.setData = function(data) {
// unselect all to ensure no selections from old data are carried over.
this.selectionHandler.unselectAll();
// we set initializing to true to ensure that the hierarchical layout is not performed until both nodes and edges are added.
this.initializing = true;
if (data && data.dot && (data.nodes || data.edges)) {
throw new SyntaxError('Data must contain either parameter "dot" or ' +
' parameter pair "nodes" and "edges", but not both.');
}
// clean up in case there is anyone in an active mode of the manipulation. This is the same option as bound to the escape button.
//if (this.constants.dataManipulation.enabled == true) {
// this._createManipulatorBar();
//}
// set options
this.setOptions(data && data.options);
// set all data
@ -279,7 +271,7 @@ Network.prototype.setOptions = function (options) {
this.interactionHandler.setOptions(options.interaction);
this.selectionHandler.setOptions(options.selection);
this.layoutEngine.setOptions(options.layout);
//this.clustering.setOptions(options.clustering);
this.clustering.setOptions(options.clustering);
//util.mergeOptions(this.constants, options,'smoothCurves');
//util.mergeOptions(this.constants, options,'hierarchicalLayout');

+ 37
- 63
lib/network/modules/Clustering.js View File

@ -3,6 +3,7 @@
*/
var util = require("../../util");
import Cluster from './components/nodes/cluster'
class ClusterEngine {
constructor(body) {
@ -10,6 +11,9 @@ class ClusterEngine {
this.clusteredNodes = {};
}
setOptions(options) {
}
/**
*
@ -319,35 +323,30 @@ class ClusterEngine {
// create the clusterNode
var clusterNode = this.body.functions.createNode(clusterNodeProperties);
var clusterNode = this.body.functions.createNode(clusterNodeProperties, Cluster);
clusterNode.isCluster = true;
clusterNode.containedNodes = childNodesObj;
clusterNode.containedEdges = childEdgesObj;
// delete contained edges from global
// disable the childEdges
for (var edgeId in childEdgesObj) {
if (childEdgesObj.hasOwnProperty(edgeId)) {
if (this.body.edges[edgeId] !== undefined) {
if (this.body.edges[edgeId].via !== null) {
var viaId = this.body.edges[edgeId].via.id;
if (viaId) {
this.body.edges[edgeId].via = null
delete this.body.supportNodes[viaId];
}
}
this.body.edges[edgeId].disconnect();
delete this.body.edges[edgeId];
let edge = this.body.edges[edgeId];
edge.togglePhysics(false);
edge.options.hidden = true;
}
}
}
// remove contained nodes from global
// disable the childNodes
for (var nodeId in childNodesObj) {
if (childNodesObj.hasOwnProperty(nodeId)) {
this.clusteredNodes[nodeId] = {clusterId:clusterNodeProperties.id, node: this.body.nodes[nodeId]};
delete this.body.nodes[nodeId];
this.body.nodes[nodeId].togglePhysics(false);
this.body.nodes[nodeId].options.hidden = true;
}
}
@ -380,7 +379,7 @@ class ClusterEngine {
*/
isCluster(nodeId) {
if (this.body.nodes[nodeId] !== undefined) {
return this.body.nodes[nodeId].isCluster;
return this.body.nodes[nodeId].isCluster === true;
}
else {
console.log("Node does not exist.")
@ -423,21 +422,25 @@ class ClusterEngine {
if (this.body.nodes[clusterNodeId] === undefined) {throw new Error("The clusterNodeId supplied to openCluster does not exist.");}
if (this.body.nodes[clusterNodeId].containedNodes === undefined) {console.log("The node:" + clusterNodeId + " is not a cluster."); return};
var node = this.body.nodes[clusterNodeId];
var containedNodes = node.containedNodes;
var containedEdges = node.containedEdges;
var clusterNode = this.body.nodes[clusterNodeId];
var containedNodes = clusterNode.containedNodes;
var containedEdges = clusterNode.containedEdges;
// release nodes
for (var nodeId in containedNodes) {
if (containedNodes.hasOwnProperty(nodeId)) {
this.body.nodes[nodeId] = containedNodes[nodeId];
let containedNode = this.body.nodes[nodeId];
containedNode = containedNodes[nodeId];
// inherit position
this.body.nodes[nodeId].x = node.x;
this.body.nodes[nodeId].y = node.y;
containedNode.x = clusterNode.x;
containedNode.y = clusterNode.y;
// inherit speed
this.body.nodes[nodeId].vx = node.vx;
this.body.nodes[nodeId].vy = node.vy;
containedNode.vx = clusterNode.vx;
containedNode.vy = clusterNode.vy;
containedNode.options.hidden = false;
containedNode.togglePhysics(true);
delete this.clusteredNodes[nodeId];
}
@ -446,52 +449,23 @@ class ClusterEngine {
// release edges
for (var edgeId in containedEdges) {
if (containedEdges.hasOwnProperty(edgeId)) {
this.body.edges[edgeId] = containedEdges[edgeId];
this.body.edges[edgeId].connect();
var edge = this.body.edges[edgeId];
if (edge.connected === false) {
if (this.clusteredNodes[edge.fromId] !== undefined) {
this._connectEdge(edge, edge.fromId, true);
}
if (this.clusteredNodes[edge.toId] !== undefined) {
this._connectEdge(edge, edge.toId, false);
}
}
edge.options.hidden = false;
edge.togglePhysics(true);
}
}
var edgeIds = [];
for (var i = 0; i < node.edges.length; i++) {
edgeIds.push(node.edges[i].id);
}
// remove edges in clusterNode
for (var i = 0; i < edgeIds.length; i++) {
var edge = this.body.edges[edgeIds[i]];
// if the edge should have been connected to a contained node
if (edge.fromArray.length > 0 && edge.fromId == clusterNodeId) {
// the node in the from array was contained in the cluster
if (this.body.nodes[edge.fromArray[0].id] !== undefined) {
this._connectEdge(edge, edge.fromArray[0].id, true);
}
}
else if (edge.toArray.length > 0 && edge.toId == clusterNodeId) {
// the node in the to array was contained in the cluster
if (this.body.nodes[edge.toArray[0].id] !== undefined) {
this._connectEdge(edge, edge.toArray[0].id, false);
}
}
else {
var edgeId = edgeIds[i];
var viaId = this.body.edges[edgeId].via.id;
if (viaId) {
this.body.edges[edgeId].via = null
delete this.body.supportNodes[viaId];
}
// this removes the edge from node.edges, which is why edgeIds is formed
this.body.edges[edgeId].disconnect();
delete this.body.edges[edgeId];
// remove all temporary edges
for (var i = 0; i < clusterNode.edges.length; i++) {
var edgeId = clusterNode.edges[i].id;
var viaId = this.body.edges[edgeId].via.id;
if (viaId) {
this.body.edges[edgeId].via = undefined;
delete this.body.nodes[viaId];
}
// this removes the edge from node.edges, which is why edgeIds is formed
this.body.edges[edgeId].disconnect();
delete this.body.edges[edgeId];
}
// remove clusterNode

+ 1
- 1
lib/network/modules/EdgesHandler.js View File

@ -82,7 +82,7 @@ class EdgesHandler {
}
}
},
selfReferenceSize:25,
selfReferenceSize:20,
smooth: {
enabled: true,
dynamic: true,

+ 3
- 3
lib/network/modules/InteractionHandler.js View File

@ -68,7 +68,7 @@ class InteractionHandler {
util.selectiveNotDeepExtend(fields,this.options, options);
// merge the keyboard options in.
util.mergeOptions(this.options, options,'keyboard');
util.mergeOptions(this.options, options, 'keyboard');
}
this.navigationHandler.setOptions(this.options);
@ -116,7 +116,7 @@ class InteractionHandler {
var selected = this.selectionHandler.selectOnPoint(pointer);
if (selected === true || (previouslySelected == true && selected === false)) { // select or unselect
this.body.emitter.emit('selected', this.selectionHandler.getSelection());
this.body.emitter.emit('select', this.selectionHandler.getSelection());
}
this.selectionHandler._generateClickEvent("click",pointer);
@ -144,7 +144,7 @@ class InteractionHandler {
var selectionChanged = this.selectionHandler.selectAdditionalOnPoint(pointer);
if (selectionChanged === true) { // select or longpress
this.body.emitter.emit('selected', this.selectionHandler.getSelection());
this.body.emitter.emit('select', this.selectionHandler.getSelection());
}
this.selectionHandler._generateClickEvent("click",pointer);

+ 42
- 38
lib/network/modules/NodesHandler.js View File

@ -27,8 +27,45 @@ class NodesHandler {
this.options = {};
this.defaultOptions = {
borderWidth: 1,
borderWidthSelected: undefined,
color: {
border: '#2B7CE9',
background: '#97C2FC',
highlight: {
border: '#2B7CE9',
background: '#D2E5FF'
},
hover: {
border: '#2B7CE9',
background: '#D2E5FF'
}
},
fixed: {
x:false,
y:false
},
font: {
color: '#343434',
size: 14, // px
face: 'arial',
background: 'none',
stroke: 0, // px
strokeColor: 'white',
align:'horizontal'
},
group: undefined,
hidden: false,
icon: {
fontFace: undefined, //'FontAwesome',
code: undefined, //'\uf007',
size: undefined, //50,
color: undefined //'#aa00ff'
},
image: undefined, // --> URL
label: undefined,
mass: 1,
size: 10,
physics: true,
scaling: {
min: 10,
max: 40,
@ -50,41 +87,8 @@ class NodesHandler {
}
},
shape: 'ellipse',
image: undefined, // --> URL
label: undefined,
font: {
color: '#343434',
size: 14, // px
face: 'arial',
background: 'none',
stroke: 0, // px
strokeColor: 'white',
align:'horizontal'
},
value: 1,
color: {
border: '#2B7CE9',
background: '#97C2FC',
highlight: {
border: '#2B7CE9',
background: '#D2E5FF'
},
hover: {
border: '#2B7CE9',
background: '#D2E5FF'
}
},
group: undefined,
borderWidth: 1,
borderWidthSelected: undefined,
physics: true,
hidden: false,
icon: {
fontFace: undefined, //'FontAwesome',
code: undefined, //'\uf007',
size: undefined, //50,
color: undefined //'#aa00ff'
}
size: 10,
value: 1
};
util.extend(this.options, this.defaultOptions);
@ -212,8 +216,8 @@ class NodesHandler {
create(properties) {
return new Node(properties, this.body, this.images, this.groups, this.options)
create(properties, constructorClass = Node) {
return new constructorClass(properties, this.body, this.images, this.groups, this.options)
}

+ 7
- 12
lib/network/modules/PhysicsEngine.js View File

@ -5,10 +5,8 @@
import {BarnesHutSolver} from "./components/physics/BarnesHutSolver";
import {Repulsion} from "./components/physics/RepulsionSolver";
import {HierarchicalRepulsion} from "./components/physics/HierarchicalRepulsionSolver";
import {SpringSolver} from "./components/physics/SpringSolver";
import {HierarchicalSpringSolver} from "./components/physics/HierarchicalSpringSolver";
import {CentralGravitySolver} from "./components/physics/CentralGravitySolver";
var util = require('../../util');
@ -53,7 +51,7 @@ class PhysicsEngine {
nodeDistance: 150,
damping: 0.09
},
model: 'BarnesHut',
solver: 'BarnesHut',
timestep: 0.5,
maxVelocity: 50,
minVelocity: 0.1, // px/s
@ -80,12 +78,8 @@ class PhysicsEngine {
setOptions(options) {
if (options !== undefined) {
if (typeof options.stabilization == 'boolean') {
options.stabilization = {
enabled: options.stabilization
}
}
util.deepExtend(this.options, options);
util.selectiveNotDeepExtend(['stabilization'],this.options, options);
util.mergeOptions(this.options, options, 'stabilization')
}
this.init();
}
@ -93,12 +87,12 @@ class PhysicsEngine {
init() {
var options;
if (this.options.model == "repulsion") {
if (this.options.solver == "repulsion") {
options = this.options.repulsion;
this.nodesSolver = new Repulsion(this.body, this.physicsBody, options);
this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options);
}
else if (this.options.model == "hierarchicalRepulsion") {
else if (this.options.solver == "hierarchicalRepulsion") {
options = this.options.hierarchicalRepulsion;
this.nodesSolver = new HierarchicalRepulsion(this.body, this.physicsBody, options);
this.edgesSolver = new HierarchicalSpringSolver(this.body, this.physicsBody, options);
@ -115,11 +109,11 @@ class PhysicsEngine {
initPhysics() {
this.stabilized = false;
this.ready = true;
if (this.options.stabilization.enabled === true) {
this.stabilize();
}
else {
this.ready = true;
this.body.emitter.emit("zoomExtent", {duration:0}, true)
this.runSimulation();
}
@ -433,6 +427,7 @@ class PhysicsEngine {
this.body.emitter.emit("stabilizationIterationsDone");
this.body.emitter.emit("_requestRedraw");
this.ready = true;
}
}

+ 63
- 782
lib/network/modules/components/Edge.js View File

@ -2,6 +2,9 @@ var util = require('../../../util');
import Label from './unified/label.js'
import BezierEdgeDynamic from './edges/bezierEdgeDynamic'
import BezierEdgeStatic from './edges/bezierEdgeStatic'
import StraightEdge from './edges/straightEdge'
/**
* @class Edge
*
@ -31,7 +34,6 @@ class Edge {
this.fromId = undefined;
this.toId = undefined;
this.title = undefined;
this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
this.value = undefined;
this.selected = false;
this.hover = false;
@ -40,15 +42,8 @@ class Edge {
this.from = undefined; // a node
this.to = undefined; // a node
this.via = undefined; // a temp node
this.fromBackup = undefined; // used to clean up after reconnect (used for manipulation)
this.toBackup = undefined; // used to clean up after reconnect (used for manipulation)
// we use this to be able to reconnect the edge to a cluster if its node is put into a cluster
// by storing the original information we can revert to the original connection when the cluser is opened.
this.fromArray = [];
this.toArray = [];
this.edgeType = undefined;
this.connected = false;
@ -76,6 +71,7 @@ class Edge {
var fields = [
'id',
'font',
'from',
'hidden',
'hoverWidth',
'label',
@ -85,6 +81,7 @@ class Edge {
'physics',
'scaling',
'selfReferenceSize',
'to',
'value',
'width',
'widthMin',
@ -137,55 +134,48 @@ class Edge {
// A node is connected when it has a from and to node that both exist in the network.body.nodes.
this.connect();
this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
this.labelModule.setOptions(this.options);
this.setupSmoothEdges(doNotEmit);
this.updateEdgeType();
this.edgeType.setOptions(this.options);
this.labelModule.setOptions(this.options);
}
updateEdgeType() {
if (this.edgeType !== undefined) {
this.edgeType.cleanup();
}
/**
* Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
* are used for the force calculation.
*
* @private
*/
setupSmoothEdges(doNotEmit = false) {
var changedData = false;
if (this.options.smooth.enabled == true && this.options.smooth.dynamic == true) {
if (this.via === undefined) {
changedData = true;
var nodeId = "edgeId:" + this.id;
var node = this.body.functions.createNode({
id: nodeId,
mass: 1,
shape: 'circle',
image: "",
physics:true,
hidden:true
});
this.body.nodes[nodeId] = node;
this.via = node;
this.via.parentEdgeId = this.id;
this.positionBezierNode();
if (this.options.smooth.enabled === true) {
if (this.options.smooth.dynamic === true) {
this.edgeType = new BezierEdgeDynamic(this.options, this.body, this.labelModule);
}
else {
this.edgeType = new BezierEdgeStatic(this.options, this.body, this.labelModule);
}
}
else {
if (this.via !== undefined) {
delete this.body.nodes[this.via.id];
this.via = undefined;
changedData = true;
}
this.edgeType = new StraightEdge(this.options, this.body, this.labelModule);
}
}
// node has been added or deleted
if (changedData === true && doNotEmit === false) {
this.body.emitter.emit("_dataChanged");
/**
* Enable or disable the physics.
* @param status
*/
togglePhysics(status) {
if (this.options.smooth.enabled == true && this.options.smooth.dynamic == true) {
if (this.via === undefined) {
this.via.pptions.physics = status;
}
}
this.options.physics = status;
}
/**
* Connect an edge to its nodes
*/
@ -284,26 +274,15 @@ class Edge {
* @param {CanvasRenderingContext2D} ctx
*/
draw(ctx) {
let via = this.drawLine(ctx);
let via = this.edgeType.drawLine(ctx, this.selected, this.hover);
this.drawArrows(ctx, via);
this.drawLabel (ctx, via);
}
drawLine(ctx) {
if (this.options.dashes.enabled === false) {
return this._drawLine(ctx);
}
else {
return this._drawDashLine(ctx);
}
}
drawArrows(ctx, viaNode) {
if (this.options.arrows.from.enabled === true) {this._drawArrowHead(ctx,'from', viaNode);}
if (this.options.arrows.middle.enabled === true) {this._drawArrowHead(ctx,'middle', viaNode);}
if (this.options.arrows.to.enabled === true) {this._drawArrowHead(ctx,'to', viaNode);}
if (this.options.arrows.from.enabled === true) {this._drawArrowHead(ctx,'from', viaNode);}
if (this.options.arrows.middle.enabled === true) {this._drawArrowHead(ctx,'middle', viaNode);}
if (this.options.arrows.to.enabled === true) {this._drawArrowHead(ctx,'to', viaNode);}
}
drawLabel(ctx, viaNode) {
@ -313,7 +292,7 @@ class Edge {
var node2 = this.to;
var selected = (this.from.selected || this.to.selected || this.selected);
if (node1.id != node2.id) {
var point = this._pointOnEdge(0.5, viaNode);
var point = this.edgeType.getPoint(0.5, viaNode);
ctx.save();
// if the label has to be rotated:
@ -361,7 +340,7 @@ class Edge {
var xObj = obj.left;
var yObj = obj.top;
var dist = this._getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
var dist = this.edgeType.getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
return (dist < distMax);
}
@ -371,380 +350,6 @@ class Edge {
}
_getColor(ctx) {
var colorObj = this.options.color;
if (colorObj.inherit.enabled === true) {
if (colorObj.inherit.useGradients == true) {
var grd = ctx.createLinearGradient(this.from.x, this.from.y, this.to.x, this.to.y);
var fromColor, toColor;
fromColor = this.from.options.color.highlight.border;
toColor = this.to.options.color.highlight.border;
if (this.from.selected == false && this.to.selected == false) {
fromColor = util.overrideOpacity(this.from.options.color.border, this.options.color.opacity);
toColor = util.overrideOpacity(this.to.options.color.border, this.options.color.opacity);
}
else if (this.from.selected == true && this.to.selected == false) {
toColor = this.to.options.color.border;
}
else if (this.from.selected == false && this.to.selected == true) {
fromColor = this.from.options.color.border;
}
grd.addColorStop(0, fromColor);
grd.addColorStop(1, toColor);
// -------------------- this returns -------------------- //
return grd;
}
if (this.colorDirty === true) {
if (colorObj.inherit.source == "to") {
colorObj.highlight = this.to.options.color.highlight.border;
colorObj.hover = this.to.options.color.hover.border;
colorObj.color = util.overrideOpacity(this.to.options.color.border, this.options.color.opacity);
}
else { // (this.options.color.inherit.source == "from") {
colorObj.highlight = this.from.options.color.highlight.border;
colorObj.hover = this.from.options.color.hover.border;
colorObj.color = util.overrideOpacity(this.from.options.color.border, this.options.color.opacity);
}
}
}
// if color inherit is on and gradients are used, the function has already returned by now.
this.colorDirty = false;
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
* Draw this edge in the given canvas
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
* @param {CanvasRenderingContext2D} ctx
* @private
*/
_drawLine(ctx) {
// set style
ctx.strokeStyle = this._getColor(ctx);
ctx.lineWidth = this._getLineWidth();
var via;
if (this.from != this.to) {
// draw line
via = this._line(ctx);
}
else {
var x, y;
var radius = this.options.selfReferenceSize;
var node = this.from;
if (!node.width) {
node.resize(ctx);
}
if (node.width > node.height) {
x = node.x + node.width * 0.5;
y = node.y - radius;
}
else {
x = node.x + radius;
y = node.y - node.height * 0.5;
}
this._circle(ctx, x, y, radius);
}
return via;
}
/**
* Get the line width of the edge. Depends on width and whether one of the
* connected nodes is selected.
* @return {Number} width
* @private
*/
_getLineWidth() {
if (this.selected == true) {
return Math.max(Math.min(this.widthSelected, this.options.widthMax), 0.3 * this.networkScaleInv);
}
else {
if (this.hover == true) {
return Math.max(Math.min(this.options.hoverWidth, this.options.widthMax), 0.3 * this.networkScaleInv);
}
else {
return Math.max(this.options.width, 0.3 * this.networkScaleInv);
}
}
}
_getViaCoordinates() {
if (this.options.smooth.dynamic == true && this.options.smooth.enabled == true) {
return this.via;
}
else if (this.options.smooth.enabled == false) {
return {x: 0, y: 0};
}
else {
let xVia = undefined;
let yVia = undefined;
let factor = this.options.smooth.roundness;
let type = this.options.smooth.type;
let dx = Math.abs(this.from.x - this.to.x);
let 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)) { // up - down
xVia = this.from.x;
if (this.from.y < this.to.y) {
yVia = this.to.y - (1 - factor) * dy;
}
else {
yVia = this.to.y + (1 - factor) * dy;
}
}
else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { // left - right
if (this.from.x < this.to.x) {
xVia = this.to.x - (1 - factor) * dx;
}
else {
xVia = this.to.x + (1 - factor) * dx;
}
yVia = this.from.y;
}
}
else if (type == 'horizontal') {
if (this.from.x < this.to.x) {
xVia = this.to.x - (1 - factor) * dx;
}
else {
xVia = this.to.x + (1 - factor) * dx;
}
yVia = this.from.y;
}
else if (type == 'vertical') {
xVia = this.from.x;
if (this.from.y < this.to.y) {
yVia = this.to.y - (1 - factor) * dy;
}
else {
yVia = this.to.y + (1 - factor) * dy;
}
}
else if (type == 'curvedCW') {
dx = this.to.x - this.from.x;
dy = this.from.y - this.to.y;
let radius = Math.sqrt(dx * dx + dy * dy);
let pi = Math.PI;
let originalAngle = Math.atan2(dy, dx);
let myAngle = (originalAngle + ((factor * 0.5) + 0.5) * pi) % (2 * pi);
xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle);
yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle);
}
else if (type == 'curvedCCW') {
dx = this.to.x - this.from.x;
dy = this.from.y - this.to.y;
let radius = Math.sqrt(dx * dx + dy * dy);
let pi = Math.PI;
let originalAngle = Math.atan2(dy, dx);
let myAngle = (originalAngle + ((-factor * 0.5) + 0.5) * pi) % (2 * pi);
xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle);
yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle);
}
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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
* @private
*/
_line(ctx) {
// draw a straight line
ctx.beginPath();
ctx.moveTo(this.from.x, this.from.y);
if (this.options.smooth.enabled == true) {
if (this.options.smooth.dynamic == false) {
var via = this._getViaCoordinates();
if (via.x === undefined) {
ctx.lineTo(this.to.x, this.to.y);
ctx.stroke();
return undefined;
}
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();
//ctx.circle(via.x,via.y,2)
//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 undefined;
}
}
/**
* Draw a line from a node to itself, a circle
* @param {CanvasRenderingContext2D} ctx
* @param {Number} x
* @param {Number} y
* @param {Number} radius
* @private
*/
_circle(ctx, x, y, radius) {
// draw a circle
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.stroke();
}
/**
* Draw label with white background and with the middle at (x, y)
* @param {CanvasRenderingContext2D} ctx
* @param {String} text
* @param {Number} x
* @param {Number} y
* @private
*/
/**
* Rotates the canvas so the text is most readable
* @param {CanvasRenderingContext2D} ctx
@ -764,94 +369,6 @@ class Edge {
}
/**
* Redraw a edge as a dashes line
* Draw this edge in the given canvas
* @author David Jordan
* @date 2012-08-08
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
* @param {CanvasRenderingContext2D} ctx
* @private
*/
_drawDashLine(ctx) {
// set style
ctx.strokeStyle = this._getColor(ctx);
ctx.lineWidth = this._getLineWidth();
var via = undefined;
// only firefox and chrome support this method, else we use the legacy one.
if (ctx.setLineDash !== undefined) {
ctx.save();
// configure the dash pattern
var pattern = [0];
if (this.options.dashes.length !== undefined && this.options.dashes.gap !== undefined) {
pattern = [this.options.dashes.length, this.options.dashes.gap];
}
else {
pattern = [5, 5];
}
// set dash settings for chrome or firefox
ctx.setLineDash(pattern);
ctx.lineDashOffset = 0;
// draw the line
via = this._line(ctx);
// restore the dash settings.
ctx.setLineDash([0]);
ctx.lineDashOffset = 0;
ctx.restore();
}
else { // unsupporting smooth lines
// draw dashes line
ctx.beginPath();
ctx.lineCap = 'round';
if (this.options.dashes.altLength !== undefined) //If an alt dash value has been set add to the array this value
{
ctx.dashesLine(this.from.x, this.from.y, this.to.x, this.to.y,
[this.options.dashes.length, this.options.dashes.gap, this.options.dashes.altLength, this.options.dashes.gap]);
}
else if (this.options.dashes.length !== undefined && this.options.dashes.gap !== undefined) //If a dash and gap value has been set add to the array this value
{
ctx.dashesLine(this.from.x, this.from.y, this.to.x, this.to.y,
[this.options.dashes.length, this.options.dashes.gap]);
}
else //If all else fails draw a line
{
ctx.moveTo(this.from.x, this.from.y);
ctx.lineTo(this.to.x, this.to.y);
}
ctx.stroke();
}
return via;
}
/**
* Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way
* @param percentage
* @param via
* @returns {{x: number, y: number}}
* @private
*/
_pointOnEdge(percentage, via = this._getViaCoordinates()) {
if (this.options.smooth.enabled == false) {
return {
x: (1 - percentage) * this.from.x + percentage * this.to.x,
y: (1 - percentage) * this.from.y + percentage * this.to.y
}
}
else {
var t = percentage;
var x = Math.pow(1 - t, 2) * this.from.x + (2 * t * (1 - t)) * via.x + Math.pow(t, 2) * this.to.x;
var y = Math.pow(1 - t, 2) * this.from.y + (2 * t * (1 - t)) * via.y + Math.pow(t, 2) * this.to.y;
return {x: x, y: y};
}
}
/**
* Get a point on a circle
* @param {Number} x
@ -869,121 +386,7 @@ class Edge {
}
}
/**
* This function uses binary search to look for the point where the bezier curve crosses the border of the node.
*
* @param nearNode
* @param ctx
* @param viaNode
* @param nearNode
* @param ctx
* @param viaNode
* @param nearNode
* @param ctx
* @param viaNode
*/
_findBorderPositionBezier(nearNode, ctx, viaNode = this._getViaCoordinates()) {
var maxIterations = 10;
var iteration = 0;
var low = 0;
var high = 1;
var pos, angle, distanceToBorder, distanceToPoint, difference;
var threshold = 0.2;
var node = this.to;
var from = false;
if (nearNode.id === this.from.id) {
node = this.from;
from = true;
}
while (low <= high && iteration < maxIterations) {
var middle = (low + high) * 0.5;
pos = this._pointOnEdge(middle, viaNode);
angle = Math.atan2((node.y - pos.y), (node.x - pos.x));
distanceToBorder = node.distanceToBorder(ctx, angle);
distanceToPoint = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2));
difference = distanceToBorder - distanceToPoint;
if (Math.abs(difference) < threshold) {
break; // found
}
else if (difference < 0) { // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node.
if (from == false) {
low = middle;
}
else {
high = middle;
}
}
else {
if (from == false) {
high = middle;
}
else {
low = middle;
}
}
iteration++;
}
pos.t = middle;
return pos;
}
/**
* This function uses binary search to look for the point where the circle crosses the border of the node.
* @param x
* @param y
* @param radius
* @param node
* @param low
* @param high
* @param direction
* @param ctx
* @returns {*}
* @private
*/
_findBorderPositionCircle(x,y,radius,node,low,high,direction,ctx) {
var maxIterations = 10;
var iteration = 0;
var pos, angle, distanceToBorder, distanceToPoint, difference;
var threshold = 0.05;
while (low <= high && iteration < maxIterations) {
var middle = (low + high) * 0.5;
pos = this._pointOnCircle(x,y,radius,middle);
angle = Math.atan2((node.y - pos.y), (node.x - pos.x));
distanceToBorder = node.distanceToBorder(ctx, angle);
distanceToPoint = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2));
difference = distanceToBorder - distanceToPoint;
if (Math.abs(difference) < threshold) {
break; // found
}
else if (difference > 0) { // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node.
if (direction > 0) {
low = middle;
}
else {
high = middle;
}
}
else {
if (direction > 0) {
high = middle;
}
else {
low = middle;
}
}
iteration++;
}
pos.t = middle;
return pos;
}
/**
*
@ -993,18 +396,18 @@ class Edge {
*/
_drawArrowHead(ctx,position,viaNode) {
// set style
ctx.strokeStyle = this._getColor(ctx);
ctx.strokeStyle = this.edgeType.getColor(ctx);
ctx.fillStyle = ctx.strokeStyle;
ctx.lineWidth = this._getLineWidth();
ctx.lineWidth = this.edgeType.getLineWidth();
// set vars
var angle;
var length;
var arrowPos;
var node1;
var node2;
var guideOffset;
var scaleFactor;
// set lets
let angle;
let length;
let arrowPos;
let node1;
let node2;
let guideOffset;
let scaleFactor;
if (position == 'from') {
node1 = this.from;
@ -1029,26 +432,18 @@ class Edge {
if (position !== 'middle') {
// draw arrow head
if (this.options.smooth.enabled == true) {
arrowPos = this._findBorderPositionBezier(node1, ctx, viaNode);
var guidePos = this._pointOnEdge(Math.max(0.0,Math.min(1.0,arrowPos.t + guideOffset)), viaNode);
arrowPos = this.edgeType.findBorderPosition(node1, ctx, {via:viaNode});
let guidePos = this.edgeType.getPoint(Math.max(0.0,Math.min(1.0,arrowPos.t + guideOffset)), viaNode);
angle = Math.atan2((arrowPos.y - guidePos.y), (arrowPos.x - guidePos.x));
}
else {
angle = Math.atan2((node1.y - node2.y), (node1.x - node2.x));
var dx = (node1.x - node2.x);
var dy = (node1.y - node2.y);
var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
var toBorderDist = this.to.distanceToBorder(ctx, angle);
var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
arrowPos = {};
arrowPos.x = (1 - toBorderPoint) * node2.x + toBorderPoint * node1.x;
arrowPos.y = (1 - toBorderPoint) * node2.y + toBorderPoint * node1.y;
arrowPos = this.edgeType.findBorderPosition(node1, ctx);
}
}
else {
angle = Math.atan2((node1.y - node2.y), (node1.x - node2.x));
arrowPos = this._pointOnEdge(0.6, viaNode); // this is 0.6 to account for the size of the arrow.
arrowPos = this.edgeType.getPoint(0.6, viaNode); // this is 0.6 to account for the size of the arrow.
}
// draw arrow at the end of the line
length = (10 + 5 * this.options.width) * scaleFactor;
@ -1058,9 +453,9 @@ class Edge {
}
else {
// draw circle
var angle, point;
var x, y;
var radius = this.options.selfReferenceSize;
let angle, point;
let x, y;
let radius = this.options.selfReferenceSize;
if (!node1.width) {
node1.resize(ctx);
}
@ -1077,131 +472,26 @@ class Edge {
if (position == 'from') {
point = this._findBorderPositionCircle(x, y, radius, node1, 0.25, 0.6, -1, ctx);
point = this.edgeType.findBorderPosition(x, y, radius, node1, 0.25, 0.6, -1, ctx);
angle = point.t * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI;
}
else if (position == 'to') {
point = this._findBorderPositionCircle(x, y, radius, node1, 0.6, 0.8, 1, ctx);
point = this.edgeType.findBorderPosition(x, y, radius, node1, 0.6, 0.8, 1, ctx);
angle = point.t * -2 * Math.PI + 1.5 * Math.PI - 1.1 * Math.PI;
}
else {
point = this._pointOnCircle(x,y,radius,0.175);
point = this.edgeType.findBorderPosition(x,y,radius,0.175);
angle = 3.9269908169872414; // == 0.175 * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI;
}
// draw the arrowhead
var length = (10 + 5 * this.options.width) * scaleFactor;
let length = (10 + 5 * this.options.width) * scaleFactor;
ctx.arrow(point.x, point.y, angle, length);
ctx.fill();
ctx.stroke();
}
}
/**
* Calculate the distance between a point (x3,y3) and a line segment from
* (x1,y1) to (x2,y2).
* http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} x3
* @param {number} y3
* @private
*/
_getDistanceToEdge(x1, y1, x2, y2, x3, y3) { // x3,y3 is the point
var returnValue = 0;
if (this.from != this.to) {
if (this.options.smooth.enabled == true) {
var xVia, yVia;
if (this.options.smooth.enabled == true && this.options.smooth.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 distance;
var i, t, x, y;
var lastX = x1;
var lastY = y1;
for (i = 1; i < 10; i++) {
t = 0.1 * i;
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;
if (i > 0) {
distance = this._getDistanceToLine(lastX, lastY, x, y, x3, y3);
minDistance = distance < minDistance ? distance : minDistance;
}
lastX = x;
lastY = y;
}
returnValue = minDistance;
}
else {
returnValue = this._getDistanceToLine(x1, y1, x2, y2, x3, y3);
}
}
else {
var x, y, dx, dy;
var radius = this.options.selfReferenceSize;
var node = this.from;
if (node.width > node.height) {
x = node.x + 0.5 * node.width;
y = node.y - radius;
}
else {
x = node.x + radius;
y = node.y - 0.5 * node.height;
}
dx = x - x3;
dy = y - y3;
returnValue = Math.abs(Math.sqrt(dx * dx + dy * dy) - radius);
}
if (this.labelModule.size.left < x3 &&
this.labelModule.size.left + this.labelModule.size.width > x3 &&
this.labelModule.size.top < y3 &&
this.labelModule.size.top + this.labelModule.size.height > y3) {
return 0;
}
else {
return returnValue;
}
}
_getDistanceToLine(x1, y1, x2, y2, x3, y3) {
var px = x2 - x1;
var py = y2 - y1;
var something = px * px + py * py;
var u = ((x3 - x1) * px + (y3 - y1) * py) / something;
if (u > 1) {
u = 1;
}
else if (u < 0) {
u = 0;
}
var x = x1 + u * px;
var y = y1 + u * py;
var dx = x - x3;
var dy = y - y3;
//# Note: If the actual distance does not matter,
//# if you only want to compare what this function
//# returns to other results of this function, you
//# can just return the squared distance instead
//# (i.e. remove the sqrt) to gain a little performance
return Math.sqrt(dx * dx + dy * dy);
}
/**
* This allows the zoom level of the network to influence the rendering
*
@ -1220,16 +510,7 @@ class Edge {
this.selected = false;
}
positionBezierNode() {
if (this.via !== undefined && this.from !== undefined && this.to !== undefined) {
this.via.x = 0.5 * (this.from.x + this.to.x);
this.via.y = 0.5 * (this.from.y + this.to.y);
}
else if (this.via !== undefined) {
this.via.x = 0;
this.via.y = 0;
}
}

+ 23
- 16
lib/network/modules/components/Node.js View File

@ -2,19 +2,19 @@ var util = require('../../../util');
import Label from './unified/label'
import Box from './nodes/box'
import Circle from './nodes/circle'
import CircularImage from './nodes/circularImage'
import Database from './nodes/database'
import Dot from './nodes/dot'
import Ellipse from './nodes/ellipse'
import Icon from './nodes/icon'
import Image from './nodes/image'
import Square from './nodes/square'
import Star from './nodes/star'
import Text from './nodes/text'
import Triangle from './nodes/triangle'
import TriangleDown from './nodes/triangleDown'
import Box from './nodes/shapes/box'
import Circle from './nodes/shapes/circle'
import CircularImage from './nodes/shapes/circularImage'
import Database from './nodes/shapes/database'
import Dot from './nodes/shapes/dot'
import Ellipse from './nodes/shapes/ellipse'
import Icon from './nodes/shapes/icon'
import Image from './nodes/shapes/image'
import Square from './nodes/shapes/square'
import Star from './nodes/shapes/star'
import Text from './nodes/shapes/text'
import Triangle from './nodes/shapes/triangle'
import TriangleDown from './nodes/shapes/triangleDown'
/**
* @class Node
@ -97,6 +97,13 @@ class Node {
}
}
/**
* Enable or disable the physics.
* @param status
*/
togglePhysics(status) {
this.options.physics = status;
}
/**
@ -274,8 +281,8 @@ class Node {
* @private
*/
_reset() {
this.width = undefined;
this.height = undefined;
this.shape.width = undefined;
this.shape.height = undefined;
}
@ -305,7 +312,7 @@ class Node {
* @return {boolean} true if fixed, false if not
*/
isFixed() {
return (this.xFixed && this.yFixed);
return (this.options.fixed.x && this.options.fixed.y);
}

+ 116
- 0
lib/network/modules/components/edges/bezierEdgeDynamic.js View File

@ -0,0 +1,116 @@
/**
* Created by Alex on 3/20/2015.
*/
import BezierBaseEdge from './util/bezierBaseEdge'
class BezierEdgeDynamic extends BezierBaseEdge {
constructor(options, body, labelModule) {
this.initializing = true;
this.via = undefined;
super(options, body, labelModule);
this.initializing = false;
}
setOptions(options) {
this.options = options;
this.from = this.body.nodes[this.options.from];
this.to = this.body.nodes[this.options.to];
this.id = this.options.id;
this.setupSupportNode(this.initializing);
}
cleanup() {
if (this.via !== undefined) {
delete this.body.nodes[this.via.id];
this.via = undefined;
this.body.emitter.emit("_dataChanged");
}
}
/**
* Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
* are used for the force calculation.
*
* @private
*/
setupSupportNode(doNotEmit = false) {
var changedData = false;
if (this.via === undefined) {
changedData = true;
var nodeId = "edgeId:" + this.id;
var node = this.body.functions.createNode({
id: nodeId,
mass: 1,
shape: 'circle',
image: "",
physics:true,
hidden:true
});
this.body.nodes[nodeId] = node;
this.via = node;
this.via.parentEdgeId = this.id;
this.positionBezierNode();
}
// node has been added or deleted
if (changedData === true && doNotEmit === false) {
this.body.emitter.emit("_dataChanged");
}
}
positionBezierNode() {
if (this.via !== undefined && this.from !== undefined && this.to !== undefined) {
this.via.x = 0.5 * (this.from.x + this.to.x);
this.via.y = 0.5 * (this.from.y + this.to.y);
}
else if (this.via !== undefined) {
this.via.x = 0;
this.via.y = 0;
}
}
/**
* Draw a line between two nodes
* @param {CanvasRenderingContext2D} ctx
* @private
*/
_line(ctx) {
// draw a straight line
ctx.beginPath();
ctx.moveTo(this.from.x, this.from.y);
ctx.quadraticCurveTo(this.via.x, this.via.y, this.to.x, this.to.y);
ctx.stroke();
return this.via;
}
/**
* Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way
* @param percentage
* @param via
* @returns {{x: number, y: number}}
* @private
*/
getPoint(percentage) {
let t = percentage;
let x = Math.pow(1 - t, 2) * this.from.x + (2 * t * (1 - t)) * this.via.x + Math.pow(t, 2) * this.to.x;
let y = Math.pow(1 - t, 2) * this.from.y + (2 * t * (1 - t)) * this.via.y + Math.pow(t, 2) * this.to.y;
return {x: x, y: y};
}
_findBorderPosition(nearNode, ctx) {
console.log(this)
return this._findBorderPositionBezier(nearNode, ctx, this.via);
}
_getDistanceToEdge(x1, y1, x2, y2, x3, y3) { // x3,y3 is the point
return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, this.via);
}
}
export default BezierEdgeDynamic;

+ 240
- 0
lib/network/modules/components/edges/bezierEdgeStatic.js View File

@ -0,0 +1,240 @@
/**
* Created by Alex on 3/20/2015.
*/
import BezierBaseEdge from './util/bezierBaseEdge'
class BezierEdgeStatic extends BezierBaseEdge {
constructor(options, body, labelModule) {
super(options, body, labelModule);
}
cleanup() {}
/**
* Draw a line between two nodes
* @param {CanvasRenderingContext2D} ctx
* @private
*/
_line(ctx) {
// draw a straight line
ctx.beginPath();
ctx.moveTo(this.from.x, this.from.y);
var via = this._getViaCoordinates();
// fallback to normal straight edges
if (via.x === undefined) {
ctx.lineTo(this.to.x, this.to.y);
ctx.stroke();
return undefined;
}
else {
ctx.quadraticCurveTo(via.x, via.y, this.to.x, this.to.y);
ctx.stroke();
return via;
}
}
_getViaCoordinates() {
let xVia = undefined;
let yVia = undefined;
let factor = this.options.smooth.roundness;
let type = this.options.smooth.type;
let dx = Math.abs(this.from.x - this.to.x);
let 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)) { // up - down
xVia = this.from.x;
if (this.from.y < this.to.y) {
yVia = this.to.y - (1 - factor) * dy;
}
else {
yVia = this.to.y + (1 - factor) * dy;
}
}
else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { // left - right
if (this.from.x < this.to.x) {
xVia = this.to.x - (1 - factor) * dx;
}
else {
xVia = this.to.x + (1 - factor) * dx;
}
yVia = this.from.y;
}
}
else if (type == 'horizontal') {
if (this.from.x < this.to.x) {
xVia = this.to.x - (1 - factor) * dx;
}
else {
xVia = this.to.x + (1 - factor) * dx;
}
yVia = this.from.y;
}
else if (type == 'vertical') {
xVia = this.from.x;
if (this.from.y < this.to.y) {
yVia = this.to.y - (1 - factor) * dy;
}
else {
yVia = this.to.y + (1 - factor) * dy;
}
}
else if (type == 'curvedCW') {
dx = this.to.x - this.from.x;
dy = this.from.y - this.to.y;
let radius = Math.sqrt(dx * dx + dy * dy);
let pi = Math.PI;
let originalAngle = Math.atan2(dy, dx);
let myAngle = (originalAngle + ((factor * 0.5) + 0.5) * pi) % (2 * pi);
xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle);
yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle);
}
else if (type == 'curvedCCW') {
dx = this.to.x - this.from.x;
dy = this.from.y - this.to.y;
let radius = Math.sqrt(dx * dx + dy * dy);
let pi = Math.PI;
let originalAngle = Math.atan2(dy, dx);
let myAngle = (originalAngle + ((-factor * 0.5) + 0.5) * pi) % (2 * pi);
xVia = this.from.x + (factor * 0.5 + 0.5) * radius * Math.sin(myAngle);
yVia = this.from.y + (factor * 0.5 + 0.5) * radius * Math.cos(myAngle);
}
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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};
}
_findBorderPosition(nearNode, ctx, options) {
return this._findBorderPositionBezier(nearNode, ctx, options.via);
}
_getDistanceToEdge(x1, y1, x2, y2, x3, y3, via = this._getViaCoordinates()) { // x3,y3 is the point
return this._getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via);
}
/**
* Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way
* @param percentage
* @param via
* @returns {{x: number, y: number}}
* @private
*/
getPoint(percentage, via = this._getViaCoordinates()) {
var t = percentage;
var x = Math.pow(1 - t, 2) * this.from.x + (2 * t * (1 - t)) * via.x + Math.pow(t, 2) * this.to.x;
var y = Math.pow(1 - t, 2) * this.from.y + (2 * t * (1 - t)) * via.y + Math.pow(t, 2) * this.to.y;
return {x: x, y: y};
}
}
export default BezierEdgeStatic;

+ 66
- 0
lib/network/modules/components/edges/straightEdge.js View File

@ -0,0 +1,66 @@
/**
* Created by Alex on 3/20/2015.
*/
import BaseEdge from './util/baseEdge'
class StraightEdge extends BaseEdge {
constructor(options, body, labelModule) {
super(options, body, labelModule);
}
cleanup() {}
/**
* Draw a line between two nodes
* @param {CanvasRenderingContext2D} ctx
* @private
*/
_line(ctx) {
// draw a straight line
ctx.beginPath();
ctx.moveTo(this.from.x, this.from.y);
ctx.lineTo(this.to.x, this.to.y);
ctx.stroke();
return undefined;
}
/**
* Combined function of pointOnLine and pointOnBezier. This gives the coordinates of a point on the line at a certain percentage of the way
* @param percentage
* @param via
* @returns {{x: number, y: number}}
* @private
*/
getPoint(percentage) {
return {
x: (1 - percentage) * this.from.x + percentage * this.to.x,
y: (1 - percentage) * this.from.y + percentage * this.to.y
}
}
_findBorderPosition(nearNode, ctx) {
let node1 = this.to;
let node2 = this.from;
let angle = Math.atan2((node1.y - node2.y), (node1.x - node2.x));
let dx = (node1.x - node2.x);
let dy = (node1.y - node2.y);
let edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
let toBorderDist = nearNode.distanceToBorder(ctx, angle);
let toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
let borderPos = {};
borderPos.x = (1 - toBorderPoint) * node2.x + toBorderPoint * node1.x;
borderPos.y = (1 - toBorderPoint) * node2.y + toBorderPoint * node1.y;
return borderPos;
}
_getDistanceToEdge(x1, y1, x2, y2, x3, y3) { // x3,y3 is the point
return this._getDistanceToLine(x1, y1, x2, y2, x3, y3);
}
}
export default StraightEdge;

+ 351
- 0
lib/network/modules/components/edges/util/baseEdge.js View File

@ -0,0 +1,351 @@
/**
* Created by Alex on 3/20/2015.
*/
var util = require("../../../../../util")
class BaseEdge {
constructor(options, body, labelModule) {
this.body = body;
this.labelModule = labelModule;
this.setOptions(options);
this.colorDirty = true;
}
setOptions(options) {
this.options = options;
this.from = this.body.nodes[this.options.from];
this.to = this.body.nodes[this.options.to];
this.id = this.options.id;
}
/**
* Redraw a edge as a line
* Draw this edge in the given canvas
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
* @param {CanvasRenderingContext2D} ctx
* @private
*/
drawLine(ctx, selected, hover) {
// set style
ctx.strokeStyle = this.getColor(ctx);
ctx.lineWidth = this.getLineWidth();
let via = undefined;
if (this.from != this.to) {
// draw line
if (this.options.dashes.enabled == true) {
via = this._drawDashedLine(ctx);
}
else {
via = this._line(ctx);
}
}
else {
let x, y;
let radius = this.options.selfReferenceSize;
let node = this.from;
node.resize(ctx);
if (node.shape.width > node.shape.height) {
x = node.x + node.shape.width * 0.5;
y = node.y - radius;
}
else {
x = node.x + radius;
y = node.y - node.shape.height * 0.5;
}
this._circle(ctx, x, y, radius);
}
return via;
}
_drawDashedLine(ctx) {
let via = undefined;
// only firefox and chrome support this method, else we use the legacy one.
if (ctx.setLineDash !== undefined) {
ctx.save();
// configure the dash pattern
var pattern = [0];
if (this.options.dashes.length !== undefined && this.options.dashes.gap !== undefined) {
pattern = [this.options.dashes.length, this.options.dashes.gap];
}
else {
pattern = [5, 5];
}
// set dash settings for chrome or firefox
ctx.setLineDash(pattern);
ctx.lineDashOffset = 0;
// draw the line
via = this._line(ctx);
// restore the dash settings.
ctx.setLineDash([0]);
ctx.lineDashOffset = 0;
ctx.restore();
}
else { // unsupporting smooth lines
// draw dashes line
ctx.beginPath();
ctx.lineCap = 'round';
if (this.options.dashes.altLength !== undefined) //If an alt dash value has been set add to the array this value
{
ctx.dashesLine(this.from.x, this.from.y, this.to.x, this.to.y,
[this.options.dashes.length, this.options.dashes.gap, this.options.dashes.altLength, this.options.dashes.gap]);
}
else if (this.options.dashes.length !== undefined && this.options.dashes.gap !== undefined) //If a dash and gap value has been set add to the array this value
{
ctx.dashesLine(this.from.x, this.from.y, this.to.x, this.to.y,
[this.options.dashes.length, this.options.dashes.gap]);
}
else //If all else fails draw a line
{
ctx.moveTo(this.from.x, this.from.y);
ctx.lineTo(this.to.x, this.to.y);
}
ctx.stroke();
}
return via;
}
findBorderPosition(nearNode, ctx, options) {
if (this.from != this.to) {
console.log(1)
return this._findBorderPosition(nearNode, ctx, options);
}
else {
return this._findBorderPositionCircle(nearNode, ctx, options);
}
}
/**
* This function uses binary search to look for the point where the circle crosses the border of the node.
* @param x
* @param y
* @param radius
* @param node
* @param low
* @param high
* @param direction
* @param ctx
* @returns {*}
* @private
*/
_findBorderPositionCircle(node, ctx, options) {
let x = options.x;
let y = options.y;
let low = options.low;
let high = options.high;
let direction = options.direction;
let maxIterations = 10;
let iteration = 0;
let radius = this.options.selfReferenceSize;
let pos, angle, distanceToBorder, distanceToPoint, difference;
let threshold = 0.05;
while (low <= high && iteration < maxIterations) {
let middle = (low + high) * 0.5;
pos = this._pointOnCircle(x,y,radius,middle);
angle = Math.atan2((node.y - pos.y), (node.x - pos.x));
distanceToBorder = node.distanceToBorder(ctx, angle);
distanceToPoint = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2));
difference = distanceToBorder - distanceToPoint;
if (Math.abs(difference) < threshold) {
break; // found
}
else if (difference > 0) { // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node.
if (direction > 0) {
low = middle;
}
else {
high = middle;
}
}
else {
if (direction > 0) {
high = middle;
}
else {
low = middle;
}
}
iteration++;
}
pos.t = middle;
return pos;
}
/**
* Get the line width of the edge. Depends on width and whether one of the
* connected nodes is selected.
* @return {Number} width
* @private
*/
getLineWidth(selected, hover) {
if (selected == true) {
return Math.max(Math.min(this.options.widthSelectionMultiplier * this.options.width, this.options.scaling.max), 0.3 / this.body.view.scale);
}
else {
if (hover == true) {
return Math.max(Math.min(this.options.hoverWidth, this.options.scaling.max), 0.3 / this.body.view.scale);
}
else {
return Math.max(this.options.width, 0.3 / this.body.view.scale);
}
}
}
getColor(ctx) {
var colorObj = this.options.color;
if (colorObj.inherit.enabled === true) {
if (colorObj.inherit.useGradients == true) {
var grd = ctx.createLinearGradient(this.from.x, this.from.y, this.to.x, this.to.y);
var fromColor, toColor;
fromColor = this.from.options.color.highlight.border;
toColor = this.to.options.color.highlight.border;
if (this.from.selected == false && this.to.selected == false) {
fromColor = util.overrideOpacity(this.from.options.color.border, this.options.color.opacity);
toColor = util.overrideOpacity(this.to.options.color.border, this.options.color.opacity);
}
else if (this.from.selected == true && this.to.selected == false) {
toColor = this.to.options.color.border;
}
else if (this.from.selected == false && this.to.selected == true) {
fromColor = this.from.options.color.border;
}
grd.addColorStop(0, fromColor);
grd.addColorStop(1, toColor);
// -------------------- this returns -------------------- //
return grd;
}
if (this.colorDirty === true) {
if (colorObj.inherit.source == "to") {
colorObj.highlight = this.to.options.color.highlight.border;
colorObj.hover = this.to.options.color.hover.border;
colorObj.color = util.overrideOpacity(this.to.options.color.border, this.options.color.opacity);
}
else { // (this.options.color.inherit.source == "from") {
colorObj.highlight = this.from.options.color.highlight.border;
colorObj.hover = this.from.options.color.hover.border;
colorObj.color = util.overrideOpacity(this.from.options.color.border, this.options.color.opacity);
}
}
}
// if color inherit is on and gradients are used, the function has already returned by now.
this.colorDirty = false;
if (this.selected == true) {
return colorObj.highlight;
}
else if (this.hover == true) {
return colorObj.hover;
}
else {
return colorObj.color;
}
}
/**
* Draw a line from a node to itself, a circle
* @param {CanvasRenderingContext2D} ctx
* @param {Number} x
* @param {Number} y
* @param {Number} radius
* @private
*/
_circle(ctx, x, y, radius) {
// draw a circle
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.stroke();
}
/**
* Calculate the distance between a point (x3,y3) and a line segment from
* (x1,y1) to (x2,y2).
* http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} x3
* @param {number} y3
* @private
*/
getDistanceToEdge(x1, y1, x2, y2, x3, y3, via) { // x3,y3 is the point
var returnValue = 0;
if (this.from != this.to) {
returnValue = this._getDistanceToEdge(x1, y1, x2, y2, x3, y3, via)
}
else {
var x, y, dx, dy;
var radius = this.options.selfReferenceSize;
var node = this.from;
if (node.width > node.height) {
x = node.x + 0.5 * node.width;
y = node.y - radius;
}
else {
x = node.x + radius;
y = node.y - 0.5 * node.height;
}
dx = x - x3;
dy = y - y3;
returnValue = Math.abs(Math.sqrt(dx * dx + dy * dy) - radius);
}
if (this.labelModule.size.left < x3 &&
this.labelModule.size.left + this.labelModule.size.width > x3 &&
this.labelModule.size.top < y3 &&
this.labelModule.size.top + this.labelModule.size.height > y3) {
return 0;
}
else {
return returnValue;
}
}
_getDistanceToLine(x1, y1, x2, y2, x3, y3) {
var px = x2 - x1;
var py = y2 - y1;
var something = px * px + py * py;
var u = ((x3 - x1) * px + (y3 - y1) * py) / something;
if (u > 1) {
u = 1;
}
else if (u < 0) {
u = 0;
}
var x = x1 + u * px;
var y = y1 + u * py;
var dx = x - x3;
var dy = y - y3;
//# Note: If the actual distance does not matter,
//# if you only want to compare what this function
//# returns to other results of this function, you
//# can just return the squared distance instead
//# (i.e. remove the sqrt) to gain a little performance
return Math.sqrt(dx * dx + dy * dy);
}
}
export default BaseEdge;

+ 116
- 0
lib/network/modules/components/edges/util/bezierBaseEdge.js View File

@ -0,0 +1,116 @@
/**
* Created by Alex on 3/20/2015.
*/
import BaseEdge from './baseEdge'
class BezierBaseEdge extends BaseEdge {
constructor(options, body, labelModule) {
super(options, body, labelModule);
}
/**
* This function uses binary search to look for the point where the bezier curve crosses the border of the node.
*
* @param nearNode
* @param ctx
* @param viaNode
* @param nearNode
* @param ctx
* @param viaNode
* @param nearNode
* @param ctx
* @param viaNode
*/
_findBorderPositionBezier(nearNode, ctx, viaNode = this._getViaCoordinates()) {
console.log(nearNode, ctx, viaNode)
var maxIterations = 10;
var iteration = 0;
var low = 0;
var high = 1;
var pos, angle, distanceToBorder, distanceToPoint, difference;
var threshold = 0.2;
var node = this.to;
var from = false;
if (nearNode.id === this.from.id) {
node = this.from;
from = true;
}
while (low <= high && iteration < maxIterations) {
var middle = (low + high) * 0.5;
pos = this.getPoint(middle, viaNode);
angle = Math.atan2((node.y - pos.y), (node.x - pos.x));
distanceToBorder = node.distanceToBorder(ctx, angle);
distanceToPoint = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2));
difference = distanceToBorder - distanceToPoint;
console.log(pos, difference, middle)
if (Math.abs(difference) < threshold) {
break; // found
}
else if (difference < 0) { // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node.
if (from == false) {
low = middle;
}
else {
high = middle;
}
}
else {
if (from == false) {
high = middle;
}
else {
low = middle;
}
}
iteration++;
}
pos.t = middle;
return pos;
}
/**
* Calculate the distance between a point (x3,y3) and a line segment from
* (x1,y1) to (x2,y2).
* http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} x3
* @param {number} y3
* @private
*/
_getDistanceToBezierEdge(x1, y1, x2, y2, x3, y3, via) { // x3,y3 is the point
let xVia, yVia;
xVia = via.x;
yVia = via.y;
let minDistance = 1e9;
let distance;
let i, t, x, y;
let lastX = x1;
let lastY = y1;
for (i = 1; i < 10; i++) {
t = 0.1 * i;
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;
if (i > 0) {
distance = this._getDistanceToLine(lastX, lastY, x, y, x3, y3);
minDistance = distance < minDistance ? distance : minDistance;
}
lastX = x;
lastY = y;
}
return minDistance;
}
}
export default BezierBaseEdge;

+ 16
- 0
lib/network/modules/components/nodes/cluster.js View File

@ -0,0 +1,16 @@
import Node from '../Node'
/**
*
*/
class Cluster extends Node {
constructor(options, body, imagelist, grouplist, globalOptions) {
super(options, body, imagelist, grouplist, globalOptions);
this.isCluster = true;
this.containedNodes = {};
this.containedEdges = {};
}
}
export default Cluster;

+ 0
- 26
lib/network/modules/components/nodes/empty.js View File

@ -1,26 +0,0 @@
/**
* Created by Alex on 3/18/2015.
*/
'use strict';
class Empty {
constructor (options, labelModule) {
this.labelModule = labelModule;
this.setOptions(options);
this.top = undefined;
this.left = undefined;
this.height = undefined;
this.height = undefined;
this.boundingBox = {top: 0, left: 0, right: 0, bottom: 0};
}
setOptions() {}
resize() {}
draw() {}
distanceToBorder() {}
}
export default Empty;

+ 0
- 126
lib/network/modules/components/nodes/nodeUtil.js View File

@ -1,126 +0,0 @@
/**
* Created by Alex on 3/19/2015.
*/
class NodeUtil {
constructor(options, body, labelModule) {
this.body = body;
this.labelModule = labelModule;
this.setOptions(options);
this.top = undefined;
this.left = undefined;
this.height = undefined;
this.height = undefined;
this.boundingBox = {top: 0, left: 0, right: 0, bottom: 0};
}
_drawRawCircle(ctx, x, y, selected, hover, size) {
var borderWidth = this.options.borderWidth;
var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border;
ctx.lineWidth = (selected ? selectionLineWidth : borderWidth);
ctx.lineWidth *= this.networkScaleInv;
ctx.lineWidth = Math.min(this.width, ctx.lineWidth);
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
ctx.circle(x, y, size);
ctx.fill();
ctx.stroke();
}
_drawImageAtPosition(ctx) {
if (this.imageObj.width != 0) {
// draw the image
ctx.globalAlpha = 1.0;
ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height);
}
}
_distanceToBorder(angle) {
var borderWidth = 1;
return Math.min(
Math.abs(this.width / 2 / Math.cos(angle)),
Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth;
}
_resizeShape() {
if (this.width === undefined) {
var size = 2 * this.options.size;
this.width = size;
this.height = size;
}
}
_drawShape(ctx, shape, sizeMultiplier, x, y, selected, hover) {
this._resizeShape();
this.left = x - this.width / 2;
this.top = y - this.height / 2;
var borderWidth = this.options.borderWidth;
var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
// choose draw method depending on the shape
switch (shape) {
case 'dot':
sizeMultiplier = 2;
break;
case 'square':
sizeMultiplier = 2;
break;
case 'triangle':
sizeMultiplier = 3;
break;
case 'triangleDown':
sizeMultiplier = 3;
break;
case 'star':
sizeMultiplier = 4;
break;
}
ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border;
ctx.lineWidth = (selected ? selectionLineWidth : borderWidth);
ctx.lineWidth /= this.body.view.scale;
ctx.lineWidth = Math.min(this.width, ctx.lineWidth);
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
ctx[shape](x, y, this.options.size);
ctx.fill();
ctx.stroke();
this.boundingBox.top = y - this.options.size;
this.boundingBox.left = x - this.options.size;
this.boundingBox.right = x + this.options.size;
this.boundingBox.bottom = y + this.options.size;
if (this.options.label!== undefined) {
this.labelModule.draw(ctx, x, y + 0.5* this.height, selected, 'hanging');
this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left);
this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width);
this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelModule.size.height);
}
}
_drawImageLabel(ctx, x, y, selected) {
var yLabel;
var offset = 0;
if (this.height !== undefined) {
offset = this.height * 0.5;
var labelDimensions = this.labelModule.getTextSize(ctx);
if (labelDimensions.lineCount >= 1) {
offset += labelDimensions.height / 2;
offset += 3;
}
}
yLabel = y + offset;
this.labelModule.draw(ctx, x, yLabel, selected, 'hanging');
}
}
export default NodeUtil;

lib/network/modules/components/nodes/box.js → lib/network/modules/components/nodes/shapes/box.js View File

@ -3,20 +3,11 @@
*/
'use strict';
class Box {
constructor (options, body, labelModule) {
this.body = body;
this.labelModule = labelModule;
this.setOptions(options);
this.top = undefined;
this.left = undefined;
this.height = undefined;
this.height = undefined;
this.boundingBox = {top: 0, left: 0, right: 0, bottom: 0};
}
import BaseNode from '../util/baseNode'
setOptions(options) {
this.options = options;
class Box extends BaseNode {
constructor (options, body, labelModule) {
super(options,body,labelModule);
}
resize(ctx) {

lib/network/modules/components/nodes/circle.js → lib/network/modules/components/nodes/shapes/circle.js View File

@ -3,27 +3,17 @@
*/
'use strict';
import NodeUtil from './nodeUtil'
class Circle extends NodeUtil {
constructor (options, labelModule) {
this.labelModule = labelModule;
this.setOptions(options);
this.top = undefined;
this.left = undefined;
this.height = undefined;
this.height = undefined;
this.boundingBox = {top: 0, left: 0, right: 0, bottom: 0};
}
import DrawUtil from '../util/drawUtil'
setOptions(options) {
this.options = options;
class Circle extends DrawUtil {
constructor(options, body, labelModule) {
super(options, body, labelModule)
}
resize(ctx) {
resize(ctx, selected) {
if (this.width === undefined) {
var margin = 5;
var textSize = this.labelModule.getTextSize(ctx,this.selected);
var textSize = this.labelModule.getTextSize(ctx, selected);
var diameter = Math.max(textSize.width, textSize.height) + 2 * margin;
this.options.size = diameter / 2;
@ -33,7 +23,7 @@ class Circle extends NodeUtil {
}
draw(ctx, x, y, selected, hover) {
this.resize(ctx);
this.resize(ctx, selected);
this.left = x - this.width / 2;
this.top = y - this.height / 2;

lib/network/modules/components/nodes/circularImage.js → lib/network/modules/components/nodes/shapes/circularImage.js View File

@ -4,18 +4,14 @@
'use strict';
import NodeUtil from './nodeUtil'
import drawUtil from '../util/drawUtil'
class CircularImage extends NodeUtil {
class CircularImage extends drawUtil {
constructor (options, body, labelModule, imageObj) {
super(options, body, labelModule);
this.imageObj = imageObj;
}
setOptions(options) {
this.options = options;
}
resize(ctx) {
if (this.imageObj.src !== undefined || this.imageObj.width !== undefined || this.imageObj.height !== undefined ) {
if (!this.width) {
@ -41,10 +37,7 @@ class CircularImage extends NodeUtil {
this.left = x - this.width / 2;
this.top = y - this.height / 2;
var centerX = this.left + (this.width / 2);
var centerY = this.top + (this.height / 2);
var size = Math.abs(this.height / 2);
this._drawRawCircle(ctx, x, y, selected, hover, size);
ctx.save();

lib/network/modules/components/nodes/database.js → lib/network/modules/components/nodes/shapes/database.js View File

@ -3,20 +3,11 @@
*/
'use strict';
class Database {
constructor (options, body, labelModule) {
this.body = body;
this.labelModule = labelModule;
this.setOptions(options);
this.top = undefined;
this.left = undefined;
this.height = undefined;
this.height = undefined;
this.boundingBox = {top: 0, left: 0, right: 0, bottom: 0};
}
import BaseNode from '../util/baseNode'
setOptions(options) {
this.options = options;
class Database extends BaseNode {
constructor (options, body, labelModule) {
super(options, body, labelModule);
}
resize(ctx, selected) {

lib/network/modules/components/nodes/dot.js → lib/network/modules/components/nodes/shapes/dot.js View File

@ -3,17 +3,13 @@
*/
'use strict';
import NodeUtil from './nodeUtil'
import ShapeUtil from '../util/shapeUtil'
class Dot extends NodeUtil {
constructor (options, body, labelModule) {
class Dot extends ShapeUtil {
constructor(options, body, labelModule) {
super(options, body, labelModule)
}
setOptions(options) {
this.options = options;
}
resize(ctx) {
this._resizeShape();
}

lib/network/modules/components/nodes/ellipse.js → lib/network/modules/components/nodes/shapes/ellipse.js View File

@ -3,20 +3,11 @@
*/
'use strict';
class Ellipse {
constructor(options, body, labelModule) {
this.body = body;
this.labelModule = labelModule;
this.setOptions(options);
this.top = undefined;
this.left = undefined;
this.height = undefined;
this.height = undefined;
this.boundingBox = {top: 0, left: 0, right: 0, bottom: 0};
}
import BaseNode from '../util/baseNode'
setOptions(options) {
this.options = options;
class Ellipse extends BaseNode {
constructor(options, body, labelModule) {
super(options, body, labelModule);
}
resize(ctx, selected) {

+ 20
- 0
lib/network/modules/components/nodes/shapes/empty.js View File

@ -0,0 +1,20 @@
/**
* Created by Alex on 3/18/2015.
*/
'use strict';
class Empty extends BaseNode {
constructor (options, body, labelModule) {
super(options, body, labelModule);
}
setOptions() {}
resize() {}
draw() {}
distanceToBorder() {}
}
export default Empty;

lib/network/modules/components/nodes/icon.js → lib/network/modules/components/nodes/shapes/icon.js View File

@ -3,17 +3,13 @@
*/
'use strict';
import NodeUtil from './nodeUtil'
import BaseNode from '../util/baseNode'
class Icon extends NodeUtil {
class Icon extends BaseNode {
constructor(options, body, labelModule) {
super(options, body, labelModule);
}
setOptions(options) {
this.options = options;
}
resize(ctx) {
if (this.width === undefined) {
var margin = 5;

lib/network/modules/components/nodes/image.js → lib/network/modules/components/nodes/shapes/image.js View File

@ -3,18 +3,14 @@
*/
'use strict';
import NodeUtil from './nodeUtil'
import drawUtil from '../util/drawUtil'
class Image extends NodeUtil {
class Image extends drawUtil {
constructor (options, body, labelModule, imageObj) {
super(options, body, labelModule);
this.imageObj = imageObj;
}
setOptions(options) {
this.options = options;
}
resize() {
if (!this.width || !this.height) { // undefined or 0
var width, height;

lib/network/modules/components/nodes/square.js → lib/network/modules/components/nodes/shapes/square.js View File

@ -3,18 +3,14 @@
*/
'use strict';
import NodeUtil from './nodeUtil'
import ShapeUtil from '../util/shapeUtil'
class Square extends NodeUtil {
constructor (options, body, labelModule) {
class Square extends ShapeUtil {
constructor(options, body, labelModule) {
super(options, body, labelModule)
}
setOptions(options) {
this.options = options;
}
resize(ctx) {
resize() {
this._resizeShape();
}

lib/network/modules/components/nodes/star.js → lib/network/modules/components/nodes/shapes/star.js View File

@ -3,17 +3,13 @@
*/
'use strict';
import NodeUtil from './nodeUtil'
import ShapeUtil from '../util/shapeUtil'
class Star extends NodeUtil {
constructor (options, body, labelModule) {
class Star extends ShapeUtil {
constructor(options, body, labelModule) {
super(options, body, labelModule)
}
setOptions(options) {
this.options = options;
}
resize(ctx) {
this._resizeShape();
}

lib/network/modules/components/nodes/text.js → lib/network/modules/components/nodes/shapes/text.js View File

@ -3,17 +3,13 @@
*/
'use strict';
import NodeUtil from './nodeUtil'
import BaseNode from '../util/baseNode'
class Text extends NodeUtil {
constructor (options, body, labelModule) {
class Text extends BaseNode {
constructor(options, body, labelModule) {
super(options, body, labelModule);
}
setOptions(options) {
this.options = options;
}
resize(ctx, selected) {
if (this.width === undefined) {
var margin = 5;
@ -37,8 +33,6 @@ class Text extends NodeUtil {
}
distanceToBorder(ctx, angle) {
console.log("hererer")
console.log(this._distanceToBorder(angle))
this.resize(ctx);
return this._distanceToBorder(angle);
}

lib/network/modules/components/nodes/triangle.js → lib/network/modules/components/nodes/shapes/triangle.js View File

@ -3,17 +3,13 @@
*/
'use strict';
import NodeUtil from './nodeUtil'
import ShapeUtil from '../util/shapeUtil'
class Triangle extends NodeUtil {
constructor (options, body, labelModule) {
class Triangle extends ShapeUtil {
constructor(options, body, labelModule) {
super(options, body, labelModule)
}
setOptions(options) {
this.options = options;
}
resize(ctx) {
this._resizeShape();
}

lib/network/modules/components/nodes/triangleDown.js → lib/network/modules/components/nodes/shapes/triangleDown.js View File

@ -3,17 +3,13 @@
*/
'use strict';
import NodeUtil from './nodeUtil'
import ShapeUtil from '../util/shapeUtil'
class TriangleDown extends NodeUtil {
constructor (options, body, labelModule) {
class TriangleDown extends ShapeUtil {
constructor(options, body, labelModule) {
super(options, body, labelModule)
}
setOptions(options) {
this.options = options;
}
resize(ctx) {
this._resizeShape();
}

+ 28
- 0
lib/network/modules/components/nodes/util/baseNode.js View File

@ -0,0 +1,28 @@
/**
* Created by Alex on 3/19/2015.
*/
class BaseNode {
constructor(options, body, labelModule) {
this.body = body;
this.labelModule = labelModule;
this.setOptions(options);
this.top = undefined;
this.left = undefined;
this.height = undefined;
this.boundingBox = {top: 0, left: 0, right: 0, bottom: 0};
}
setOptions(options) {
this.options = options;
}
_distanceToBorder(angle) {
var borderWidth = 1;
return Math.min(
Math.abs(this.width / 2 / Math.cos(angle)),
Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth;
}
}
export default BaseNode;

+ 55
- 0
lib/network/modules/components/nodes/util/drawUtil.js View File

@ -0,0 +1,55 @@
/**
* Created by Alex on 3/19/2015.
*/
import BaseNode from '../util/baseNode'
class drawUtil extends BaseNode {
constructor(options, body, labelModule) {
super(options, body, labelModule);
}
_drawRawCircle(ctx, x, y, selected, hover, size) {
var borderWidth = this.options.borderWidth;
var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border;
ctx.lineWidth = (selected ? selectionLineWidth : borderWidth);
ctx.lineWidth *= this.networkScaleInv;
ctx.lineWidth = Math.min(this.width, ctx.lineWidth);
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
ctx.circle(x, y, size);
ctx.fill();
ctx.stroke();
}
_drawImageAtPosition(ctx) {
if (this.imageObj.width != 0) {
// draw the image
ctx.globalAlpha = 1.0;
ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height);
}
}
_drawImageLabel(ctx, x, y, selected) {
var yLabel;
var offset = 0;
if (this.height !== undefined) {
offset = this.height * 0.5;
var labelDimensions = this.labelModule.getTextSize(ctx);
if (labelDimensions.lineCount >= 1) {
offset += labelDimensions.height / 2;
offset += 3;
}
}
yLabel = y + offset;
this.labelModule.draw(ctx, x, yLabel, selected, 'hanging');
}
}
export default drawUtil;

+ 52
- 0
lib/network/modules/components/nodes/util/shapeUtil.js View File

@ -0,0 +1,52 @@
/**
* Created by Alex on 3/19/2015.
*/
import BaseNode from '../util/baseNode'
class ShapeUtil extends BaseNode {
constructor(options, body, labelModule) {
super(options, body, labelModule)
}
_resizeShape() {
if (this.width === undefined) {
var size = 2 * this.options.size;
this.width = size;
this.height = size;
}
}
_drawShape(ctx, shape, sizeMultiplier, x, y, selected, hover) {
this._resizeShape();
this.left = x - this.width / 2;
this.top = y - this.height / 2;
var borderWidth = this.options.borderWidth;
var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
ctx.strokeStyle = selected ? this.options.color.highlight.border : hover ? this.options.color.hover.border : this.options.color.border;
ctx.lineWidth = (selected ? selectionLineWidth : borderWidth);
ctx.lineWidth /= this.body.view.scale;
ctx.lineWidth = Math.min(this.width, ctx.lineWidth);
ctx.fillStyle = selected ? this.options.color.highlight.background : hover ? this.options.color.hover.background : this.options.color.background;
ctx[shape](x, y, this.options.size);
ctx.fill();
ctx.stroke();
this.boundingBox.top = y - this.options.size;
this.boundingBox.left = x - this.options.size;
this.boundingBox.right = x + this.options.size;
this.boundingBox.bottom = y + this.options.size;
if (this.options.label!== undefined) {
this.labelModule.draw(ctx, x, y + 0.5* this.height, selected, 'hanging');
this.boundingBox.left = Math.min(this.boundingBox.left, this.labelModule.size.left);
this.boundingBox.right = Math.max(this.boundingBox.right, this.labelModule.size.left + this.labelModule.size.width);
this.boundingBox.bottom = Math.max(this.boundingBox.bottom, this.boundingBox.bottom + this.labelModule.size.height);
}
}
}
export default ShapeUtil;

+ 3
- 2
lib/network/modules/components/physics/SpringSolver.js View File

@ -30,11 +30,12 @@ class SpringSolver {
// only calculate forces if nodes are in the same sector
if (this.body.nodes[edge.toId] !== undefined && this.body.nodes[edge.fromId] !== undefined) {
edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length;
if (edge.via != null) {
if (edge.edgeType.via !== undefined) {
var node1 = edge.to;
var node2 = edge.via;
var node2 = edge.edgeType.via;
var node3 = edge.from;
this._calculateSpringForce(node1, node2, 0.5 * edgeLength);
this._calculateSpringForce(node2, node3, 0.5 * edgeLength);
}

Loading…
Cancel
Save