Browse Source

edges are now accepting arrows back and forth using unified code

flowchartTest
Alex de Mulder 9 years ago
parent
commit
4a1ae62796
7 changed files with 733 additions and 552 deletions
  1. +354
    -263
      dist/vis.js
  2. +1
    -1
      docs/network.html
  3. +11
    -8
      examples/network/01_basic_usage.html
  4. +53
    -40
      lib/network/modules/EdgesHandler.js
  5. +30
    -20
      lib/network/modules/NodesHandler.js
  6. +281
    -220
      lib/network/modules/components/edges/EdgeMain.js
  7. +3
    -0
      lib/network/modules/components/unified/labels.js

+ 354
- 263
dist/vis.js View File

@ -5,7 +5,7 @@
* A dynamic, browser-based visualization library. * A dynamic, browser-based visualization library.
* *
* @version 4.0.0-SNAPSHOT * @version 4.0.0-SNAPSHOT
* @date 2015-03-16
* @date 2015-03-17
* *
* @license * @license
* Copyright (C) 2011-2014 Almende B.V, http://almende.com * Copyright (C) 2011-2014 Almende B.V, http://almende.com
@ -25628,22 +25628,33 @@ return /******/ (function(modules) { // webpackBootstrap
this.options = {}; this.options = {};
this.defaultOptions = { this.defaultOptions = {
customScalingFunction: function (min, max, total, value) {
if (max == min) {
return 0.5;
} else {
var scale = 1 / (max - min);
return Math.max(0, (value - min) * scale);
}
},
mass: 1, mass: 1,
radiusMin: 10,
radiusMax: 30,
//radiusMin: 10,
//radiusMax: 30,
radius: 10, radius: 10,
scaling: {
min: 10,
max: 40,
label: {
enabled: true,
min: 14,
max: 30,
maxVisible: 30,
drawThreshold: 3
},
customScalingFunction: function (min, max, total, value) {
if (max == min) {
return 0.5;
} else {
var scale = 1 / (max - min);
return Math.max(0, (value - min) * scale);
}
}
},
shape: "ellipse", shape: "ellipse",
image: undefined,
widthMin: 16, // px
widthMax: 64, // px
image: undefined, // --> URL
//widthMin: 16, // px
//widthMax: 64, // px
label: undefined, label: undefined,
labelStyle: { labelStyle: {
fontColor: "black", fontColor: "black",
@ -25651,13 +25662,7 @@ return /******/ (function(modules) { // webpackBootstrap
fontFace: "verdana", fontFace: "verdana",
fontFill: undefined, fontFill: undefined,
fontStrokeWidth: 0, // px fontStrokeWidth: 0, // px
fontStrokeColor: "#ffffff",
scaleFontWithValue: false,
fontSizeMin: 14,
fontSizeMax: 30,
fontSizeMaxVisible: 30,
fontDrawThreshold: 3
},
fontStrokeColor: "#ffffff" },
value: 1, value: 1,
color: { color: {
border: "#2B7CE9", border: "#2B7CE9",
@ -25695,7 +25700,6 @@ return /******/ (function(modules) { // webpackBootstrap
}, },
setData: { setData: {
/** /**
* Set a data set with nodes for the network * Set a data set with nodes for the network
* @param {Array | DataSet | DataView} nodes The data containing the nodes. * @param {Array | DataSet | DataView} nodes The data containing the nodes.
@ -25834,6 +25838,11 @@ return /******/ (function(modules) { // webpackBootstrap
})(); })();
module.exports = NodesHandler; module.exports = NodesHandler;
//scaleFontWithValue: false,
//fontSizeMin: 14,
//fontSizeMax: 30,
//fontSizeMaxVisible: 30,
//fontDrawThreshold: 3
/***/ }, /***/ },
/* 62 */ /* 62 */
@ -26868,59 +26877,72 @@ return /******/ (function(modules) { // webpackBootstrap
this.options = {}; this.options = {};
this.defaultOptions = { this.defaultOptions = {
customScalingFunction: function (min, max, total, value) {
if (max == min) {
return 0.5;
} else {
var scale = 1 / (max - min);
return Math.max(0, (value - min) * scale);
}
arrows: {
to: { enabled: false, scaleFactor: 1 }, // boolean / {arrowScaleFactor:1} / {enabled: false, arrowScaleFactor:1}
middle: { enabled: false, scaleFactor: 1 },
from: { enabled: false, scaleFactor: 1 }
}, },
style: {
widthMin: 1, //
widthMax: 15, //
width: 1,
widthSelectionMultiplier: 2,
hoverWidth: 1.5,
type: "line",
color: {
color: "#848484",
highlight: "#848484",
hover: "#848484",
inherit: {
enabled: true,
source: "from", // from / true
useGradients: false // release in 4.0
}
color: {
color: "#848484",
highlight: "#848484",
hover: "#848484",
inherit: {
enabled: true,
source: "from", // from / true
useGradients: false // release in 4.0
}, },
opacity: 1 opacity: 1
}, },
value: 1,
label: undefined,
labelStyle: {
fontColor: "#343434",
fontSize: 14, // px
fontFace: "arial",
fontFill: "white",
fontStrokeWidth: 0, // px
fontStrokeColor: "white",
labelAlignment: "horizontal"
},
length: undefined,
arrowScaleFactor: 1,
dash: {
dashes: {
enabled: false,
preset: "dotted",
length: 10, length: 10,
gap: 5, gap: 5,
altLength: undefined altLength: undefined
}, },
font: {
color: "#343434",
size: 14, // px
face: "arial",
background: "none",
stroke: 1, // px
strokeColor: "white",
align: "horizontal"
},
hidden: false,
hoverWidth: 1.5,
label: undefined,
length: undefined,
physics: true,
scaling: {
min: 1,
max: 15,
label: {
enabled: true,
min: 14,
max: 30,
maxVisible: 30,
drawThreshold: 3
},
customScalingFunction: function (min, max, total, value) {
if (max == min) {
return 0.5;
} else {
var scale = 1 / (max - min);
return Math.max(0, (value - min) * scale);
}
}
},
smooth: { smooth: {
enabled: true, enabled: true,
dynamic: true, dynamic: true,
type: "continuous", type: "continuous",
roundness: 0.5 roundness: 0.5
}, },
hidden: false,
physics: true
title: undefined,
width: 1,
widthSelectionMultiplier: 2,
value: 1
}; };
util.extend(this.options, this.defaultOptions); util.extend(this.options, this.defaultOptions);
@ -27128,8 +27150,6 @@ return /******/ (function(modules) { // webpackBootstrap
throw "No body provided"; throw "No body provided";
} }
this.initializing = true;
this.options = util.bridgeObject(globalOptions); this.options = util.bridgeObject(globalOptions);
this.body = body; this.body = body;
@ -27138,7 +27158,7 @@ return /******/ (function(modules) { // webpackBootstrap
this.fromId = undefined; this.fromId = undefined;
this.toId = undefined; this.toId = undefined;
this.title = undefined; this.title = undefined;
this.widthSelected = this.options.style.width * this.options.style.widthSelectionMultiplier;
this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
this.value = undefined; this.value = undefined;
this.selected = false; this.selected = false;
this.hover = false; this.hover = false;
@ -27160,13 +27180,11 @@ return /******/ (function(modules) { // webpackBootstrap
this.connected = false; this.connected = false;
this.setOptions(options);
this.setOptions(options, true);
this.controlNodesEnabled = false; this.controlNodesEnabled = false;
this.controlNodes = { from: undefined, to: undefined, positions: {} }; this.controlNodes = { from: undefined, to: undefined, positions: {} };
this.connectedNode = undefined; this.connectedNode = undefined;
this.initializing = false;
} }
_prototypeProperties(Edge, null, { _prototypeProperties(Edge, null, {
@ -27179,17 +27197,27 @@ return /******/ (function(modules) { // webpackBootstrap
* @param {Object} constants and object with default, global options * @param {Object} constants and object with default, global options
*/ */
value: function setOptions(options) { value: function setOptions(options) {
var doNotEmit = arguments[1] === undefined ? false : arguments[1];
if (!options) { if (!options) {
return; return;
} }
this.colorDirty = true; this.colorDirty = true;
var fields = ["style", "label", "labelStyle", "arrowScaleFactor", "dash", "opacity", "customScalingFunction", "value", "hidden", "physics"];
var fields = ["font", "hidden", "hoverWidth", "label", "length", "line", "opacity", "physics", "scaling", "value", "width", "widthMin", "widthMax", "widthSelectionMultiplier"];
util.selectiveDeepExtend(fields, this.options, options); util.selectiveDeepExtend(fields, this.options, options);
util.mergeOptions(this.options, options, "inheritColor");
util.mergeOptions(this.options, options, "smooth"); util.mergeOptions(this.options, options, "smooth");
util.mergeOptions(this.options, options, "dashes");
if (options.arrows !== undefined) {
util.mergeOptions(this.options.arrows, options.arrows, "to");
util.mergeOptions(this.options.arrows, options.arrows, "middle");
util.mergeOptions(this.options.arrows, options.arrows, "from");
}
if (options.id !== undefined) {
this.id = options.id;
}
if (options.from !== undefined) { if (options.from !== undefined) {
this.fromId = options.from; this.fromId = options.from;
} }
@ -27197,9 +27225,6 @@ return /******/ (function(modules) { // webpackBootstrap
this.toId = options.to; this.toId = options.to;
} }
if (options.id !== undefined) {
this.id = options.id;
}
if (options.label !== undefined) { if (options.label !== undefined) {
this.label = options.label; this.label = options.label;
this.labelDirty = true; this.labelDirty = true;
@ -27211,55 +27236,37 @@ return /******/ (function(modules) { // webpackBootstrap
if (options.value !== undefined) { if (options.value !== undefined) {
this.value = options.value; this.value = options.value;
} }
if (options.length !== undefined) {
this.physics.springLength = options.length;
}
if (options.style !== undefined) {
if (options.style.color !== undefined) {
this.options.style.color.inherit.enabled = false;
if (util.isString(options.style.color)) {
this.options.style.color.color = options.style.color;
this.options.style.color.highlight = options.style.color;
} else {
if (options.style.color.color !== undefined) {
this.options.style.color.color = options.style.color.color;
}
if (options.style.color.highlight !== undefined) {
this.options.style.color.highlight = options.style.color.highlight;
}
if (options.style.color.hover !== undefined) {
this.options.style.color.hover = options.style.color.hover;
}
if (options.color !== undefined) {
if (util.isString(options.color)) {
this.options.color.color = options.color;
this.options.color.highlight = options.color;
} else {
if (options.color.color !== undefined) {
this.options.color.color = options.color.color;
}
if (options.color.highlight !== undefined) {
this.options.color.highlight = options.color.highlight;
}
if (options.color.hover !== undefined) {
this.options.color.hover = options.color.hover;
} }
} }
// inherit colors
if (options.color.inherit === undefined) {
this.options.color.inherit.enabled = false;
} else {
util.mergeOptions(this.options.color, options.color, "inherit");
}
} }
// A node is connected when it has a from and to node that both exist in the network.body.nodes. // A node is connected when it has a from and to node that both exist in the network.body.nodes.
this.connect(); this.connect();
this.widthSelected = this.options.style.width * this.options.style.widthSelectionMultiplier;
this.setupSmoothEdges(this.initializing === false);
this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
// set draw method based on style
switch (this.options.style.type) {
case "line":
this.draw = this._drawLine;
break;
case "arrow":
this.draw = this._drawArrow;
break;
case "arrow-center":
this.draw = this._drawArrowCenter;
break;
case "dash-line":
this.draw = this._drawDashLine;
break;
default:
this.draw = this._drawLine;
break;
}
this.setupSmoothEdges(doNotEmit);
}, },
writable: true, writable: true,
configurable: true configurable: true
@ -27274,7 +27281,7 @@ return /******/ (function(modules) { // webpackBootstrap
* @private * @private
*/ */
value: function setupSmoothEdges() { value: function setupSmoothEdges() {
var emitChange = arguments[0] === undefined ? true : arguments[0];
var doNotEmit = arguments[0] === undefined ? false : arguments[0];
var changedData = false; var changedData = false;
if (this.options.smooth.enabled == true && this.options.smooth.dynamic == true) { if (this.options.smooth.enabled == true && this.options.smooth.dynamic == true) {
if (this.via === undefined) { if (this.via === undefined) {
@ -27302,7 +27309,7 @@ return /******/ (function(modules) { // webpackBootstrap
} }
// node has been added or deleted // node has been added or deleted
if (changedData === true && emitChange === true) {
if (changedData === true && doNotEmit === false) {
this.body.emitter.emit("_dataChanged"); this.body.emitter.emit("_dataChanged");
} }
}, },
@ -27411,9 +27418,9 @@ return /******/ (function(modules) { // webpackBootstrap
value: function setValueRange(min, max, total) { value: function setValueRange(min, max, total) {
if (!this.widthFixed && this.value !== undefined) { if (!this.widthFixed && this.value !== undefined) {
var scale = this.options.customScalingFunction(min, max, total, this.value); var scale = this.options.customScalingFunction(min, max, total, this.value);
var widthDiff = this.options.style.widthMax - this.options.style.widthMin;
this.options.style.width = this.options.style.widthMin + scale * widthDiff;
this.widthSelected = this.options.style.width * this.options.style.widthSelectionMultiplier;
var widthDiff = this.options.widthMax - this.options.widthMin;
this.options.width = this.options.widthMin + scale * widthDiff;
this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
} }
}, },
writable: true, writable: true,
@ -27429,7 +27436,68 @@ return /******/ (function(modules) { // webpackBootstrap
* @param {CanvasRenderingContext2D} ctx * @param {CanvasRenderingContext2D} ctx
*/ */
value: function draw(ctx) { value: function draw(ctx) {
throw "Method draw not initialized in edge";
var via = this.drawLine(ctx);
this.drawArrows(ctx);
this.drawLabel(ctx, via);
},
writable: true,
configurable: true
},
drawLine: {
value: function drawLine(ctx) {
if (this.options.dashes.enabled === false) {
return this._drawLine(ctx);
} else {
return this._drawDashLine(ctx);
}
},
writable: true,
configurable: true
},
drawArrows: {
value: function drawArrows(ctx) {
if (this.options.arrows.from.enabled === true) {
this._drawArrowHead(ctx, "from");
}
//if (this.options.arrows.middle.enabled === true) {this._drawArrowCenter(ctx, via);}
if (this.options.arrows.to.enabled === true) {
this._drawArrowHead(ctx, "to");
}
},
writable: true,
configurable: true
},
drawLabel: {
value: function drawLabel(ctx, via) {
if (this.label !== undefined) {
// set style
var node1 = this.from;
var node2 = this.to;
if (node1.id != node2.id) {
var point;
if (this.options.smooth.enabled == true && via != undefined) {
var midpointX = 0.5 * (0.5 * (this.from.x + via.x) + 0.5 * (this.to.x + via.x));
var midpointY = 0.5 * (0.5 * (this.from.y + via.y) + 0.5 * (this.to.y + via.y));
point = { x: midpointX, y: midpointY };
} else {
point = this._pointOnLine(0.5);
}
this._label(ctx, this.label, point.x, point.y);
} else {
var x, y;
var radius = 25;
if (node1.width > node1.height) {
x = node1.x + node1.width * 0.5;
y = node1.y - radius;
} else {
x = node1.x + radius;
y = node1.y - node1.height * 0.5;
}
point = this._pointOnCircle(x, y, radius, 0.125);
this._label(ctx, this.label, point.x, point.y);
}
}
}, },
writable: true, writable: true,
configurable: true configurable: true
@ -27464,7 +27532,7 @@ return /******/ (function(modules) { // webpackBootstrap
}, },
_getColor: { _getColor: {
value: function _getColor(ctx) { value: function _getColor(ctx) {
var colorObj = this.options.style.color;
var colorObj = this.options.color;
if (colorObj.inherit.enabled === true) { if (colorObj.inherit.enabled === true) {
if (colorObj.inherit.useGradients == true) { if (colorObj.inherit.useGradients == true) {
@ -27474,8 +27542,8 @@ return /******/ (function(modules) { // webpackBootstrap
toColor = this.to.options.color.highlight.border; toColor = this.to.options.color.highlight.border;
if (this.from.selected == false && this.to.selected == false) { if (this.from.selected == false && this.to.selected == false) {
fromColor = util.overrideOpacity(this.from.options.color.border, this.options.style.opacity);
toColor = util.overrideOpacity(this.to.options.color.border, this.options.style.opacity);
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) { } else if (this.from.selected == true && this.to.selected == false) {
toColor = this.to.options.color.border; toColor = this.to.options.color.border;
} else if (this.from.selected == false && this.to.selected == true) { } else if (this.from.selected == false && this.to.selected == true) {
@ -27492,12 +27560,12 @@ return /******/ (function(modules) { // webpackBootstrap
if (colorObj.inherit.source == "to") { if (colorObj.inherit.source == "to") {
colorObj.highlight = this.to.options.color.highlight.border; colorObj.highlight = this.to.options.color.highlight.border;
colorObj.hover = this.to.options.color.hover.border; colorObj.hover = this.to.options.color.hover.border;
colorObj.color = util.overrideOpacity(this.to.options.color.border, this.options.style.opacity);
colorObj.color = util.overrideOpacity(this.to.options.color.border, this.options.color.opacity);
} else { } else {
// (this.options.style.color.inherit.source == "from") {
// (this.options.color.inherit.source == "from") {
colorObj.highlight = this.from.options.color.highlight.border; colorObj.highlight = this.from.options.color.highlight.border;
colorObj.hover = this.from.options.color.hover.border; colorObj.hover = this.from.options.color.hover.border;
colorObj.color = util.overrideOpacity(this.from.options.color.border, this.options.style.opacity);
colorObj.color = util.overrideOpacity(this.from.options.color.border, this.options.color.opacity);
} }
} }
} }
@ -27531,41 +27599,29 @@ return /******/ (function(modules) { // webpackBootstrap
// set style // set style
ctx.strokeStyle = this._getColor(ctx); ctx.strokeStyle = this._getColor(ctx);
ctx.lineWidth = this._getLineWidth(); ctx.lineWidth = this._getLineWidth();
var via;
if (this.from != this.to) { if (this.from != this.to) {
// draw line // draw line
var via = this._line(ctx);
// draw label
var point;
if (this.label) {
if (this.options.smooth.enabled == true && via != undefined) {
var midpointX = 0.5 * (0.5 * (this.from.x + via.x) + 0.5 * (this.to.x + via.x));
var midpointY = 0.5 * (0.5 * (this.from.y + via.y) + 0.5 * (this.to.y + via.y));
point = { x: midpointX, y: midpointY };
} else {
point = this._pointOnLine(0.5);
}
this._label(ctx, this.label, point.x, point.y);
}
via = this._line(ctx);
} else { } else {
var x, y; var x, y;
var radius = this.physics.springLength / 4;
var radius = 25;
var node = this.from; var node = this.from;
if (!node.width) { if (!node.width) {
node.resize(ctx); node.resize(ctx);
} }
if (node.width > node.height) { if (node.width > node.height) {
x = node.x + node.width / 2;
x = node.x + node.width * 0.5;
y = node.y - radius; y = node.y - radius;
} else { } else {
x = node.x + radius; x = node.x + radius;
y = node.y - node.height / 2;
y = node.y - node.height * 0.5;
} }
this._circle(ctx, x, y, radius); this._circle(ctx, x, y, radius);
point = this._pointOnCircle(x, y, radius, 0.5);
this._label(ctx, this.label, point.x, point.y);
} }
return via;
}, },
writable: true, writable: true,
configurable: true configurable: true
@ -27581,12 +27637,12 @@ return /******/ (function(modules) { // webpackBootstrap
*/ */
value: function _getLineWidth() { value: function _getLineWidth() {
if (this.selected == true) { if (this.selected == true) {
return Math.max(Math.min(this.widthSelected, this.options.style.widthMax), 0.3 * this.networkScaleInv);
return Math.max(Math.min(this.widthSelected, this.options.widthMax), 0.3 * this.networkScaleInv);
} else { } else {
if (this.hover == true) { if (this.hover == true) {
return Math.max(Math.min(this.options.style.hoverWidth, this.options.style.widthMax), 0.3 * this.networkScaleInv);
return Math.max(Math.min(this.options.hoverWidth, this.options.widthMax), 0.3 * this.networkScaleInv);
} else { } else {
return Math.max(this.options.style.width, 0.3 * this.networkScaleInv);
return Math.max(this.options.width, 0.3 * this.networkScaleInv);
} }
} }
}, },
@ -27835,23 +27891,23 @@ return /******/ (function(modules) { // webpackBootstrap
*/ */
value: function _label(ctx, text, x, y) { value: function _label(ctx, text, x, y) {
if (text) { if (text) {
ctx.font = (this.from.selected || this.to.selected ? "bold " : "") + this.options.labelStyle.fontSize + "px " + this.options.labelStyle.fontFace;
ctx.font = (this.from.selected || this.to.selected ? "bold " : "") + this.options.font.size + "px " + this.options.font.face;
var yLine; var yLine;
if (this.labelDirty == true) { if (this.labelDirty == true) {
var lines = String(text).split("\n"); var lines = String(text).split("\n");
var lineCount = lines.length; var lineCount = lines.length;
var fontSize = Number(this.options.labelStyle.fontSize);
yLine = y + (1 - lineCount) / 2 * fontSize;
var fontSize = Number(this.options.font.size);
yLine = y + (1 - lineCount) * 0.5 * fontSize;
var width = ctx.measureText(lines[0]).width; var width = ctx.measureText(lines[0]).width;
for (var i = 1; i < lineCount; i++) { for (var i = 1; i < lineCount; i++) {
var lineWidth = ctx.measureText(lines[i]).width; var lineWidth = ctx.measureText(lines[i]).width;
width = lineWidth > width ? lineWidth : width; width = lineWidth > width ? lineWidth : width;
} }
var height = this.options.labelStyle.fontSize * lineCount;
var left = x - width / 2;
var top = y - height / 2;
var height = this.options.font.size * lineCount;
var left = x - width * 0.5;
var top = y - height * 0.5;
// cache // cache
this.labelDimensions = { top: top, left: left, width: width, height: height, yLine: yLine }; this.labelDimensions = { top: top, left: left, width: width, height: height, yLine: yLine };
@ -27861,7 +27917,7 @@ return /******/ (function(modules) { // webpackBootstrap
ctx.save(); ctx.save();
if (this.options.labelStyle.labelAlignment != "horizontal") {
if (this.options.font.align != "horizontal") {
ctx.translate(x, yLine); ctx.translate(x, yLine);
this._rotateForLabelAlignment(ctx); this._rotateForLabelAlignment(ctx);
x = 0; x = 0;
@ -27911,16 +27967,16 @@ return /******/ (function(modules) { // webpackBootstrap
* @private * @private
*/ */
value: function _drawLabelRect(ctx) { value: function _drawLabelRect(ctx) {
if (this.options.labelStyle.fontFill !== undefined && this.options.labelStyle.fontFill !== undefined && this.options.labelStyle.fontFill !== "none") {
ctx.fillStyle = this.options.labelStyle.fontFill;
if (this.options.font.background !== undefined && this.options.font.background !== "none") {
ctx.fillStyle = this.options.font.background;
var lineMargin = 2; var lineMargin = 2;
if (this.options.labelStyle.labelAlignment == "line-center") {
if (this.options.font.align == "middle") {
ctx.fillRect(-this.labelDimensions.width * 0.5, -this.labelDimensions.height * 0.5, this.labelDimensions.width, this.labelDimensions.height); ctx.fillRect(-this.labelDimensions.width * 0.5, -this.labelDimensions.height * 0.5, this.labelDimensions.width, this.labelDimensions.height);
} else if (this.options.labelStyle.labelAlignment == "line-above") {
} else if (this.options.font.align == "top") {
ctx.fillRect(-this.labelDimensions.width * 0.5, -(this.labelDimensions.height + lineMargin), this.labelDimensions.width, this.labelDimensions.height); ctx.fillRect(-this.labelDimensions.width * 0.5, -(this.labelDimensions.height + lineMargin), this.labelDimensions.width, this.labelDimensions.height);
} else if (this.options.labelStyle.labelAlignment == "line-below") {
} else if (this.options.font.align == "bottom") {
ctx.fillRect(-this.labelDimensions.width * 0.5, lineMargin, this.labelDimensions.width, this.labelDimensions.height); ctx.fillRect(-this.labelDimensions.width * 0.5, lineMargin, this.labelDimensions.width, this.labelDimensions.height);
} else { } else {
ctx.fillRect(this.labelDimensions.left, this.labelDimensions.top, this.labelDimensions.width, this.labelDimensions.height); ctx.fillRect(this.labelDimensions.left, this.labelDimensions.top, this.labelDimensions.width, this.labelDimensions.height);
@ -27945,16 +28001,16 @@ return /******/ (function(modules) { // webpackBootstrap
*/ */
value: function _drawLabelText(ctx, x, yLine, lines, lineCount, fontSize) { value: function _drawLabelText(ctx, x, yLine, lines, lineCount, fontSize) {
// draw text // draw text
ctx.fillStyle = this.options.labelStyle.fontColor || "black";
ctx.fillStyle = this.options.font.color || "black";
ctx.textAlign = "center"; ctx.textAlign = "center";
// check for label alignment // check for label alignment
if (this.options.labelStyle.labelAlignment != "horizontal") {
if (this.options.font.align != "horizontal") {
var lineMargin = 2; var lineMargin = 2;
if (this.options.labelStyle.labelAlignment == "line-above") {
if (this.options.font.align == "top") {
ctx.textBaseline = "alphabetic"; ctx.textBaseline = "alphabetic";
yLine -= 2 * lineMargin; // distance from edge, required because we use alphabetic. Alphabetic has less difference between browsers yLine -= 2 * lineMargin; // distance from edge, required because we use alphabetic. Alphabetic has less difference between browsers
} else if (this.options.labelStyle.labelAlignment == "line-below") {
} else if (this.options.font.align == "bottom") {
ctx.textBaseline = "hanging"; ctx.textBaseline = "hanging";
yLine += 2 * lineMargin; // distance from edge, required because we use hanging. Hanging has less difference between browsers yLine += 2 * lineMargin; // distance from edge, required because we use hanging. Hanging has less difference between browsers
} else { } else {
@ -27965,13 +28021,13 @@ return /******/ (function(modules) { // webpackBootstrap
} }
// check for strokeWidth // check for strokeWidth
if (this.options.labelStyle.fontStrokeWidth > 0) {
ctx.lineWidth = this.options.labelStyle.fontStrokeWidth;
ctx.strokeStyle = this.options.labelStyle.fontStrokeColor;
if (this.options.font.stroke > 0) {
ctx.lineWidth = this.options.font.stroke;
ctx.strokeStyle = this.options.font.strokeColor;
ctx.lineJoin = "round"; ctx.lineJoin = "round";
} }
for (var i = 0; i < lineCount; i++) { for (var i = 0; i < lineCount; i++) {
if (this.options.labelStyle.fontStrokeWidth > 0) {
if (this.options.font.stroke > 0) {
ctx.strokeText(lines[i], x, yLine); ctx.strokeText(lines[i], x, yLine);
} }
ctx.fillText(lines[i], x, yLine); ctx.fillText(lines[i], x, yLine);
@ -27985,7 +28041,7 @@ return /******/ (function(modules) { // webpackBootstrap
/** /**
* Redraw a edge as a dashed line
* Redraw a edge as a dashes line
* Draw this edge in the given canvas * Draw this edge in the given canvas
* @author David Jordan * @author David Jordan
* @date 2012-08-08 * @date 2012-08-08
@ -28004,8 +28060,8 @@ return /******/ (function(modules) { // webpackBootstrap
ctx.save(); ctx.save();
// configure the dash pattern // configure the dash pattern
var pattern = [0]; var pattern = [0];
if (this.options.dash.length !== undefined && this.options.dash.gap !== undefined) {
pattern = [this.options.dash.length, this.options.dash.gap];
if (this.options.dashes.length !== undefined && this.options.dashes.gap !== undefined) {
pattern = [this.options.dashes.length, this.options.dashes.gap];
} else { } else {
pattern = [5, 5]; pattern = [5, 5];
} }
@ -28023,15 +28079,15 @@ return /******/ (function(modules) { // webpackBootstrap
ctx.restore(); ctx.restore();
} else { } else {
// unsupporting smooth lines // unsupporting smooth lines
// draw dashed line
// draw dashes line
ctx.beginPath(); ctx.beginPath();
ctx.lineCap = "round"; ctx.lineCap = "round";
if (this.options.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
if (this.options.dashes.altLength !== undefined) //If an alt dash value has been set add to the array this value
{ {
ctx.dashedLine(this.from.x, this.from.y, this.to.x, this.to.y, [this.options.dash.length, this.options.dash.gap, this.options.dash.altLength, this.options.dash.gap]);
} else if (this.options.dash.length !== undefined && this.options.dash.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, 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.dashedLine(this.from.x, this.from.y, this.to.x, this.to.y, [this.options.dash.length, this.options.dash.gap]);
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 } else //If all else fails draw a line
{ {
ctx.moveTo(this.from.x, this.from.y); ctx.moveTo(this.from.x, this.from.y);
@ -28040,18 +28096,7 @@ return /******/ (function(modules) { // webpackBootstrap
ctx.stroke(); ctx.stroke();
} }
// draw label
if (this.label) {
var point;
if (this.options.smooth.enabled == true && via != undefined) {
var midpointX = 0.5 * (0.5 * (this.from.x + via.x) + 0.5 * (this.to.x + via.x));
var midpointY = 0.5 * (0.5 * (this.from.y + via.y) + 0.5 * (this.to.y + via.y));
point = { x: midpointX, y: midpointY };
} else {
point = this._pointOnLine(0.5);
}
this._label(ctx, this.label, point.x, point.y);
}
return via;
}, },
writable: true, writable: true,
configurable: true configurable: true
@ -28087,7 +28132,7 @@ return /******/ (function(modules) { // webpackBootstrap
* @private * @private
*/ */
value: function _pointOnCircle(x, y, radius, percentage) { value: function _pointOnCircle(x, y, radius, percentage) {
var angle = (percentage - 3 / 8) * 2 * Math.PI;
var angle = percentage * 2 * Math.PI;
return { return {
x: x + radius * Math.cos(angle), x: x + radius * Math.cos(angle),
y: y - radius * Math.sin(angle) y: y - radius * Math.sin(angle)
@ -28118,7 +28163,7 @@ return /******/ (function(modules) { // webpackBootstrap
var via = this._line(ctx); var via = this._line(ctx);
var angle = Math.atan2(this.to.y - this.from.y, this.to.x - this.from.x); var angle = Math.atan2(this.to.y - this.from.y, this.to.x - this.from.x);
var length = (10 + 5 * this.options.style.width) * this.options.arrowScaleFactor;
var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
// draw an arrow halfway the line // draw an arrow halfway the line
if (this.options.smooth.enabled == true && via != undefined) { if (this.options.smooth.enabled == true && via != undefined) {
var midpointX = 0.5 * (0.5 * (this.from.x + via.x) + 0.5 * (this.to.x + via.x)); var midpointX = 0.5 * (0.5 * (this.from.x + via.x) + 0.5 * (this.to.x + via.x));
@ -28139,7 +28184,7 @@ return /******/ (function(modules) { // webpackBootstrap
} else { } else {
// draw circle // draw circle
var x, y; var x, y;
var radius = 0.25 * Math.max(100, this.physics.springLength);
var radius = 25;
var node = this.from; var node = this.from;
if (!node.width) { if (!node.width) {
node.resize(ctx); node.resize(ctx);
@ -28155,7 +28200,7 @@ return /******/ (function(modules) { // webpackBootstrap
// draw all arrows // draw all arrows
var angle = 0.2 * Math.PI; var angle = 0.2 * Math.PI;
var length = (10 + 5 * this.options.style.width) * this.options.arrowScaleFactor;
var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
point = this._pointOnCircle(x, y, radius, 0.5); point = this._pointOnCircle(x, y, radius, 0.5);
ctx.arrow(point.x, point.y, angle, length); ctx.arrow(point.x, point.y, angle, length);
ctx.fill(); ctx.fill();
@ -28183,26 +28228,27 @@ return /******/ (function(modules) { // webpackBootstrap
writable: true, writable: true,
configurable: true configurable: true
}, },
_findBorderPosition: {
_findBorderPositionBezier: {
/** /**
* This function uses binary search to look for the point where the bezier curve crosses the border of the node. * This function uses binary search to look for the point where the bezier curve crosses the border of the node.
* *
* @param from * @param from
* @param ctx
* @returns {*} * @returns {*}
* @private * @private
*/ */
value: function _findBorderPosition(from, ctx) {
value: function _findBorderPositionBezier(nearNode, ctx) {
var maxIterations = 10; var maxIterations = 10;
var iteration = 0; var iteration = 0;
var low = 0; var low = 0;
var high = 1; var high = 1;
var pos, angle, distanceToBorder, distanceToNodes, difference;
var pos, angle, distanceToBorder, distanceToPoint, difference;
var threshold = 0.2; var threshold = 0.2;
var node = this.to; var node = this.to;
if (from == true) {
var from = false;
if (nearNode.id === this.from.id) {
node = this.from; node = this.from;
from = true;
} }
while (low <= high && iteration < maxIterations) { while (low <= high && iteration < maxIterations) {
@ -28211,8 +28257,8 @@ return /******/ (function(modules) { // webpackBootstrap
pos = this._pointOnBezier(middle); pos = this._pointOnBezier(middle);
angle = Math.atan2(node.y - pos.y, node.x - pos.x); angle = Math.atan2(node.y - pos.y, node.x - pos.x);
distanceToBorder = node.distanceToBorder(ctx, angle); distanceToBorder = node.distanceToBorder(ctx, angle);
distanceToNodes = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2));
difference = distanceToBorder - distanceToNodes;
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) { if (Math.abs(difference) < threshold) {
break; // found break; // found
} else if (difference < 0) { } else if (difference < 0) {
@ -28239,106 +28285,151 @@ return /******/ (function(modules) { // webpackBootstrap
writable: true, writable: true,
configurable: true configurable: true
}, },
_drawArrow: {
_findBorderPositionCircle: {
/**
* This function uses binary search to look for the point where the bezier curve crosses the border of the node.
*
* @param from
* @param ctx
* @returns {*}
* @private
*/
value: function _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;
},
writable: true,
configurable: true
},
_drawArrowHead: {
/** /**
* Redraw a edge as a line with an arrow
* Draw this edge in the given canvas
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
* @param {CanvasRenderingContext2D} ctx
*
* @param ctx
* @param node1
* @param node2
* @param guideOffset
* @private * @private
*/ */
value: function _drawArrow(ctx) {
value: function _drawArrowHead(ctx, position) {
// set style // set style
ctx.strokeStyle = this._getColor(ctx); ctx.strokeStyle = this._getColor(ctx);
ctx.fillStyle = ctx.strokeStyle; ctx.fillStyle = ctx.strokeStyle;
ctx.lineWidth = this._getLineWidth(); ctx.lineWidth = this._getLineWidth();
// set vars // set vars
var angle, length, arrowPos;
var angle;
var length;
var arrowPos;
var node1;
var node2;
var guideOffset;
var scaleFactor;
if (position == "from") {
node1 = this.from;
node2 = this.to;
guideOffset = 0.1;
scaleFactor = this.options.arrows.from.scaleFactor;
} else if (position == "to") {
node1 = this.to;
node2 = this.from;
guideOffset = -0.1;
scaleFactor = this.options.arrows.to.scaleFactor;
}
// if not connected to itself // if not connected to itself
if (this.from != this.to) {
// draw line
this._line(ctx);
if (node1 != node2) {
// draw arrow head // draw arrow head
if (this.options.smooth.enabled == true) { if (this.options.smooth.enabled == true) {
var via = this._getViaCoordinates();
arrowPos = this._findBorderPosition(false, ctx);
var guidePos = this._pointOnBezier(Math.max(0, arrowPos.t - 0.1));
arrowPos = this._findBorderPositionBezier(node1, ctx);
var guidePos = this._pointOnBezier(Math.max(0, arrowPos.t + guideOffset));
angle = Math.atan2(arrowPos.y - guidePos.y, arrowPos.x - guidePos.x); angle = Math.atan2(arrowPos.y - guidePos.y, arrowPos.x - guidePos.x);
} else { } else {
angle = Math.atan2(this.to.y - this.from.y, this.to.x - this.from.x);
var dx = this.to.x - this.from.x;
var dy = this.to.y - this.from.y;
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 edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
var toBorderDist = this.to.distanceToBorder(ctx, angle); var toBorderDist = this.to.distanceToBorder(ctx, angle);
var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength; var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
arrowPos = {}; arrowPos = {};
arrowPos.x = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
arrowPos.y = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
arrowPos.x = (1 - toBorderPoint) * node2.x + toBorderPoint * node1.x;
arrowPos.y = (1 - toBorderPoint) * node2.y + toBorderPoint * node1.y;
} }
// draw arrow at the end of the line // draw arrow at the end of the line
length = (10 + 5 * this.options.style.width) * this.options.arrowScaleFactor;
length = (10 + 5 * this.options.width) * scaleFactor;
ctx.arrow(arrowPos.x, arrowPos.y, angle, length); ctx.arrow(arrowPos.x, arrowPos.y, angle, length);
ctx.fill(); ctx.fill();
ctx.stroke(); ctx.stroke();
// draw label
if (this.label) {
var point;
if (this.options.smooth.enabled == true && via != undefined) {
point = this._pointOnBezier(0.5);
} else {
point = this._pointOnLine(0.5);
}
this._label(ctx, this.label, point.x, point.y);
}
} else { } else {
// draw circle // draw circle
var node = this.from;
var x, y, arrow;
var radius = 0.25 * Math.max(100, this.physics.springLength);
if (!node.width) {
node.resize(ctx);
var angle, point;
var x, y;
var radius = 25;
if (!node1.width) {
node1.resize(ctx);
} }
if (node.width > node.height) {
x = node.x + node.width * 0.5;
y = node.y - radius;
arrow = {
x: x,
y: node.y,
angle: 0.9 * Math.PI
};
// get circle coordinates
if (node1.width > node1.height) {
x = node1.x + node1.width * 0.5;
y = node1.y - radius;
} else { } else {
x = node.x + radius;
y = node.y - node.height * 0.5;
arrow = {
x: node.x,
y: y,
angle: 0.6 * Math.PI
};
x = node1.x + radius;
y = node1.y - node1.height * 0.5;
} }
ctx.beginPath();
// TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.stroke();
if (position == "from") {
point = this._findBorderPositionCircle(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 {
point = this._findBorderPositionCircle(x, y, radius, node1, 0.6, 0.8, 1, ctx);
angle = point.t * -2 * Math.PI + 1.5 * Math.PI - 1.1 * Math.PI;
}
// draw all arrows // draw all arrows
var length = (10 + 5 * this.options.style.width) * this.options.arrowScaleFactor;
ctx.arrow(arrow.x, arrow.y, arrow.angle, length);
var length = (10 + 5 * this.options.width) * scaleFactor;
ctx.arrow(point.x, point.y, angle, length);
ctx.fill(); ctx.fill();
ctx.stroke(); ctx.stroke();
// draw label
if (this.label) {
point = this._pointOnCircle(x, y, radius, 0.5);
this._label(ctx, this.label, point.x, point.y);
}
} }
}, },
writable: true, writable: true,
@ -28393,7 +28484,7 @@ return /******/ (function(modules) { // webpackBootstrap
} }
} else { } else {
var x, y, dx, dy; var x, y, dx, dy;
var radius = 0.25 * this.physics.springLength;
var radius = 25;
var node = this.from; var node = this.from;
if (node.width > node.height) { if (node.width > node.height) {
x = node.x + 0.5 * node.width; x = node.x + 0.5 * node.width;
@ -28642,7 +28733,7 @@ return /******/ (function(modules) { // webpackBootstrap
// draw arrow head // draw arrow head
var controlnodeFromPos; var controlnodeFromPos;
if (this.options.smooth.enabled == true) { if (this.options.smooth.enabled == true) {
controlnodeFromPos = this._findBorderPosition(true, ctx);
controlnodeFromPos = this._findBorderPositionBezier(true, ctx);
} else { } else {
var angle = Math.atan2(this.to.y - this.from.y, this.to.x - this.from.x); var angle = Math.atan2(this.to.y - this.from.y, this.to.x - this.from.x);
var dx = this.to.x - this.from.x; var dx = this.to.x - this.from.x;
@ -28674,7 +28765,7 @@ return /******/ (function(modules) { // webpackBootstrap
// draw arrow head // draw arrow head
var controlnodeToPos; var controlnodeToPos;
if (this.options.smooth.enabled == true) { if (this.options.smooth.enabled == true) {
controlnodeToPos = this._findBorderPosition(false, ctx);
controlnodeToPos = this._findBorderPositionBezier(false, ctx);
} else { } else {
var angle = Math.atan2(this.to.y - this.from.y, this.to.x - this.from.x); var angle = Math.atan2(this.to.y - this.from.y, this.to.x - this.from.x);
var dx = this.to.x - this.from.x; var dx = this.to.x - this.from.x;

+ 1
- 1
docs/network.html View File

@ -1281,7 +1281,7 @@ var options = {
<td class="greenField">labelAlignment</td> <td class="greenField">labelAlignment</td>
<td>String</td> <td>String</td>
<td>horizontal</td> <td>horizontal</td>
<td>Possible values: <code>"line-above", "line-center", "line-below"</code>. The alignment of the label when drawn on the edge.
<td>Possible values: <code>"top", "middle", "bottom", "horizontial"</code>. The alignment of the label when drawn on the edge.
If <code>horizontal</code> it will align the label absolute horizontial.</td> If <code>horizontal</code> it will align the label absolute horizontial.</td>
</tr> </tr>
<tr> <tr>

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

@ -22,19 +22,22 @@
<script type="text/javascript"> <script type="text/javascript">
// create an array with nodes // create an array with nodes
var nodes = [ var nodes = [
{id: 1, label: 'Node 1'},
{id: 2, label: 'Node 2'},
{id: 3, label: 'Node 3'},
// {id: 1, label: 'Node 1'},
// {id: 2, label: 'Node 2'},
// {id: 3, label: 'Node 3'},
{id: 4, label: 'Node 4'}, {id: 4, label: 'Node 4'},
{id: 5, label: 'Node 5'}
{id: 5, label: 'Node 5', shape:'database'}
]; ];
// create an array with edges // create an array with edges
var edges = [ var edges = [
{from: 1, to: 2},
{from: 1, to: 3},
{from: 2, to: 4},
{from: 2, to: 5}
// {from: 1, to: 2, label:'bla', font:{size: 15, face: 'arial', color: 'red'}},
// {from: 1, to: 3, arrows:{from:true, to:true, middle: true}, label:'bla'},
// {from: 1, to: 1, arrows:{from:true, to:true, middle: true}, smooth:false, label:'bla'},
{from: 4, to: 4, arrows:{from:true, to:true, middle: true}, smooth:false, label:'bla'},
{from: 5, to: 5, arrows:{from:true, to:true, middle: true}, smooth:false, label:'bla'},
// {from: 2, to: 4},
// {from: 2, to: 5}
]; ];
// create a network // create a network

+ 53
- 40
lib/network/modules/EdgesHandler.js View File

@ -25,60 +25,73 @@ class EdgesHandler {
this.options = {}; this.options = {};
this.defaultOptions = { this.defaultOptions = {
customScalingFunction: function (min,max,total,value) {
if (max == min) {
return 0.5;
}
else {
var scale = 1 / (max - min);
return Math.max(0,(value - min)*scale);
}
arrows: {
to: {enabled: false, scaleFactor:1}, // boolean / {arrowScaleFactor:1} / {enabled: false, arrowScaleFactor:1}
middle: {enabled: false, scaleFactor:1},
from: {enabled: false, scaleFactor:1}
}, },
style: {
widthMin: 1, //
widthMax: 15,//
width: 1,
widthSelectionMultiplier: 2,
hoverWidth: 1.5,
type: 'line',
color: {
color:'#848484',
highlight:'#848484',
hover: '#848484',
inherit: {
enabled: true,
source: 'from', // from / true
useGradients: false // release in 4.0
}
color: {
color:'#848484',
highlight:'#848484',
hover: '#848484',
inherit: {
enabled: true,
source: 'from', // from / true
useGradients: false // release in 4.0
}, },
opacity:1.0 opacity:1.0
}, },
value:1,
label: undefined,
labelStyle: {
fontColor: '#343434',
fontSize: 14, // px
fontFace: 'arial',
fontFill: 'white',
fontStrokeWidth: 0, // px
fontStrokeColor: 'white',
labelAlignment:'horizontal'
},
length: undefined,
arrowScaleFactor: 1,
dash: {
dashes:{
enabled: false,
preset: 'dotted',
length: 10, length: 10,
gap: 5, gap: 5,
altLength: undefined altLength: undefined
}, },
font: {
color: '#343434',
size: 14, // px
face: 'arial',
background: 'none',
stroke: 1, // px
strokeColor: 'white',
align:'horizontal'
},
hidden: false,
hoverWidth: 1.5,
label: undefined,
length: undefined,
physics: true,
scaling:{
min: 1,
max: 15,
label: {
enabled: true,
min: 14,
max: 30,
maxVisible: 30,
drawThreshold: 3
},
customScalingFunction: function (min,max,total,value) {
if (max == min) {
return 0.5;
}
else {
var scale = 1 / (max - min);
return Math.max(0,(value - min)*scale);
}
}
},
smooth: { smooth: {
enabled: true, enabled: true,
dynamic: true, dynamic: true,
type: "continuous", type: "continuous",
roundness: 0.5 roundness: 0.5
}, },
hidden: false,
physics: true
title:undefined,
width: 1,
widthSelectionMultiplier: 2,
value:1
}; };
util.extend(this.options, this.defaultOptions); util.extend(this.options, this.defaultOptions);

+ 30
- 20
lib/network/modules/NodesHandler.js View File

@ -27,23 +27,34 @@ class NodesHandler {
this.options = {}; this.options = {};
this.defaultOptions = { this.defaultOptions = {
customScalingFunction: function (min,max,total,value) {
if (max == min) {
return 0.5;
}
else {
var scale = 1 / (max - min);
return Math.max(0,(value - min)*scale);
}
},
mass: 1, mass: 1,
radiusMin: 10,
radiusMax: 30,
//radiusMin: 10,
//radiusMax: 30,
radius: 10, radius: 10,
scaling: {
min: 10,
max: 40,
label: {
enabled: true,
min: 14,
max: 30,
maxVisible: 30,
drawThreshold: 3
},
customScalingFunction: function (min,max,total,value) {
if (max == min) {
return 0.5;
}
else {
var scale = 1 / (max - min);
return Math.max(0,(value - min)*scale);
}
}
},
shape: 'ellipse', shape: 'ellipse',
image: undefined,
widthMin: 16, // px
widthMax: 64, // px
image: undefined, // --> URL
//widthMin: 16, // px
//widthMax: 64, // px
label: undefined, label: undefined,
labelStyle: { labelStyle: {
fontColor: 'black', fontColor: 'black',
@ -52,11 +63,11 @@ class NodesHandler {
fontFill: undefined, fontFill: undefined,
fontStrokeWidth: 0, // px fontStrokeWidth: 0, // px
fontStrokeColor: '#ffffff', fontStrokeColor: '#ffffff',
scaleFontWithValue: false,
fontSizeMin: 14,
fontSizeMax: 30,
fontSizeMaxVisible: 30,
fontDrawThreshold: 3
//scaleFontWithValue: false,
//fontSizeMin: 14,
//fontSizeMax: 30,
//fontSizeMaxVisible: 30,
//fontDrawThreshold: 3
}, },
value: 1, value: 1,
color: { color: {
@ -91,7 +102,6 @@ class NodesHandler {
} }
/** /**
* Set a data set with nodes for the network * Set a data set with nodes for the network
* @param {Array | DataSet | DataView} nodes The data containing the nodes. * @param {Array | DataSet | DataView} nodes The data containing the nodes.

+ 281
- 220
lib/network/modules/components/edges/EdgeMain.js View File

@ -21,8 +21,6 @@ class Edge {
throw "No body provided"; throw "No body provided";
} }
this.initializing = true;
this.options = util.bridgeObject(globalOptions); this.options = util.bridgeObject(globalOptions);
this.body = body; this.body = body;
@ -31,7 +29,7 @@ class Edge {
this.fromId = undefined; this.fromId = undefined;
this.toId = undefined; this.toId = undefined;
this.title = undefined; this.title = undefined;
this.widthSelected = this.options.style.width * this.options.style.widthSelectionMultiplier;
this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
this.value = undefined; this.value = undefined;
this.selected = false; this.selected = false;
this.hover = false; this.hover = false;
@ -53,13 +51,11 @@ class Edge {
this.connected = false; this.connected = false;
this.setOptions(options);
this.setOptions(options, true);
this.controlNodesEnabled = false; this.controlNodesEnabled = false;
this.controlNodes = {from: undefined, to: undefined, positions: {}}; this.controlNodes = {from: undefined, to: undefined, positions: {}};
this.connectedNode = undefined; this.connectedNode = undefined;
this.initializing = false;
} }
@ -68,92 +64,83 @@ class Edge {
* @param {Object} options an object with options * @param {Object} options an object with options
* @param {Object} constants and object with default, global options * @param {Object} constants and object with default, global options
*/ */
setOptions(options) {
setOptions(options, doNotEmit = false) {
if (!options) { if (!options) {
return; return;
} }
this.colorDirty = true; this.colorDirty = true;
var fields = ['style', 'label', 'labelStyle',
'arrowScaleFactor', 'dash', 'opacity',
'customScalingFunction', 'value', 'hidden', 'physics'
var fields = [
'font',
'hidden',
'hoverWidth',
'label',
'length',
'line',
'opacity',
'physics',
'scaling',
'value',
'width',
'widthMin',
'widthMax',
'widthSelectionMultiplier',
]; ];
util.selectiveDeepExtend(fields, this.options, options); util.selectiveDeepExtend(fields, this.options, options);
util.mergeOptions(this.options, options, 'inheritColor');
util.mergeOptions(this.options, options, 'smooth'); util.mergeOptions(this.options, options, 'smooth');
util.mergeOptions(this.options, options, 'dashes');
if (options.from !== undefined) {
this.fromId = options.from;
}
if (options.to !== undefined) {
this.toId = options.to;
if (options.arrows !== undefined) {
util.mergeOptions(this.options.arrows, options.arrows, 'to');
util.mergeOptions(this.options.arrows, options.arrows, 'middle');
util.mergeOptions(this.options.arrows, options.arrows, 'from');
} }
if (options.id !== undefined) {
this.id = options.id;
}
if (options.id !== undefined) {this.id = options.id;}
if (options.from !== undefined) {this.fromId = options.from;}
if (options.to !== undefined) {this.toId = options.to;}
if (options.label !== undefined) { if (options.label !== undefined) {
this.label = options.label; this.label = options.label;
this.labelDirty = true; this.labelDirty = true;
} }
if (options.title !== undefined) {
this.title = options.title;
}
if (options.value !== undefined) {
this.value = options.value;
}
if (options.length !== undefined) {
this.physics.springLength = options.length;
}
if (options.title !== undefined) {this.title = options.title;}
if (options.value !== undefined) {this.value = options.value;}
if (options.style !== undefined) {
if (options.style.color !== undefined) {
this.options.style.color.inherit.enabled = false;
if (util.isString(options.style.color)) {
this.options.style.color.color = options.style.color;
this.options.style.color.highlight = options.style.color;
if (options.color !== undefined) {
if (util.isString(options.color)) {
this.options.color.color = options.color;
this.options.color.highlight = options.color;
}
else {
if (options.color.color !== undefined) {
this.options.color.color = options.color.color;
} }
else {
if (options.style.color.color !== undefined) {
this.options.style.color.color = options.style.color.color;
}
if (options.style.color.highlight !== undefined) {
this.options.style.color.highlight = options.style.color.highlight;
}
if (options.style.color.hover !== undefined) {
this.options.style.color.hover = options.style.color.hover;
}
if (options.color.highlight !== undefined) {
this.options.color.highlight = options.color.highlight;
} }
if (options.color.hover !== undefined) {
this.options.color.hover = options.color.hover;
}
}
// inherit colors
if (options.color.inherit === undefined) {
this.options.color.inherit.enabled = false;
}
else {
util.mergeOptions(this.options.color, options.color, 'inherit');
} }
} }
// A node is connected when it has a from and to node that both exist in the network.body.nodes. // A node is connected when it has a from and to node that both exist in the network.body.nodes.
this.connect(); this.connect();
this.widthSelected = this.options.style.width * this.options.style.widthSelectionMultiplier;
this.setupSmoothEdges(this.initializing === false);
// set draw method based on style
switch (this.options.style.type) {
case 'line':
this.draw = this._drawLine;
break;
case 'arrow':
this.draw = this._drawArrow;
break;
case 'arrow-center':
this.draw = this._drawArrowCenter;
break;
case 'dash-line':
this.draw = this._drawDashLine;
break;
default:
this.draw = this._drawLine;
break;
}
this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
this.setupSmoothEdges(doNotEmit);
} }
@ -163,7 +150,7 @@ class Edge {
* *
* @private * @private
*/ */
setupSmoothEdges(emitChange = true) {
setupSmoothEdges(doNotEmit = false) {
var changedData = false; var changedData = false;
if (this.options.smooth.enabled == true && this.options.smooth.dynamic == true) { if (this.options.smooth.enabled == true && this.options.smooth.dynamic == true) {
if (this.via === undefined) { if (this.via === undefined) {
@ -192,7 +179,7 @@ class Edge {
} }
// node has been added or deleted // node has been added or deleted
if (changedData === true && emitChange === true) {
if (changedData === true && doNotEmit === false) {
this.body.emitter.emit("_dataChanged"); this.body.emitter.emit("_dataChanged");
} }
} }
@ -278,9 +265,9 @@ class Edge {
setValueRange(min, max, total) { setValueRange(min, max, total) {
if (!this.widthFixed && this.value !== undefined) { if (!this.widthFixed && this.value !== undefined) {
var scale = this.options.customScalingFunction(min, max, total, this.value); var scale = this.options.customScalingFunction(min, max, total, this.value);
var widthDiff = this.options.style.widthMax - this.options.style.widthMin;
this.options.style.width = this.options.style.widthMin + scale * widthDiff;
this.widthSelected = this.options.style.width * this.options.style.widthSelectionMultiplier;
var widthDiff = this.options.widthMax - this.options.widthMin;
this.options.width = this.options.widthMin + scale * widthDiff;
this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
} }
} }
@ -292,7 +279,60 @@ class Edge {
* @param {CanvasRenderingContext2D} ctx * @param {CanvasRenderingContext2D} ctx
*/ */
draw(ctx) { draw(ctx) {
throw "Method draw not initialized in edge";
let via = this.drawLine(ctx);
this.drawArrows(ctx);
this.drawLabel(ctx, via);
}
drawLine(ctx) {
if (this.options.dashes.enabled === false) {
return this._drawLine(ctx);
}
else {
return this._drawDashLine(ctx);
}
}
drawArrows(ctx) {
if (this.options.arrows.from.enabled === true) {this._drawArrowHead(ctx,'from');}
//if (this.options.arrows.middle.enabled === true) {this._drawArrowCenter(ctx, via);}
if (this.options.arrows.to.enabled === true) {this._drawArrowHead(ctx,'to');}
}
drawLabel(ctx, via) {
if (this.label !== undefined) {
// set style
var node1 = this.from;
var node2 = this.to;
if (node1.id != node2.id) {
var point;
if (this.options.smooth.enabled == true && via != undefined) {
var midpointX = 0.5 * (0.5 * (this.from.x + via.x) + 0.5 * (this.to.x + via.x));
var midpointY = 0.5 * (0.5 * (this.from.y + via.y) + 0.5 * (this.to.y + via.y));
point = {x: midpointX, y: midpointY};
}
else {
point = this._pointOnLine(0.5);
}
this._label(ctx, this.label, point.x, point.y);
}
else {
var x, y;
var radius = 25;
if (node1.width > node1.height) {
x = node1.x + node1.width * 0.5;
y = node1.y - radius;
}
else {
x = node1.x + radius;
y = node1.y - node1.height * 0.5;
}
point = this._pointOnCircle(x, y, radius, 0.125);
this._label(ctx, this.label, point.x, point.y);
}
}
} }
@ -322,7 +362,7 @@ class Edge {
_getColor(ctx) { _getColor(ctx) {
var colorObj = this.options.style.color;
var colorObj = this.options.color;
if (colorObj.inherit.enabled === true) { if (colorObj.inherit.enabled === true) {
if (colorObj.inherit.useGradients == true) { if (colorObj.inherit.useGradients == true) {
@ -332,8 +372,8 @@ class Edge {
toColor = this.to.options.color.highlight.border; toColor = this.to.options.color.highlight.border;
if (this.from.selected == false && this.to.selected == false) { if (this.from.selected == false && this.to.selected == false) {
fromColor = util.overrideOpacity(this.from.options.color.border, this.options.style.opacity);
toColor = util.overrideOpacity(this.to.options.color.border, this.options.style.opacity);
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) { else if (this.from.selected == true && this.to.selected == false) {
toColor = this.to.options.color.border; toColor = this.to.options.color.border;
@ -352,12 +392,12 @@ class Edge {
if (colorObj.inherit.source == "to") { if (colorObj.inherit.source == "to") {
colorObj.highlight = this.to.options.color.highlight.border; colorObj.highlight = this.to.options.color.highlight.border;
colorObj.hover = this.to.options.color.hover.border; colorObj.hover = this.to.options.color.hover.border;
colorObj.color = util.overrideOpacity(this.to.options.color.border, this.options.style.opacity);
colorObj.color = util.overrideOpacity(this.to.options.color.border, this.options.color.opacity);
} }
else { // (this.options.style.color.inherit.source == "from") {
else { // (this.options.color.inherit.source == "from") {
colorObj.highlight = this.from.options.color.highlight.border; colorObj.highlight = this.from.options.color.highlight.border;
colorObj.hover = this.from.options.color.hover.border; colorObj.hover = this.from.options.color.hover.border;
colorObj.color = util.overrideOpacity(this.from.options.color.border, this.options.style.opacity);
colorObj.color = util.overrideOpacity(this.from.options.color.border, this.options.color.opacity);
} }
} }
} }
@ -389,44 +429,31 @@ class Edge {
// set style // set style
ctx.strokeStyle = this._getColor(ctx); ctx.strokeStyle = this._getColor(ctx);
ctx.lineWidth = this._getLineWidth(); ctx.lineWidth = this._getLineWidth();
var via;
if (this.from != this.to) { if (this.from != this.to) {
// draw line // draw line
var via = this._line(ctx);
// draw label
var point;
if (this.label) {
if (this.options.smooth.enabled == true && via != undefined) {
var midpointX = 0.5 * (0.5 * (this.from.x + via.x) + 0.5 * (this.to.x + via.x));
var midpointY = 0.5 * (0.5 * (this.from.y + via.y) + 0.5 * (this.to.y + via.y));
point = {x: midpointX, y: midpointY};
}
else {
point = this._pointOnLine(0.5);
}
this._label(ctx, this.label, point.x, point.y);
}
via = this._line(ctx);
} }
else { else {
var x, y; var x, y;
var radius = this.physics.springLength / 4;
var radius = 25;
var node = this.from; var node = this.from;
if (!node.width) { if (!node.width) {
node.resize(ctx); node.resize(ctx);
} }
if (node.width > node.height) { if (node.width > node.height) {
x = node.x + node.width / 2;
x = node.x + node.width * 0.5;
y = node.y - radius; y = node.y - radius;
} }
else { else {
x = node.x + radius; x = node.x + radius;
y = node.y - node.height / 2;
y = node.y - node.height * 0.5;
} }
this._circle(ctx, x, y, radius); this._circle(ctx, x, y, radius);
point = this._pointOnCircle(x, y, radius, 0.5);
this._label(ctx, this.label, point.x, point.y);
} }
return via;
} }
@ -438,14 +465,14 @@ class Edge {
*/ */
_getLineWidth() { _getLineWidth() {
if (this.selected == true) { if (this.selected == true) {
return Math.max(Math.min(this.widthSelected, this.options.style.widthMax), 0.3 * this.networkScaleInv);
return Math.max(Math.min(this.widthSelected, this.options.widthMax), 0.3 * this.networkScaleInv);
} }
else { else {
if (this.hover == true) { if (this.hover == true) {
return Math.max(Math.min(this.options.style.hoverWidth, this.options.style.widthMax), 0.3 * this.networkScaleInv);
return Math.max(Math.min(this.options.hoverWidth, this.options.widthMax), 0.3 * this.networkScaleInv);
} }
else { else {
return Math.max(this.options.style.width, 0.3 * this.networkScaleInv);
return Math.max(this.options.width, 0.3 * this.networkScaleInv);
} }
} }
} }
@ -708,23 +735,23 @@ class Edge {
_label(ctx, text, x, y) { _label(ctx, text, x, y) {
if (text) { if (text) {
ctx.font = ((this.from.selected || this.to.selected) ? "bold " : "") + ctx.font = ((this.from.selected || this.to.selected) ? "bold " : "") +
this.options.labelStyle.fontSize + "px " + this.options.labelStyle.fontFace;
this.options.font.size + "px " + this.options.font.face;
var yLine; var yLine;
if (this.labelDirty == true) { if (this.labelDirty == true) {
var lines = String(text).split('\n'); var lines = String(text).split('\n');
var lineCount = lines.length; var lineCount = lines.length;
var fontSize = Number(this.options.labelStyle.fontSize);
yLine = y + (1 - lineCount) / 2 * fontSize;
var fontSize = Number(this.options.font.size);
yLine = y + (1 - lineCount) * 0.5 * fontSize;
var width = ctx.measureText(lines[0]).width; var width = ctx.measureText(lines[0]).width;
for (var i = 1; i < lineCount; i++) { for (var i = 1; i < lineCount; i++) {
var lineWidth = ctx.measureText(lines[i]).width; var lineWidth = ctx.measureText(lines[i]).width;
width = lineWidth > width ? lineWidth : width; width = lineWidth > width ? lineWidth : width;
} }
var height = this.options.labelStyle.fontSize * lineCount;
var left = x - width / 2;
var top = y - height / 2;
var height = this.options.font.size * lineCount;
var left = x - width * 0.5;
var top = y - height * 0.5;
// cache // cache
this.labelDimensions = {top: top, left: left, width: width, height: height, yLine: yLine}; this.labelDimensions = {top: top, left: left, width: width, height: height, yLine: yLine};
@ -734,7 +761,7 @@ class Edge {
ctx.save(); ctx.save();
if (this.options.labelStyle.labelAlignment != "horizontal") {
if (this.options.font.align != "horizontal") {
ctx.translate(x, yLine); ctx.translate(x, yLine);
this._rotateForLabelAlignment(ctx); this._rotateForLabelAlignment(ctx);
x = 0; x = 0;
@ -776,18 +803,18 @@ class Edge {
* @private * @private
*/ */
_drawLabelRect(ctx) { _drawLabelRect(ctx) {
if (this.options.labelStyle.fontFill !== undefined && this.options.labelStyle.fontFill !== undefined && this.options.labelStyle.fontFill !== "none") {
ctx.fillStyle = this.options.labelStyle.fontFill;
if (this.options.font.background !== undefined && this.options.font.background !== "none") {
ctx.fillStyle = this.options.font.background;
var lineMargin = 2; var lineMargin = 2;
if (this.options.labelStyle.labelAlignment == 'line-center') {
if (this.options.font.align == 'middle') {
ctx.fillRect(-this.labelDimensions.width * 0.5, -this.labelDimensions.height * 0.5, this.labelDimensions.width, this.labelDimensions.height); ctx.fillRect(-this.labelDimensions.width * 0.5, -this.labelDimensions.height * 0.5, this.labelDimensions.width, this.labelDimensions.height);
} }
else if (this.options.labelStyle.labelAlignment == 'line-above') {
else if (this.options.font.align == 'top') {
ctx.fillRect(-this.labelDimensions.width * 0.5, -(this.labelDimensions.height + lineMargin), this.labelDimensions.width, this.labelDimensions.height); ctx.fillRect(-this.labelDimensions.width * 0.5, -(this.labelDimensions.height + lineMargin), this.labelDimensions.width, this.labelDimensions.height);
} }
else if (this.options.labelStyle.labelAlignment == 'line-below') {
else if (this.options.font.align == 'bottom') {
ctx.fillRect(-this.labelDimensions.width * 0.5, lineMargin, this.labelDimensions.width, this.labelDimensions.height); ctx.fillRect(-this.labelDimensions.width * 0.5, lineMargin, this.labelDimensions.width, this.labelDimensions.height);
} }
else { else {
@ -809,17 +836,17 @@ class Edge {
*/ */
_drawLabelText(ctx, x, yLine, lines, lineCount, fontSize) { _drawLabelText(ctx, x, yLine, lines, lineCount, fontSize) {
// draw text // draw text
ctx.fillStyle = this.options.labelStyle.fontColor || "black";
ctx.fillStyle = this.options.font.color || "black";
ctx.textAlign = "center"; ctx.textAlign = "center";
// check for label alignment // check for label alignment
if (this.options.labelStyle.labelAlignment != 'horizontal') {
if (this.options.font.align != 'horizontal') {
var lineMargin = 2; var lineMargin = 2;
if (this.options.labelStyle.labelAlignment == 'line-above') {
if (this.options.font.align == 'top') {
ctx.textBaseline = "alphabetic"; ctx.textBaseline = "alphabetic";
yLine -= 2 * lineMargin; // distance from edge, required because we use alphabetic. Alphabetic has less difference between browsers yLine -= 2 * lineMargin; // distance from edge, required because we use alphabetic. Alphabetic has less difference between browsers
} }
else if (this.options.labelStyle.labelAlignment == 'line-below') {
else if (this.options.font.align == 'bottom') {
ctx.textBaseline = "hanging"; ctx.textBaseline = "hanging";
yLine += 2 * lineMargin;// distance from edge, required because we use hanging. Hanging has less difference between browsers yLine += 2 * lineMargin;// distance from edge, required because we use hanging. Hanging has less difference between browsers
} }
@ -832,13 +859,13 @@ class Edge {
} }
// check for strokeWidth // check for strokeWidth
if (this.options.labelStyle.fontStrokeWidth > 0) {
ctx.lineWidth = this.options.labelStyle.fontStrokeWidth;
ctx.strokeStyle = this.options.labelStyle.fontStrokeColor;
if (this.options.font.stroke > 0) {
ctx.lineWidth = this.options.font.stroke;
ctx.strokeStyle = this.options.font.strokeColor;
ctx.lineJoin = 'round'; ctx.lineJoin = 'round';
} }
for (var i = 0; i < lineCount; i++) { for (var i = 0; i < lineCount; i++) {
if (this.options.labelStyle.fontStrokeWidth > 0) {
if (this.options.font.stroke > 0) {
ctx.strokeText(lines[i], x, yLine); ctx.strokeText(lines[i], x, yLine);
} }
ctx.fillText(lines[i], x, yLine); ctx.fillText(lines[i], x, yLine);
@ -848,7 +875,7 @@ class Edge {
/** /**
* Redraw a edge as a dashed line
* Redraw a edge as a dashes line
* Draw this edge in the given canvas * Draw this edge in the given canvas
* @author David Jordan * @author David Jordan
* @date 2012-08-08 * @date 2012-08-08
@ -867,8 +894,8 @@ class Edge {
ctx.save(); ctx.save();
// configure the dash pattern // configure the dash pattern
var pattern = [0]; var pattern = [0];
if (this.options.dash.length !== undefined && this.options.dash.gap !== undefined) {
pattern = [this.options.dash.length, this.options.dash.gap];
if (this.options.dashes.length !== undefined && this.options.dashes.gap !== undefined) {
pattern = [this.options.dashes.length, this.options.dashes.gap];
} }
else { else {
pattern = [5, 5]; pattern = [5, 5];
@ -887,18 +914,18 @@ class Edge {
ctx.restore(); ctx.restore();
} }
else { // unsupporting smooth lines else { // unsupporting smooth lines
// draw dashed line
// draw dashes line
ctx.beginPath(); ctx.beginPath();
ctx.lineCap = 'round'; ctx.lineCap = 'round';
if (this.options.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
if (this.options.dashes.altLength !== undefined) //If an alt dash value has been set add to the array this value
{ {
ctx.dashedLine(this.from.x, this.from.y, this.to.x, this.to.y,
[this.options.dash.length, this.options.dash.gap, this.options.dash.altLength, this.options.dash.gap]);
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.dash.length !== undefined && this.options.dash.gap !== undefined) //If a dash and gap value has been set add to the array this value
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.dashedLine(this.from.x, this.from.y, this.to.x, this.to.y,
[this.options.dash.length, this.options.dash.gap]);
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 else //If all else fails draw a line
{ {
@ -908,19 +935,7 @@ class Edge {
ctx.stroke(); ctx.stroke();
} }
// draw label
if (this.label) {
var point;
if (this.options.smooth.enabled == true && via != undefined) {
var midpointX = 0.5 * (0.5 * (this.from.x + via.x) + 0.5 * (this.to.x + via.x));
var midpointY = 0.5 * (0.5 * (this.from.y + via.y) + 0.5 * (this.to.y + via.y));
point = {x: midpointX, y: midpointY};
}
else {
point = this._pointOnLine(0.5);
}
this._label(ctx, this.label, point.x, point.y);
}
return via;
} }
@ -948,7 +963,7 @@ class Edge {
* @private * @private
*/ */
_pointOnCircle(x, y, radius, percentage) { _pointOnCircle(x, y, radius, percentage) {
var angle = (percentage - 3 / 8) * 2 * Math.PI;
var angle = percentage * 2 * Math.PI;
return { return {
x: x + radius * Math.cos(angle), x: x + radius * Math.cos(angle),
y: y - radius * Math.sin(angle) y: y - radius * Math.sin(angle)
@ -975,7 +990,7 @@ class Edge {
var via = this._line(ctx); var via = this._line(ctx);
var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x)); var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
var length = (10 + 5 * this.options.style.width) * this.options.arrowScaleFactor;
var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
// draw an arrow halfway the line // draw an arrow halfway the line
if (this.options.smooth.enabled == true && via != undefined) { if (this.options.smooth.enabled == true && via != undefined) {
var midpointX = 0.5 * (0.5 * (this.from.x + via.x) + 0.5 * (this.to.x + via.x)); var midpointX = 0.5 * (0.5 * (this.from.x + via.x) + 0.5 * (this.to.x + via.x));
@ -998,7 +1013,7 @@ class Edge {
else { else {
// draw circle // draw circle
var x, y; var x, y;
var radius = 0.25 * Math.max(100, this.physics.springLength);
var radius = 25;
var node = this.from; var node = this.from;
if (!node.width) { if (!node.width) {
node.resize(ctx); node.resize(ctx);
@ -1015,7 +1030,7 @@ class Edge {
// draw all arrows // draw all arrows
var angle = 0.2 * Math.PI; var angle = 0.2 * Math.PI;
var length = (10 + 5 * this.options.style.width) * this.options.arrowScaleFactor;
var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
point = this._pointOnCircle(x, y, radius, 0.5); point = this._pointOnCircle(x, y, radius, 0.5);
ctx.arrow(point.x, point.y, angle, length); ctx.arrow(point.x, point.y, angle, length);
ctx.fill(); ctx.fill();
@ -1043,20 +1058,21 @@ class Edge {
* This function uses binary search to look for the point where the bezier curve crosses the border of the node. * This function uses binary search to look for the point where the bezier curve crosses the border of the node.
* *
* @param from * @param from
* @param ctx
* @returns {*} * @returns {*}
* @private * @private
*/ */
_findBorderPosition(from, ctx) {
_findBorderPositionBezier(nearNode, ctx) {
var maxIterations = 10; var maxIterations = 10;
var iteration = 0; var iteration = 0;
var low = 0; var low = 0;
var high = 1; var high = 1;
var pos, angle, distanceToBorder, distanceToNodes, difference;
var pos, angle, distanceToBorder, distanceToPoint, difference;
var threshold = 0.2; var threshold = 0.2;
var node = this.to; var node = this.to;
if (from == true) {
var from = false;
if (nearNode.id === this.from.id) {
node = this.from; node = this.from;
from = true;
} }
while (low <= high && iteration < maxIterations) { while (low <= high && iteration < maxIterations) {
@ -1065,8 +1081,8 @@ class Edge {
pos = this._pointOnBezier(middle); pos = this._pointOnBezier(middle);
angle = Math.atan2((node.y - pos.y), (node.x - pos.x)); angle = Math.atan2((node.y - pos.y), (node.x - pos.x));
distanceToBorder = node.distanceToBorder(ctx, angle); distanceToBorder = node.distanceToBorder(ctx, angle);
distanceToNodes = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2));
difference = distanceToBorder - distanceToNodes;
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) { if (Math.abs(difference) < threshold) {
break; // found break; // found
} }
@ -1094,108 +1110,153 @@ class Edge {
return pos; return pos;
} }
/**
* This function uses binary search to look for the point where the bezier curve crosses the border of the node.
*
* @param from
* @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;
}
/** /**
* Redraw a edge as a line with an arrow
* Draw this edge in the given canvas
* The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
* @param {CanvasRenderingContext2D} ctx
*
* @param ctx
* @param node1
* @param node2
* @param guideOffset
* @private * @private
*/ */
_drawArrow(ctx) {
_drawArrowHead(ctx,position) {
// set style // set style
ctx.strokeStyle = this._getColor(ctx); ctx.strokeStyle = this._getColor(ctx);
ctx.fillStyle = ctx.strokeStyle; ctx.fillStyle = ctx.strokeStyle;
ctx.lineWidth = this._getLineWidth(); ctx.lineWidth = this._getLineWidth();
// set vars // set vars
var angle, length, arrowPos;
var angle;
var length;
var arrowPos;
var node1;
var node2;
var guideOffset;
var scaleFactor;
if (position == 'from') {
node1 = this.from;
node2 = this.to;
guideOffset = 0.1;
scaleFactor = this.options.arrows.from.scaleFactor;
}
else if (position == 'to') {
node1 = this.to;
node2 = this.from;
guideOffset = -0.1;
scaleFactor = this.options.arrows.to.scaleFactor;
}
// if not connected to itself // if not connected to itself
if (this.from != this.to) {
// draw line
this._line(ctx);
if (node1 != node2) {
// draw arrow head // draw arrow head
if (this.options.smooth.enabled == true) { if (this.options.smooth.enabled == true) {
var via = this._getViaCoordinates();
arrowPos = this._findBorderPosition(false, ctx);
var guidePos = this._pointOnBezier(Math.max(0.0, arrowPos.t - 0.1))
arrowPos = this._findBorderPositionBezier(node1, ctx);
var guidePos = this._pointOnBezier(Math.max(0.0, arrowPos.t + guideOffset))
angle = Math.atan2((arrowPos.y - guidePos.y), (arrowPos.x - guidePos.x)); angle = Math.atan2((arrowPos.y - guidePos.y), (arrowPos.x - guidePos.x));
} }
else { else {
angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
var dx = (this.to.x - this.from.x);
var dy = (this.to.y - this.from.y);
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 edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
var toBorderDist = this.to.distanceToBorder(ctx, angle); var toBorderDist = this.to.distanceToBorder(ctx, angle);
var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength; var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
arrowPos = {}; arrowPos = {};
arrowPos.x = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
arrowPos.y = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
arrowPos.x = (1 - toBorderPoint) * node2.x + toBorderPoint * node1.x;
arrowPos.y = (1 - toBorderPoint) * node2.y + toBorderPoint * node1.y;
} }
// draw arrow at the end of the line // draw arrow at the end of the line
length = (10 + 5 * this.options.style.width) * this.options.arrowScaleFactor;
length = (10 + 5 * this.options.width) * scaleFactor;
ctx.arrow(arrowPos.x, arrowPos.y, angle, length); ctx.arrow(arrowPos.x, arrowPos.y, angle, length);
ctx.fill(); ctx.fill();
ctx.stroke(); ctx.stroke();
// draw label
if (this.label) {
var point;
if (this.options.smooth.enabled == true && via != undefined) {
point = this._pointOnBezier(0.5);
}
else {
point = this._pointOnLine(0.5);
}
this._label(ctx, this.label, point.x, point.y);
}
} }
else { else {
// draw circle // draw circle
var node = this.from;
var x, y, arrow;
var radius = 0.25 * Math.max(100, this.physics.springLength);
if (!node.width) {
node.resize(ctx);
var angle, point;
var x, y;
var radius = 25;
if (!node1.width) {
node1.resize(ctx);
} }
if (node.width > node.height) {
x = node.x + node.width * 0.5;
y = node.y - radius;
arrow = {
x: x,
y: node.y,
angle: 0.9 * Math.PI
};
// get circle coordinates
if (node1.width > node1.height) {
x = node1.x + node1.width * 0.5;
y = node1.y - radius;
} }
else { else {
x = node.x + radius;
y = node.y - node.height * 0.5;
arrow = {
x: node.x,
y: y,
angle: 0.6 * Math.PI
};
x = node1.x + radius;
y = node1.y - node1.height * 0.5;
} }
ctx.beginPath();
// TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.stroke();
if (position == 'from') {
point = this._findBorderPositionCircle(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 {
point = this._findBorderPositionCircle(x, y, radius, node1, 0.6, 0.8, 1, ctx);
angle = point.t * -2 * Math.PI + 1.5 * Math.PI - 1.1 * Math.PI;
}
// draw all arrows // draw all arrows
var length = (10 + 5 * this.options.style.width) * this.options.arrowScaleFactor;
ctx.arrow(arrow.x, arrow.y, arrow.angle, length);
var length = (10 + 5 * this.options.width) * scaleFactor;
ctx.arrow(point.x, point.y, angle, length);
ctx.fill(); ctx.fill();
ctx.stroke(); ctx.stroke();
// draw label
if (this.label) {
point = this._pointOnCircle(x, y, radius, 0.5);
this._label(ctx, this.label, point.x, point.y);
}
} }
} }
@ -1248,7 +1309,7 @@ class Edge {
} }
else { else {
var x, y, dx, dy; var x, y, dx, dy;
var radius = 0.25 * this.physics.springLength;
var radius = 25;
var node = this.from; var node = this.from;
if (node.width > node.height) { if (node.width > node.height) {
x = node.x + 0.5 * node.width; x = node.x + 0.5 * node.width;
@ -1470,7 +1531,7 @@ class Edge {
// draw arrow head // draw arrow head
var controlnodeFromPos; var controlnodeFromPos;
if (this.options.smooth.enabled == true) { if (this.options.smooth.enabled == true) {
controlnodeFromPos = this._findBorderPosition(true, ctx);
controlnodeFromPos = this._findBorderPositionBezier(true, ctx);
} }
else { else {
var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x)); var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
@ -1499,7 +1560,7 @@ class Edge {
// draw arrow head // draw arrow head
var controlnodeToPos; var controlnodeToPos;
if (this.options.smooth.enabled == true) { if (this.options.smooth.enabled == true) {
controlnodeToPos = this._findBorderPosition(false, ctx);
controlnodeToPos = this._findBorderPositionBezier(false, ctx);
} }
else { else {
var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x)); var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));

+ 3
- 0
lib/network/modules/components/unified/labels.js View File

@ -0,0 +1,3 @@
/**
* Created by Alex on 3/17/2015.
*/

Loading…
Cancel
Save