Browse Source

simplified fixed for nodes, removed duplicate datachanged events with support nodes in edges

flowchartTest
Alex de Mulder 9 years ago
parent
commit
cf057e7ecc
16 changed files with 5743 additions and 5695 deletions
  1. +5563
    -5535
      dist/vis.js
  2. +1
    -2
      examples/network/01_basic_usage.html
  3. +2
    -34
      lib/network/Network.js
  4. +8
    -2
      lib/network/modules/Clustering.js
  5. +12
    -2
      lib/network/modules/EdgesHandler.js
  6. +11
    -11
      lib/network/modules/InteractionHandler.js
  7. +3
    -3
      lib/network/modules/LayoutEngine.js
  8. +8
    -1
      lib/network/modules/NodesHandler.js
  9. +12
    -11
      lib/network/modules/PhysicsEngine.js
  10. +24
    -13
      lib/network/modules/components/Edge.js
  11. +26
    -43
      lib/network/modules/components/Node.js
  12. +6
    -13
      lib/network/modules/components/edges/bezierEdgeDynamic.js
  13. +3
    -1
      lib/network/modules/components/edges/bezierEdgeStatic.js
  14. +3
    -3
      lib/network/modules/components/edges/straightEdge.js
  15. +44
    -20
      lib/network/modules/components/unified/label.js
  16. +17
    -1
      lib/util.js

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


+ 1
- 2
examples/network/01_basic_usage.html View File

@ -32,8 +32,7 @@
// create an array with edges // create an array with edges
var edges = [ var edges = [
{from: 1, to: 3}, {from: 1, to: 3},
{from: 1, to: 1},
{from: 1, to: 2,smooth:false, arrows:{from:true, middle:true, to: true}},
{from: 1, to: 2},
{from: 2, to: 4}, {from: 2, to: 4},
{from: 2, to: 5} {from: 2, to: 5}
]; ];

+ 2
- 34
lib/network/Network.js View File

