Browse Source

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

flowchartTest
Alex de Mulder 10 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