Browse Source

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

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