@ -125,7 +125,7 @@ function Network (container, data, options) {
var t0 = new Date().valueOf(); var t0 = new Date().valueOf();
// update shortcut lists // update shortcut lists
this._updateVisibleIndices(); this._updateVisibleIndices();
this.physics._updatePhysicsIndices();
this.physics.updatePhysicsIndices();
// update values // update values
this._updateValueRange(this.body.nodes); this._updateValueRange(this.body.nodes);
this._updateValueRange(this.body.edges); this._updateValueRange(this.body.edges);
@ -287,41 +287,9 @@ Network.prototype.setOptions = function (options) {
//// TODO: work out these options and document them //// TODO: work out these options and document them
//if (options.edges) {
// if (options.edges.color !== undefined) {
// if (util.isString(options.edges.color)) {
// this.constants.edges.color = {};
// this.constants.edges.color.color = options.edges.color;
// this.constants.edges.color.highlight = options.edges.color;
// this.constants.edges.color.hover = options.edges.color;
// }
// else {
// if (options.edges.color.color !== undefined) {this.constants.edges.color.color = options.edges.color.color;}
// if (options.edges.color.highlight !== undefined) {this.constants.edges.color.highlight = options.edges.color.highlight;}
// if (options.edges.color.hover !== undefined) {this.constants.edges.color.hover = options.edges.color.hover;}
// }
// this.constants.edges.inheritColor = false;
// }
// //
// if (!options.edges.fontColor) {
// if (options.edges.color !== undefined) {
// if (util.isString(options.edges.color)) {this.constants.edges.fontColor = options.edges.color;}
// else if (options.edges.color.color !== undefined) {this.constants.edges.fontColor = options.edges.color.color;}
// }
// }
//}
// //
//if (options.nodes) {
// if (options.nodes.color) {
// var newColorObj = util.parseColor(options.nodes.color);
// this.constants.nodes.color.background = newColorObj.background;
// this.constants.nodes.color.border = newColorObj.border;
// this.constants.nodes.color.highlight.background = newColorObj.highlight.background;
// this.constants.nodes.color.highlight.border = newColorObj.highlight.border;
// this.constants.nodes.color.hover.background = newColorObj.hover.background;
// this.constants.nodes.color.hover.border = newColorObj.hover.border;
// }
//}
//
//if (options.groups) { //if (options.groups) {
// for (var groupname in options.groups) { // for (var groupname in options.groups) {
// if (options.groups.hasOwnProperty(groupname)) { // if (options.groups.hasOwnProperty(groupname)) {

+ 8
- 2
lib/network/modules/Clustering.js View File

@ -131,8 +131,14 @@ class ClusterEngine {
var node = this.body.nodes[nodeId]; var node = this.body.nodes[nodeId];
options = this._checkOptions(options, node); options = this._checkOptions(options, node);
if (options.clusterNodeProperties.x === undefined) {options.clusterNodeProperties.x = node.x; options.clusterNodeProperties.allowedToMoveX = !node.xFixed;}
if (options.clusterNodeProperties.y === undefined) {options.clusterNodeProperties.y = node.y; options.clusterNodeProperties.allowedToMoveY = !node.yFixed;}
if (options.clusterNodeProperties.x === undefined) {options.clusterNodeProperties.x = node.x;}
if (options.clusterNodeProperties.y === undefined) {options.clusterNodeProperties.y = node.y;}
if (options.clusterNodeProperties.fixed === undefined) {
options.clusterNodeProperties.fixed = {};
options.clusterNodeProperties.fixed.x = node.options.fixed.x;
options.clusterNodeProperties.fixed.y = node.options.fixed.y;
}
var childNodesObj = {}; var childNodesObj = {};
var childEdgesObj = {} var childEdgesObj = {}

+ 12
- 2
lib/network/modules/EdgesHandler.js View File

@ -99,7 +99,17 @@ class EdgesHandler {
} }
setOptions(options) { setOptions(options) {
if (options) {
if (options.color !== undefined) {
if (util.isString(options.color)) {
util.assignAllKeys(this.options.color, options.color);
}
else {
util.extend(this.options.color, options.color);
}
this.options.color.inherit.enabled = false;
}
}
} }
@ -191,7 +201,7 @@ class EdgesHandler {
if (edge === null) { if (edge === null) {
// update edge // update edge
edge.disconnect(); edge.disconnect();
edge.setOptions(data);
dataChanged = edge.setOptions(data) || dataChanged; // if a support node is added, data can be changed.
edge.connect(); edge.connect();
} }
else { else {

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

@ -173,8 +173,8 @@ class InteractionHandler {
this.onTouch(event); this.onTouch(event);
} }
var node = this.selectionHandler.getNodeAt(this.drag.pointer);
// note: drag.pointer is set in onTouch to get the initial touch location // note: drag.pointer is set in onTouch to get the initial touch location
var node = this.selectionHandler.getNodeAt(this.drag.pointer);
this.drag.dragging = true; this.drag.dragging = true;
this.drag.selection = []; this.drag.selection = [];
@ -203,12 +203,12 @@ class InteractionHandler {
// store original x, y, xFixed and yFixed, make the node temporarily Fixed // store original x, y, xFixed and yFixed, make the node temporarily Fixed
x: object.x, x: object.x,
y: object.y, y: object.y,
xFixed: object.xFixed,
yFixed: object.yFixed
xFixed: object.options.fixed.x,
yFixed: object.options.fixed.y
}; };
object.xFixed = true;
object.yFixed = true;
object.options.fixed.x = true;
object.options.fixed.y = true;
this.drag.selection.push(s); this.drag.selection.push(s);
} }
@ -239,12 +239,12 @@ class InteractionHandler {
// update position of all selected nodes // update position of all selected nodes
selection.forEach((selection) => { selection.forEach((selection) => {
var node = selection.node; var node = selection.node;
if (!selection.xFixed) {
// only move the node if it was not fixed initially
if (selection.xFixed === false) {
node.x = this.canvas._XconvertDOMtoCanvas(this.canvas._XconvertCanvasToDOM(selection.x) + deltaX); node.x = this.canvas._XconvertDOMtoCanvas(this.canvas._XconvertCanvasToDOM(selection.x) + deltaX);
} }
if (!selection.yFixed) {
// only move the node if it was not fixed initially
if (selection.yFixed === false) {
node.y = this.canvas._YconvertDOMtoCanvas(this.canvas._YconvertCanvasToDOM(selection.y) + deltaY); node.y = this.canvas._YconvertDOMtoCanvas(this.canvas._YconvertCanvasToDOM(selection.y) + deltaY);
} }
}); });
@ -281,8 +281,8 @@ class InteractionHandler {
if (selection && selection.length) { if (selection && selection.length) {
selection.forEach(function (s) { selection.forEach(function (s) {
// restore original xFixed and yFixed // restore original xFixed and yFixed
s.node.xFixed = s.xFixed;
s.node.yFixed = s.yFixed;
s.node.options.fixed.x = s.xFixed;
s.node.options.fixed.y = s.yFixed;
}); });
this.body.emitter.emit("startSimulation"); this.body.emitter.emit("startSimulation");
} }

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

@ -14,11 +14,11 @@ class LayoutEngine {
positionInitially(nodesArray) { positionInitially(nodesArray) {
for (var i = 0; i < nodesArray.length; i++) { for (var i = 0; i < nodesArray.length; i++) {
let node = nodesArray[i]; let node = nodesArray[i];
if ((node.xFixed == false || node.yFixed == false) && (node.x === null || node.y === null)) {
if ((!node.isFixed()) && (node.x === null || node.y === null)) {
var radius = 10 * 0.1*nodesArray.length + 10; var radius = 10 * 0.1*nodesArray.length + 10;
var angle = 2 * Math.PI * Math.random(); var angle = 2 * Math.PI * Math.random();
if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
if (node.options.fixed.x == false) {node.x = radius * Math.cos(angle);}
if (node.options.fixed.x == false) {node.y = radius * Math.sin(angle);}
} }
} }
} }

+ 8
- 1
lib/network/modules/NodesHandler.js View File

@ -90,12 +90,19 @@ class NodesHandler {
size: 10, size: 10,
value: 1 value: 1
}; };
util.extend(this.options, this.defaultOptions); util.extend(this.options, this.defaultOptions);
this.setOptions(options);
} }
setOptions(options) { setOptions(options) {
if (options) {
util.selectiveNotDeepExtend(['color'], this.options, options);
if (options.color) {
this.options.color = util.parseColor(options.color);
}
}
} }
/** /**

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

@ -20,6 +20,7 @@ class PhysicsEngine {
this.simulationInterval = 1000 / 60; this.simulationInterval = 1000 / 60;
this.requiresTimeout = true; this.requiresTimeout = true;
this.previousStates = {}; this.previousStates = {};
this.freezeCache = {};
this.renderTimer == undefined; this.renderTimer == undefined;
this.stabilized = false; this.stabilized = false;
@ -207,7 +208,7 @@ class PhysicsEngine {
* *
* @private * @private
*/ */
_updatePhysicsIndices() {
updatePhysicsIndices() {
this.physicsBody.forces = {}; this.physicsBody.forces = {};
this.physicsBody.physicsNodeIndices = []; this.physicsBody.physicsNodeIndices = [];
this.physicsBody.physicsEdgeIndices = []; this.physicsBody.physicsEdgeIndices = [];
@ -307,7 +308,7 @@ class PhysicsEngine {
// store the state so we can revert // store the state so we can revert
this.previousStates[nodeId] = {x:node.x, y:node.y, vx:velocities[nodeId].x, vy:velocities[nodeId].y}; this.previousStates[nodeId] = {x:node.x, y:node.y, vx:velocities[nodeId].x, vy:velocities[nodeId].y};
if (!node.xFixed) {
if (node.options.fixed.x === false) {
let dx = this.modelOptions.damping * velocities[nodeId].x; // damping force let dx = this.modelOptions.damping * velocities[nodeId].x; // damping force
let ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration let ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration
velocities[nodeId].x += ax * timestep; // velocity velocities[nodeId].x += ax * timestep; // velocity
@ -319,7 +320,7 @@ class PhysicsEngine {
velocities[nodeId].x = 0; velocities[nodeId].x = 0;
} }
if (!node.yFixed) {
if (node.options.fixed.y === false) {
let dy = this.modelOptions.damping * velocities[nodeId].y; // damping force let dy = this.modelOptions.damping * velocities[nodeId].y; // damping force
let ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration let ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration
velocities[nodeId].y += ay * timestep; // velocity velocities[nodeId].y += ay * timestep; // velocity
@ -359,11 +360,10 @@ class PhysicsEngine {
var nodes = this.body.nodes; var nodes = this.body.nodes;
for (var id in nodes) { for (var id in nodes) {
if (nodes.hasOwnProperty(id)) { if (nodes.hasOwnProperty(id)) {
if (nodes[id].x != null && nodes[id].y != null) {
nodes[id].fixedData.x = nodes[id].xFixed;
nodes[id].fixedData.y = nodes[id].yFixed;
nodes[id].xFixed = true;
nodes[id].yFixed = true;
if (nodes[id].x && nodes[id].y) {
this.freezeCache[id] = {x:nodes[id].options.fixed.x,y:nodes[id].options.fixed.y};
nodes[id].options.fixed.x = true;
nodes[id].options.fixed.y = true;
} }
} }
} }
@ -378,12 +378,13 @@ class PhysicsEngine {
var nodes = this.body.nodes; var nodes = this.body.nodes;
for (var id in nodes) { for (var id in nodes) {
if (nodes.hasOwnProperty(id)) { if (nodes.hasOwnProperty(id)) {
if (nodes[id].fixedData.x != null) {
nodes[id].xFixed = nodes[id].fixedData.x;
nodes[id].yFixed = nodes[id].fixedData.y;
if (this.freezeCache[id] !== undefined) {
nodes[id].options.fixed.x = this.freezeCache[id].x;
nodes[id].options.fixed.y = this.freezeCache[id].y;
} }
} }
} }
this.freezeCache = {};
} }
/** /**

+ 24
- 13
lib/network/modules/components/Edge.js View File

@ -25,7 +25,6 @@ class Edge {
if (body === undefined) { if (body === undefined) {
throw "No body provided"; throw "No body provided";
} }
this.options = util.bridgeObject(globalOptions); this.options = util.bridgeObject(globalOptions);
this.body = body; this.body = body;
@ -49,7 +48,7 @@ class Edge {
this.labelModule = new Label(this.body, this.options); this.labelModule = new Label(this.body, this.options);
this.setOptions(options, true);
this.setOptions(options);
this.controlNodesEnabled = false; this.controlNodesEnabled = false;
this.controlNodes = {from: undefined, to: undefined, positions: {}}; this.controlNodes = {from: undefined, to: undefined, positions: {}};
@ -62,7 +61,7 @@ class Edge {
* @param {Object} options an object with options * @param {Object} options an object with options
* @param doNotEmit * @param doNotEmit
*/ */
setOptions(options, doNotEmit = false) {
setOptions(options) {
if (!options) { if (!options) {
return; return;
} }
@ -138,26 +137,38 @@ class Edge {
this.updateEdgeType(); this.updateEdgeType();
this.edgeType.setOptions(this.options);
return this.edgeType.setOptions(this.options);
} }
updateEdgeType() { updateEdgeType() {
let dataChanged = false;
let changeInType = true;
if (this.edgeType !== undefined) { if (this.edgeType !== undefined) {
this.edgeType.cleanup();
if (this.edgeType instanceof BezierEdgeDynamic && this.options.smooth.enabled == true && this.options.smooth.dynamic == true) {changeInType = false;}
if (this.edgeType instanceof BezierEdgeStatic && this.options.smooth.enabled == true && this.options.smooth.dynamic == false){changeInType = false;}
if (this.edgeType instanceof StraightEdge && this.options.smooth.enabled == false) {changeInType = false;}
if (changeInType == true) {
dataChanged = this.edgeType.cleanup();
}
} }
if (this.options.smooth.enabled === true) {
if (this.options.smooth.dynamic === true) {
this.edgeType = new BezierEdgeDynamic(this.options, this.body, this.labelModule);
if (changeInType === true) {
if (this.options.smooth.enabled === true) {
if (this.options.smooth.dynamic === true) {
dataChanged = true;
this.edgeType = new BezierEdgeDynamic(this.options, this.body, this.labelModule);
}
else {
this.edgeType = new BezierEdgeStatic(this.options, this.body, this.labelModule);
}
} }
else { else {
this.edgeType = new BezierEdgeStatic(this.options, this.body, this.labelModule);
this.edgeType = new StraightEdge(this.options, this.body, this.labelModule);
} }
} }
else {
this.edgeType = new StraightEdge(this.options, this.body, this.labelModule);
}
return dataChanged;
} }

+ 26
- 43
lib/network/modules/components/Node.js View File

@ -44,33 +44,25 @@ import TriangleDown from './nodes/shapes/triangleDown'
class Node { class Node {
constructor(options, body, imagelist, grouplist, globalOptions) { constructor(options, body, imagelist, grouplist, globalOptions) {
this.options = util.bridgeObject(globalOptions); this.options = util.bridgeObject(globalOptions);
this.body = body; this.body = body;
this.selected = false;
this.hover = false;
this.edges = []; // all edges connected to this node this.edges = []; // all edges connected to this node
// set defaults for the options // set defaults for the options
this.id = undefined; this.id = undefined;
this.allowedToMoveX = false;
this.allowedToMoveY = false;
this.xFixed = false;
this.yFixed = false;
this.boundingBox = {top: 0, left: 0, right: 0, bottom: 0}; this.boundingBox = {top: 0, left: 0, right: 0, bottom: 0};
this.imagelist = imagelist; this.imagelist = imagelist;
this.grouplist = grouplist; this.grouplist = grouplist;
// physics options
// state options
this.x = null; this.x = null;
this.y = null; this.y = null;
this.predefinedPosition = false; // used to check if initial zoomExtent should just take the range or approximate this.predefinedPosition = false; // used to check if initial zoomExtent should just take the range or approximate
this.selected = false;
this.hover = false;
this.fixedData = {x: null, y: null};
this.labelModule = new Label(this.body, this.options); this.labelModule = new Label(this.body, this.options);
this.setOptions(options); this.setOptions(options);
} }
@ -117,29 +109,26 @@ class Node {
} }
var fields = [ var fields = [
'id',
'borderWidth', 'borderWidth',
'borderWidthSelected', 'borderWidthSelected',
'shape',
'image',
'brokenImage', 'brokenImage',
'size',
'label',
'customScalingFunction', 'customScalingFunction',
'icon',
'value',
'font',
'hidden', 'hidden',
'physics'
'icon',
'id',
'image',
'label',
'physics',
'shape',
'size',
'value'
]; ];
util.selectiveDeepExtend(fields, this.options, options); util.selectiveDeepExtend(fields, this.options, options);
// basic options // basic options
if (options.id !== undefined) { if (options.id !== undefined) {
this.id = options.id; this.id = options.id;
} }
if (options.title !== undefined) {
this.title = options.title;
}
if (options.x !== undefined) { if (options.x !== undefined) {
this.x = options.x; this.x = options.x;
this.predefinedPosition = true; this.predefinedPosition = true;
@ -156,10 +145,6 @@ class Node {
this.preassignedLevel = true; this.preassignedLevel = true;
} }
if (options.triggerFunction !== undefined) {
this.triggerFunction = options.triggerFunction;
}
if (this.id === undefined) { if (this.id === undefined) {
throw "Node must have an id"; throw "Node must have an id";
} }
@ -185,21 +170,19 @@ class Node {
} }
} }
if (options.allowedToMoveX !== undefined) {
this.xFixed = !options.allowedToMoveX;
this.allowedToMoveX = options.allowedToMoveX;
}
else if (options.x !== undefined && this.allowedToMoveX == false) {
this.xFixed = true;
}
if (options.allowedToMoveY !== undefined) {
this.yFixed = !options.allowedToMoveY;
this.allowedToMoveY = options.allowedToMoveY;
}
else if (options.y !== undefined && this.allowedToMoveY == false) {
this.yFixed = true;
if (options.fixed !== undefined) {
if (typeof options.fixed == 'boolean') {
this.options.fixed.x = true;
this.options.fixed.y = true;
}
else {
if (options.fixed.x !== undefined && typeof options.fixed.x == 'boolean') {
this.options.fixed.x = options.fixed.x;
}
if (options.fixed.y !== undefined && typeof options.fixed.y == 'boolean') {
this.options.fixed.y = options.fixed.y;
}
}
} }
// choose draw method depending on the shape // choose draw method depending on the shape
@ -249,7 +232,7 @@ class Node {
break; break;
} }
this.labelModule.setOptions(this.options);
this.labelModule.setOptions(this.options, options);
// reset the size of the node, this can be changed // reset the size of the node, this can be changed
this._reset(); this._reset();

+ 6
- 13
lib/network/modules/components/edges/bezierEdgeDynamic.js View File

@ -6,10 +6,8 @@ import BezierBaseEdge from './util/bezierBaseEdge'
class BezierEdgeDynamic extends BezierBaseEdge { class BezierEdgeDynamic extends BezierBaseEdge {
constructor(options, body, labelModule) { constructor(options, body, labelModule) {
this.initializing = true;
this.via = undefined; this.via = undefined;
super(options, body, labelModule);
this.initializing = false;
super(options, body, labelModule); // --> this calls the setOptions below
} }
setOptions(options) { setOptions(options) {
@ -17,27 +15,27 @@ class BezierEdgeDynamic extends BezierBaseEdge {
this.from = this.body.nodes[this.options.from]; this.from = this.body.nodes[this.options.from];
this.to = this.body.nodes[this.options.to]; this.to = this.body.nodes[this.options.to];
this.id = this.options.id; this.id = this.options.id;
this.setupSupportNode(this.initializing);
this.setupSupportNode();
} }
cleanup() { cleanup() {
if (this.via !== undefined) { if (this.via !== undefined) {
delete this.body.nodes[this.via.id]; delete this.body.nodes[this.via.id];
this.via = undefined; this.via = undefined;
this.body.emitter.emit("_dataChanged");
return true;
} }
return false;
} }
/** /**
* Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but * 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. * are used for the force calculation.
* *
* The changed data is not called, if needed, it is returned by the main edge constructor.
* @private * @private
*/ */
setupSupportNode(doNotEmit = false) {
var changedData = false;
setupSupportNode() {
if (this.via === undefined) { if (this.via === undefined) {
changedData = true;
var nodeId = "edgeId:" + this.id; var nodeId = "edgeId:" + this.id;
var node = this.body.functions.createNode({ var node = this.body.functions.createNode({
id: nodeId, id: nodeId,
@ -52,11 +50,6 @@ class BezierEdgeDynamic extends BezierBaseEdge {
this.via.parentEdgeId = this.id; this.via.parentEdgeId = this.id;
this.positionBezierNode(); this.positionBezierNode();
} }
// node has been added or deleted
if (changedData === true && doNotEmit === false) {
this.body.emitter.emit("_dataChanged");
}
} }
positionBezierNode() { positionBezierNode() {

+ 3
- 1
lib/network/modules/components/edges/bezierEdgeStatic.js View File

@ -9,7 +9,9 @@ class BezierEdgeStatic extends BezierBaseEdge {
super(options, body, labelModule); super(options, body, labelModule);
} }
cleanup() {}
cleanup() {
return false;
}
/** /**
* Draw a line between two nodes * Draw a line between two nodes
* @param {CanvasRenderingContext2D} ctx * @param {CanvasRenderingContext2D} ctx

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

@ -9,7 +9,9 @@ class StraightEdge extends BaseEdge {
super(options, body, labelModule); super(options, body, labelModule);
} }
cleanup() {}
cleanup() {
return false;
}
/** /**
* Draw a line between two nodes * Draw a line between two nodes
* @param {CanvasRenderingContext2D} ctx * @param {CanvasRenderingContext2D} ctx
@ -47,8 +49,6 @@ class StraightEdge extends BaseEdge {
node2 = this.to; node2 = this.to;
} }
let angle = Math.atan2((node1.y - node2.y), (node1.x - node2.x)); let angle = Math.atan2((node1.y - node2.y), (node1.x - node2.x));
let dx = (node1.x - node2.x); let dx = (node1.x - node2.x);
let dy = (node1.y - node2.y); let dy = (node1.y - node2.y);

+ 44
- 20
lib/network/modules/components/unified/label.js View File

@ -7,8 +7,20 @@ let util = require('../../../../util');
class Label { class Label {
constructor(body,options) { constructor(body,options) {
this.body = body; this.body = body;
this.setOptions(options);
this.fontOptions = {};
this.defaultOptions = {
color: '#343434',
size: 14, // px
face: 'arial',
background: 'none',
stroke: 0, // px
strokeColor: 'white',
align:'horizontal'
}
util.extend(this.fontOptions, this.defaultOptions);
this.setOptions(options);
this.size = {top: 0, left: 0, width: 0, height: 0, yLine: 0}; // could be cached this.size = {top: 0, left: 0, width: 0, height: 0, yLine: 0}; // could be cached
} }
@ -17,6 +29,18 @@ class Label {
if (options.label !== undefined) { if (options.label !== undefined) {
this.labelDirty = true; this.labelDirty = true;
} }
if (options.font) {
if (typeof options.font === 'string') {
let optionsArray = options.font.split(" ");
this.fontOptions.size = optionsArray[0].replace("px",'');
this.fontOptions.face = optionsArray[1];
this.fontOptions.color = optionsArray[2];
}
else if (typeof options.font == 'object') {
util.extend(this.fontOptions, options.font);
}
this.fontOptions.size = Number(this.fontOptions.size);
}
} }
@ -34,7 +58,7 @@ class Label {
return; return;
// check if we have to render the label // check if we have to render the label
let viewFontSize = Number(this.options.font.size) * this.body.view.scale;
let viewFontSize = this.fontOptions.size * this.body.view.scale;
if (this.options.label && viewFontSize < this.options.scaling.label.drawThreshold - 1) if (this.options.label && viewFontSize < this.options.scaling.label.drawThreshold - 1)
return; return;
@ -53,12 +77,12 @@ class Label {
* @private * @private
*/ */
_drawBackground(ctx) { _drawBackground(ctx) {
if (this.options.font.background !== undefined && this.options.font.background !== "none") {
ctx.fillStyle = this.options.font.background;
if (this.fontOptions.background !== undefined && this.fontOptions.background !== "none") {
ctx.fillStyle = this.fontOptions.background;
let lineMargin = 2; let lineMargin = 2;
switch (this.options.font.align) {
switch (this.fontOptions.align) {
case 'middle': case 'middle':
ctx.fillRect(-this.size.width * 0.5, -this.size.height * 0.5, this.size.width, this.size.height); ctx.fillRect(-this.size.width * 0.5, -this.size.height * 0.5, this.size.width, this.size.height);
break; break;
@ -84,7 +108,7 @@ class Label {
* @private * @private
*/ */
_drawText(ctx, selected, x, y, baseline = 'middle') { _drawText(ctx, selected, x, y, baseline = 'middle') {
let fontSize = Number(this.options.font.size);
let fontSize = this.fontOptions.size;
let viewFontSize = fontSize * this.body.view.scale; let viewFontSize = fontSize * this.body.view.scale;
// this ensures that there will not be HUGE letters on screen by setting an upper limit on the visible text size (regardless of zoomLevel) // this ensures that there will not be HUGE letters on screen by setting an upper limit on the visible text size (regardless of zoomLevel)
if (viewFontSize >= this.options.scaling.label.maxVisible) { if (viewFontSize >= this.options.scaling.label.maxVisible) {
@ -96,20 +120,20 @@ class Label {
[x, yLine] = this._setAlignment(ctx, x, yLine, baseline); [x, yLine] = this._setAlignment(ctx, x, yLine, baseline);
// configure context for drawing the text // configure context for drawing the text
ctx.font = (selected ? 'bold ' : '') + fontSize + "px " + this.options.font.face;
ctx.font = (selected ? 'bold ' : '') + fontSize + "px " + this.fontOptions.face;
ctx.fillStyle = fontColor; ctx.fillStyle = fontColor;
ctx.textAlign = 'center'; ctx.textAlign = 'center';
// set the strokeWidth // set the strokeWidth
if (this.options.font.stroke > 0) {
ctx.lineWidth = this.options.font.stroke;
if (this.fontOptions.stroke > 0) {
ctx.lineWidth = this.fontOptions.stroke;
ctx.strokeStyle = strokeColor; ctx.strokeStyle = strokeColor;
ctx.lineJoin = 'round'; ctx.lineJoin = 'round';
} }
// draw the text // draw the text
for (let i = 0; i < this.lineCount; i++) { for (let i = 0; i < this.lineCount; i++) {
if (this.options.font.stroke > 0) {
if (this.fontOptions.stroke > 0) {
ctx.strokeText(this.lines[i], x, yLine); ctx.strokeText(this.lines[i], x, yLine);
} }
ctx.fillText(this.lines[i], x, yLine); ctx.fillText(this.lines[i], x, yLine);
@ -120,16 +144,16 @@ class Label {
_setAlignment(ctx, x, yLine, baseline) { _setAlignment(ctx, x, yLine, baseline) {
// check for label alignment (for edges) // check for label alignment (for edges)
// TODO: make alignment for nodes // TODO: make alignment for nodes
if (this.options.font.align !== 'horizontal') {
if (this.fontOptions.align !== 'horizontal') {
x = 0; x = 0;
yLine = 0; yLine = 0;
let lineMargin = 2; let lineMargin = 2;
if (this.options.font.align === 'top') {
if (this.fontOptions.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.font.align === 'bottom') {
else if (this.fontOptions.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
} }
@ -153,8 +177,8 @@ class Label {
* @private * @private
*/ */
_getColor(viewFontSize) { _getColor(viewFontSize) {
let fontColor = this.options.font.color || '#000000';
let strokeColor = this.options.font.strokeColor || '#ffffff';
let fontColor = this.fontOptions.color || '#000000';
let strokeColor = this.fontOptions.strokeColor || '#ffffff';
if (viewFontSize <= this.options.scaling.label.drawThreshold) { if (viewFontSize <= this.options.scaling.label.drawThreshold) {
let opacity = Math.max(0, Math.min(1, 1 - (this.options.scaling.label.drawThreshold - viewFontSize))); let opacity = Math.max(0, Math.min(1, 1 - (this.options.scaling.label.drawThreshold - viewFontSize)));
fontColor = util.overrideOpacity(fontColor, opacity); fontColor = util.overrideOpacity(fontColor, opacity);
@ -173,7 +197,7 @@ class Label {
getTextSize(ctx, selected = false) { getTextSize(ctx, selected = false) {
let size = { let size = {
width: this._processLabel(ctx,selected), width: this._processLabel(ctx,selected),
height: this.options.font.size * this.lineCount
height: this.fontOptions.size * this.lineCount
}; };
return size; return size;
} }
@ -191,12 +215,12 @@ class Label {
if (this.labelDirty === true) { if (this.labelDirty === true) {
this.size.width = this._processLabel(ctx,selected); this.size.width = this._processLabel(ctx,selected);
} }
this.size.height = this.options.font.size * this.lineCount;
this.size.height = this.fontOptions.size * this.lineCount;
this.size.left = x - this.size.width * 0.5; this.size.left = x - this.size.width * 0.5;
this.size.top = y - this.size.height * 0.5; this.size.top = y - this.size.height * 0.5;
this.size.yLine = y + (1 - this.lineCount) * 0.5 * this.options.font.size;
this.size.yLine = y + (1 - this.lineCount) * 0.5 * this.fontOptions.size;
if (baseline == "hanging") { if (baseline == "hanging") {
this.size.top += 0.5 * this.options.font.size;
this.size.top += 0.5 * this.fontOptions.size;
this.size.top += 4; // distance from node, required because we use hanging. Hanging has less difference between browsers this.size.top += 4; // distance from node, required because we use hanging. Hanging has less difference between browsers
this.size.yLine += 4; // distance from node this.size.yLine += 4; // distance from node
} }
@ -219,7 +243,7 @@ class Label {
if (this.options.label !== undefined) { if (this.options.label !== undefined) {
lines = String(this.options.label).split('\n'); lines = String(this.options.label).split('\n');
lineCount = lines.length; lineCount = lines.length;
ctx.font = (selected ? 'bold ' : '') + this.options.font.size + "px " + this.options.font.face;
ctx.font = (selected ? 'bold ' : '') + this.fontOptions.size + "px " + this.fontOptions.face;
width = ctx.measureText(lines[0]).width; width = ctx.measureText(lines[0]).width;
for (let i = 1; i < lineCount; i++) { for (let i = 1; i < lineCount; i++) {
let lineWidth = ctx.measureText(lines[i]).width; let lineWidth = ctx.measureText(lines[i]).width;

+ 17
- 1
lib/util.js View File

@ -105,6 +105,22 @@ exports.randomUUID = function() {
); );
}; };
/**
* assign all keys of an object that are not nested objects to a certain value (used for color objects).
* @param obj
* @param value
*/
exports.assignAllKeys = function (obj, value) {
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
if (typeof obj[prop] !== 'object') {
obj[prop] = value;
}
}
}
}
/** /**
* Extend object a with the properties of object b or a series of objects * Extend object a with the properties of object b or a series of objects
* Only properties with defined values are copied * Only properties with defined values are copied
@ -113,7 +129,7 @@ exports.randomUUID = function() {
* @return {Object} a * @return {Object} a
*/ */
exports.extend = function (a, b) { exports.extend = function (a, b) {
for (var i = 1, len = arguments.length; i < len; i++) {
for (var i = 1; i < arguments.length; i++) {
var other = arguments[i]; var other = arguments[i];
for (var prop in other) { for (var prop in other) {
if (other.hasOwnProperty(prop)) { if (other.hasOwnProperty(prop)) {

Loading…
Cancel
Save