Browse Source

fixed all trello issues and added snazzy option lookup to validator

flowchartTest
Alex de Mulder 9 years ago
parent
commit
c9a1a88f41
19 changed files with 295 additions and 157 deletions
  1. +132
    -55
      dist/vis.js
  2. +2
    -2
      docs/network/edges.html
  3. +21
    -23
      docs/network/manipulation.html
  4. +2
    -2
      docs/network/nodes.html
  5. +6
    -4
      docs/network/view.html
  6. +1
    -1
      examples/network/01_basic_usage.html
  7. +0
    -1
      examples/network/27_world_cup_network.html
  8. +3
    -13
      examples/network/35_label_stroke.html
  9. +21
    -0
      lib/network/Network.js
  10. +1
    -1
      lib/network/modules/EdgesHandler.js
  11. +15
    -15
      lib/network/modules/ManipulationSystem.js
  12. +1
    -1
      lib/network/modules/NodesHandler.js
  13. +13
    -11
      lib/network/modules/PhysicsEngine.js
  14. +50
    -17
      lib/network/modules/Validator.js
  15. +1
    -1
      lib/network/modules/View.js
  16. +8
    -6
      lib/network/modules/components/AllOptions.js
  17. +0
    -1
      lib/network/modules/components/Node.js
  18. +3
    -3
      lib/network/modules/components/shared/Label.js
  19. +15
    -0
      lib/util.js

+ 132
- 55
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-04-30
* @date 2015-05-01
* *
* @license * @license
* Copyright (C) 2011-2014 Almende B.V, http://almende.com * Copyright (C) 2011-2014 Almende B.V, http://almende.com
@ -676,6 +676,21 @@ return /******/ (function(modules) { // webpackBootstrap
return newArr; return newArr;
}; };
/**
* Used to extend an array and copy it. This is used to propagate paths recursively.
*
* @param arr
* @param newValue
* @returns {Array}
*/
exports.copyArray = function (arr) {
var newArr = [];
for (var i = 0; i < arr.length; i++) {
newArr.push(arr[i]);
}
return newArr;
};
/** /**
* Retrieve the absolute left value of a DOM element * Retrieve the absolute left value of a DOM element
* @param {Element} elem A dom element, for example a div * @param {Element} elem A dom element, for example a div
@ -15827,6 +15842,8 @@ return /******/ (function(modules) { // webpackBootstrap
console.log('%cErrors have been found in the supplied options object. None of the options will be used.', _Validator.printStyle); console.log('%cErrors have been found in the supplied options object. None of the options will be used.', _Validator.printStyle);
} }
//this.placeConvenienceOptions(options);
// the hierarchical system can adapt the edges and the physics to it's own options because not all combinations work with the hierarichical system. // the hierarchical system can adapt the edges and the physics to it's own options because not all combinations work with the hierarichical system.
options = this.layoutEngine.setOptions(options.layout, options); options = this.layoutEngine.setOptions(options.layout, options);
@ -15884,6 +15901,24 @@ return /******/ (function(modules) { // webpackBootstrap
} }
}; };
//
///**
// *
// */
//Network.prototype.placeConvenienceOptions = function (options) {
// if (options.locale !== undefined) {
// if (options.manipulation === undefined) {
// options.manipulation = {enabled:false, locale:options.locale};
// }
// else if (typeof options.manipulation === 'boolean') {
// options.manipulation = {enabled: options.manipulation, locale:options.locale};
// }
// else {
// options.manipulation.locale = options.locale;
// }
// }
//}
/** /**
* Update the this.body.nodeIndices with the most recent node index list * Update the this.body.nodeIndices with the most recent node index list
* @private * @private
@ -19467,7 +19502,7 @@ return /******/ (function(modules) { // webpackBootstrap
size: 14, // px size: 14, // px
face: 'arial', face: 'arial',
background: 'none', background: 'none',
stroke: 0, // px
strokeWidth: 0, // px
strokeColor: '#ffffff', strokeColor: '#ffffff',
align: 'horizontal' align: 'horizontal'
}, },
@ -19933,7 +19968,7 @@ return /******/ (function(modules) { // webpackBootstrap
size: 14, // px size: 14, // px
face: 'arial', face: 'arial',
background: 'none', background: 'none',
stroke: 1, // px
strokeWidth: 0, // px
strokeColor: '#ffffff', strokeColor: '#ffffff',
align: 'horizontal' align: 'horizontal'
}, },
@ -20418,17 +20453,18 @@ return /******/ (function(modules) { // webpackBootstrap
}, { }, {
key: 'setOptions', key: 'setOptions',
value: function setOptions(options) { value: function setOptions(options) {
if (options === false) {
this.physicsEnabled = false;
this.stopSimulation();
} else {
this.physicsEnabled = true;
if (options !== undefined) {
if (options !== undefined) {
if (options === false) {
this.physicsEnabled = false;
this.stopSimulation();
} else {
this.physicsEnabled = true;
util.selectiveNotDeepExtend(['stabilization'], this.options, options); util.selectiveNotDeepExtend(['stabilization'], this.options, options);
util.mergeOptions(this.options, options, 'stabilization'); util.mergeOptions(this.options, options, 'stabilization');
} }
this.init();
} }
this.init();
} }
}, { }, {
key: 'init', key: 'init',
@ -20461,12 +20497,12 @@ return /******/ (function(modules) { // webpackBootstrap
} else { } else {
this.stabilized = false; this.stabilized = false;
this.ready = true; this.ready = true;
this.body.emitter.emit('fit', { duration: 0 }, true);
this.body.emitter.emit('fit', {}, true);
this.startSimulation(); this.startSimulation();
} }
} else { } else {
this.ready = true; this.ready = true;
this.body.emitter.emit('_redraw');
this.body.emitter.emit('fit');
} }
} }
}, { }, {
@ -20835,7 +20871,7 @@ return /******/ (function(modules) { // webpackBootstrap
value: function _finalizeStabilization() { value: function _finalizeStabilization() {
this.body.emitter.emit('_allowRedrawRequests'); this.body.emitter.emit('_allowRedrawRequests');
if (this.options.stabilization.fit === true) { if (this.options.stabilization.fit === true) {
this.body.emitter.emit('fit', { duration: 0 });
this.body.emitter.emit('fit');
} }
if (this.options.stabilization.onlyDynamicEdges === true) { if (this.options.stabilization.onlyDynamicEdges === true) {
@ -22382,7 +22418,7 @@ return /******/ (function(modules) { // webpackBootstrap
} }
var center = this._findCenter(range); var center = this._findCenter(range);
var animationOptions = { position: center, scale: zoomLevel, animation: options };
var animationOptions = { position: center, scale: zoomLevel, animation: options.animation };
this.moveTo(animationOptions); this.moveTo(animationOptions);
} }
}, { }, {
@ -24094,6 +24130,7 @@ return /******/ (function(modules) { // webpackBootstrap
} }
} }
this.body.emitter.emit('_resetHierarchicalLayout');
// because the hierarchical system needs it's own physics and smooth curve settings, we adapt the other options if needed. // because the hierarchical system needs it's own physics and smooth curve settings, we adapt the other options if needed.
return this.adaptAllOptions(allOptions); return this.adaptAllOptions(allOptions);
} else { } else {
@ -24774,7 +24811,7 @@ return /******/ (function(modules) { // webpackBootstrap
this.manipulationDOM = {}; this.manipulationDOM = {};
this._createBackButton(locale); this._createBackButton(locale);
this._createSeperator(); this._createSeperator();
this._createDescription(locale.addDescription);
this._createDescription(locale.addDescription || this.options.locales.en.addDescription);
// bind the close button // bind the close button
this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this));
@ -24821,7 +24858,7 @@ return /******/ (function(modules) { // webpackBootstrap
throw new Error('The function for edit does not support two arguments (data, callback)'); throw new Error('The function for edit does not support two arguments (data, callback)');
} }
} else { } else {
alert(this.options.locales[this.options.locale].editClusterError);
alert(this.options.locales[this.options.locale].editClusterError || this.options.locales.en.editClusterError);
} }
} else { } else {
throw new Error('No function has been configured to handle the editing of nodes.'); throw new Error('No function has been configured to handle the editing of nodes.');
@ -24850,7 +24887,7 @@ return /******/ (function(modules) { // webpackBootstrap
this.manipulationDOM = {}; this.manipulationDOM = {};
this._createBackButton(locale); this._createBackButton(locale);
this._createSeperator(); this._createSeperator();
this._createDescription(locale.edgeDescription);
this._createDescription(locale.edgeDescription || this.options.locales.en.edgeDescription);
// bind the close button // bind the close button
this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this));
@ -24888,7 +24925,7 @@ return /******/ (function(modules) { // webpackBootstrap
this.manipulationDOM = {}; this.manipulationDOM = {};
this._createBackButton(locale); this._createBackButton(locale);
this._createSeperator(); this._createSeperator();
this._createDescription(locale.editEdgeDescription);
this._createDescription(locale.editEdgeDescription || this.options.locales.en.editEdgeDescription);
// bind the close button // bind the close button
this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this));
@ -24960,7 +24997,7 @@ return /******/ (function(modules) { // webpackBootstrap
if (selectedNodes.length > 0) { if (selectedNodes.length > 0) {
for (var i = 0; i < selectedNodes.length; i++) { for (var i = 0; i < selectedNodes.length; i++) {
if (this.body.nodes[selectedNodes[i]].isCluster === true) { if (this.body.nodes[selectedNodes[i]].isCluster === true) {
alert(this.options.locales[this.options.locale].deleteClusterError);
alert(this.options.locales[this.options.locale].deleteClusterError || this.options.locales.en.deleteClusterError);
return; return;
} }
} }
@ -25100,7 +25137,7 @@ return /******/ (function(modules) { // webpackBootstrap
// create the contents for the editMode button // create the contents for the editMode button
var locale = this.options.locales[this.options.locale]; var locale = this.options.locales[this.options.locale];
var button = this._createButton('editMode', 'vis-button vis-edit vis-edit-mode', locale.edit);
var button = this._createButton('editMode', 'vis-button vis-edit vis-edit-mode', locale.edit || this.options.locales.en.edit);
this.editModeDiv.appendChild(button); this.editModeDiv.appendChild(button);
// bind a hammer listener to the button, calling the function toggleEditMode. // bind a hammer listener to the button, calling the function toggleEditMode.
@ -25201,42 +25238,42 @@ return /******/ (function(modules) { // webpackBootstrap
// ---------------------- DOM functions for buttons --------------------------// // ---------------------- DOM functions for buttons --------------------------//
value: function _createAddNodeButton(locale) { value: function _createAddNodeButton(locale) {
var button = this._createButton('addNode', 'vis-button vis-add', locale.addNode);
var button = this._createButton('addNode', 'vis-button vis-add', locale.addNode || this.options.locales.en.addNode);
this.manipulationDiv.appendChild(button); this.manipulationDiv.appendChild(button);
this._bindHammerToDiv(button, this.addNodeMode.bind(this)); this._bindHammerToDiv(button, this.addNodeMode.bind(this));
} }
}, { }, {
key: '_createAddEdgeButton', key: '_createAddEdgeButton',
value: function _createAddEdgeButton(locale) { value: function _createAddEdgeButton(locale) {
var button = this._createButton('addEdge', 'vis-button vis-connect', locale.addEdge);
var button = this._createButton('addEdge', 'vis-button vis-connect', locale.addEdge || this.options.locales.en.addEdge);
this.manipulationDiv.appendChild(button); this.manipulationDiv.appendChild(button);
this._bindHammerToDiv(button, this.addEdgeMode.bind(this)); this._bindHammerToDiv(button, this.addEdgeMode.bind(this));
} }
}, { }, {
key: '_createEditNodeButton', key: '_createEditNodeButton',
value: function _createEditNodeButton(locale) { value: function _createEditNodeButton(locale) {
var button = this._createButton('editNodeMode', 'vis-button vis-edit', locale.editNodeMode);
var button = this._createButton('editNodeMode', 'vis-button vis-edit', locale.editNode || this.options.locales.en.editNode);
this.manipulationDiv.appendChild(button); this.manipulationDiv.appendChild(button);
this._bindHammerToDiv(button, this.editNodeMode.bind(this)); this._bindHammerToDiv(button, this.editNodeMode.bind(this));
} }
}, { }, {
key: '_createEditEdgeButton', key: '_createEditEdgeButton',
value: function _createEditEdgeButton(locale) { value: function _createEditEdgeButton(locale) {
var button = this._createButton('editEdge', 'vis-button vis-edit', locale.editEdge);
var button = this._createButton('editEdge', 'vis-button vis-edit', locale.editEdge || this.options.locales.en.editEdge);
this.manipulationDiv.appendChild(button); this.manipulationDiv.appendChild(button);
this._bindHammerToDiv(button, this.editEdgeMode.bind(this)); this._bindHammerToDiv(button, this.editEdgeMode.bind(this));
} }
}, { }, {
key: '_createDeleteButton', key: '_createDeleteButton',
value: function _createDeleteButton(locale) { value: function _createDeleteButton(locale) {
var button = this._createButton('delete', 'vis-button vis-delete', locale.del);
var button = this._createButton('delete', 'vis-button vis-delete', locale.del || this.options.locales.en.del);
this.manipulationDiv.appendChild(button); this.manipulationDiv.appendChild(button);
this._bindHammerToDiv(button, this.deleteSelected.bind(this)); this._bindHammerToDiv(button, this.deleteSelected.bind(this));
} }
}, { }, {
key: '_createBackButton', key: '_createBackButton',
value: function _createBackButton(locale) { value: function _createBackButton(locale) {
var button = this._createButton('back', 'vis-button vis-back', locale.back);
var button = this._createButton('back', 'vis-button vis-back', locale.back || this.options.locales.en.back);
this.manipulationDiv.appendChild(button); this.manipulationDiv.appendChild(button);
this._bindHammerToDiv(button, this.showManipulatorToolbar.bind(this)); this._bindHammerToDiv(button, this.showManipulatorToolbar.bind(this));
} }
@ -25457,7 +25494,7 @@ return /******/ (function(modules) { // webpackBootstrap
// perform the connection // perform the connection
if (node !== undefined && this.selectedControlNode !== undefined) { if (node !== undefined && this.selectedControlNode !== undefined) {
if (node.isCluster === true) { if (node.isCluster === true) {
alert(this.options.locales[this.options.locale].createEdgeError);
alert(this.options.locales[this.options.locale].createEdgeError || this.options.locales.en.createEdgeError);
} else { } else {
var from = this.body.nodes[this.temporaryIds.nodes[0]]; var from = this.body.nodes[this.temporaryIds.nodes[0]];
if (this.selectedControlNode.id === from.id) { if (this.selectedControlNode.id === from.id) {
@ -25495,7 +25532,7 @@ return /******/ (function(modules) { // webpackBootstrap
if (node !== undefined) { if (node !== undefined) {
if (node.isCluster === true) { if (node.isCluster === true) {
alert(this.options.locales[this.options.locale].createEdgeError);
alert(this.options.locales[this.options.locale].createEdgeError || this.options.locales.en.createEdgeError);
} else { } else {
// create a node the temporary line can look at // create a node the temporary line can look at
var targetNode = this._getNewTargetNode(node.x, node.y); var targetNode = this._getNewTargetNode(node.x, node.y);
@ -25575,7 +25612,7 @@ return /******/ (function(modules) { // webpackBootstrap
// perform the connection // perform the connection
if (node !== undefined) { if (node !== undefined) {
if (node.isCluster === true) { if (node.isCluster === true) {
alert(this.options.locales[this.options.locale].createEdgeError);
alert(this.options.locales[this.options.locale].createEdgeError || this.options.locales.en.createEdgeError);
} else { } else {
if (this.body.nodes[connectFromId] !== undefined && this.body.nodes[node.id] !== undefined) { if (this.body.nodes[connectFromId] !== undefined && this.body.nodes[node.id] !== undefined) {
this._performAddEdge(connectFromId, node.id); this._performAddEdge(connectFromId, node.id);
@ -26307,6 +26344,7 @@ return /******/ (function(modules) { // webpackBootstrap
var util = __webpack_require__(1); var util = __webpack_require__(1);
var errorFound = false; var errorFound = false;
var allOptions = undefined;
var printStyle = 'background: #FFeeee; color: #dd0000'; var printStyle = 'background: #FFeeee; color: #dd0000';
/** /**
* Used to validate options. * Used to validate options.
@ -26328,6 +26366,7 @@ return /******/ (function(modules) { // webpackBootstrap
*/ */
value: function validate(options, referenceOptions, subObject) { value: function validate(options, referenceOptions, subObject) {
errorFound = false; errorFound = false;
allOptions = referenceOptions;
var usedOptions = referenceOptions; var usedOptions = referenceOptions;
if (subObject !== undefined) { if (subObject !== undefined) {
usedOptions = referenceOptions[subObject]; usedOptions = referenceOptions[subObject];
@ -26367,13 +26406,11 @@ return /******/ (function(modules) { // webpackBootstrap
} else if (referenceOptions[option] === undefined && referenceOptions.__any__ !== undefined) { } else if (referenceOptions[option] === undefined && referenceOptions.__any__ !== undefined) {
// __any__ is a wildcard. Any value is accepted and will be further analysed by reference. // __any__ is a wildcard. Any value is accepted and will be further analysed by reference.
if (Validator.getType(options[option]) === 'object') { if (Validator.getType(options[option]) === 'object') {
util.copyAndExtendArray(path, option);
Validator.checkFields(option, options, referenceOptions, '__any__', referenceOptions.__any__.__type__, path); Validator.checkFields(option, options, referenceOptions, '__any__', referenceOptions.__any__.__type__, path);
} }
} else { } else {
// Since all options in the reference are objects, we can check whether they are supposed to be object to look for the __type__ field. // Since all options in the reference are objects, we can check whether they are supposed to be object to look for the __type__ field.
if (referenceOptions[option].__type__ !== undefined) { if (referenceOptions[option].__type__ !== undefined) {
util.copyAndExtendArray(path, option);
// if this should be an object, we check if the correct type has been supplied to account for shorthand options. // if this should be an object, we check if the correct type has been supplied to account for shorthand options.
Validator.checkFields(option, options, referenceOptions, option, referenceOptions[option].__type__, path); Validator.checkFields(option, options, referenceOptions, option, referenceOptions[option].__type__, path);
} else { } else {
@ -26403,9 +26440,11 @@ return /******/ (function(modules) { // webpackBootstrap
console.log('%cInvalid option detected in "' + option + '".' + ' Allowed values are:' + Validator.print(refOptionType) + ' not "' + options[option] + '". ' + Validator.printLocation(path, option), printStyle); console.log('%cInvalid option detected in "' + option + '".' + ' Allowed values are:' + Validator.print(refOptionType) + ' not "' + options[option] + '". ' + Validator.printLocation(path, option), printStyle);
errorFound = true; errorFound = true;
} else if (optionType === 'object') { } else if (optionType === 'object') {
path = util.copyAndExtendArray(path, option);
Validator.parse(options[option], referenceOptions[referenceOption], path); Validator.parse(options[option], referenceOptions[referenceOption], path);
} }
} else if (optionType === 'object') { } else if (optionType === 'object') {
path = util.copyAndExtendArray(path, option);
Validator.parse(options[option], referenceOptions[referenceOption], path); Validator.parse(options[option], referenceOptions[referenceOption], path);
} }
} else { } else {
@ -26458,30 +26497,66 @@ return /******/ (function(modules) { // webpackBootstrap
}, { }, {
key: 'getSuggestion', key: 'getSuggestion',
value: function getSuggestion(option, options, path) { value: function getSuggestion(option, options, path) {
var closestMatch = '';
var min = 1000000000;
var threshold = 8;
for (var op in options) {
var distance = Validator.levenshteinDistance(option, op);
if (min > distance && distance < threshold) {
closestMatch = op;
min = distance;
}
}
var localSearch = Validator.findInOptions(option, options, path, false);
var globalSearch = Validator.findInOptions(option, allOptions, [], true);
if (min < threshold) {
console.log('%cUnknown option detected: "' + option + '". Did you mean "' + closestMatch + '"?' + Validator.printLocation(path, option), printStyle);
var localSearchThreshold = 8;
var globalSearchThreshold = 5;
if (globalSearch.distance <= globalSearchThreshold && localSearch.distance > globalSearch.distance) {
console.log('%cUnknown option detected: "' + option + '" in ' + Validator.printLocation(localSearch.path, option, '') + 'Perhaps it was misplaced? Matching option found at: ' + Validator.printLocation(globalSearch.path, option, ''), printStyle);
} else if (localSearch.distance <= localSearchThreshold) {
console.log('%cUnknown option detected: "' + option + '". Did you mean "' + localSearch.closestMatch + '"?' + Validator.printLocation(localSearch.path, option), printStyle);
} else { } else {
console.log('%cUnknown option detected: "' + option + '". Did you mean one of these: ' + Validator.print(Object.keys(options)) + Validator.printLocation(path, option), printStyle); console.log('%cUnknown option detected: "' + option + '". Did you mean one of these: ' + Validator.print(Object.keys(options)) + Validator.printLocation(path, option), printStyle);
} }
errorFound = true; errorFound = true;
return closestMatch;
}
}, {
key: 'findInOptions',
/**
* traverse the options in search for a match.
* @param option
* @param options
* @param path
* @param recursive
* @returns {{closestMatch: string, path: Array, distance: number}}
*/
value: function findInOptions(option, options, path) {
var recursive = arguments[3] === undefined ? false : arguments[3];
var min = 1000000000;
var closestMatch = '';
var closestMatchPath = [];
for (var op in options) {
var type = Validator.getType(options[op]);
var distance = undefined;
if (type === 'object' && recursive === true) {
var result = Validator.findInOptions(option, options[op], util.copyAndExtendArray(path, op));
if (min > result.distance) {
closestMatch = result.closestMatch;
closestMatchPath = result.path;
min = result.distance;
}
} else {
distance = Validator.levenshteinDistance(option, op);
if (min > distance) {
closestMatch = op;
closestMatchPath = util.copyArray(path);
min = distance;
}
}
}
return { closestMatch: closestMatch, path: closestMatchPath, distance: min };
} }
}, { }, {
key: 'printLocation', key: 'printLocation',
value: function printLocation(path, option) { value: function printLocation(path, option) {
var str = '\n\nProblem value found at: \noptions = {\n';
var prefix = arguments[2] === undefined ? 'Problem value found at: \n' : arguments[2];
var str = '\n\n' + prefix + 'options = {\n';
for (var i = 0; i < path.length; i++) { for (var i = 0; i < path.length; i++) {
for (var j = 0; j < i + 1; j++) { for (var j = 0; j < i + 1; j++) {
str += ' '; str += ' ';
@ -26608,9 +26683,9 @@ return /******/ (function(modules) { // webpackBootstrap
}, },
edges: { edges: {
arrows: { arrows: {
to: { enabled: { boolean: boolean }, scaleFactor: { number: number }, __type__: { object: object } },
middle: { enabled: { boolean: boolean }, scaleFactor: { number: number }, __type__: { object: object } },
from: { enabled: { boolean: boolean }, scaleFactor: { number: number }, __type__: { object: object } },
to: { enabled: { boolean: boolean }, scaleFactor: { number: number }, __type__: { object: object, boolean: boolean } },
middle: { enabled: { boolean: boolean }, scaleFactor: { number: number }, __type__: { object: object, boolean: boolean } },
from: { enabled: { boolean: boolean }, scaleFactor: { number: number }, __type__: { object: object, boolean: boolean } },
__type__: { string: ['from', 'to', 'middle'], object: object } __type__: { string: ['from', 'to', 'middle'], object: object }
}, },
color: { color: {
@ -26627,7 +26702,7 @@ return /******/ (function(modules) { // webpackBootstrap
size: { number: number }, // px size: { number: number }, // px
face: { string: string }, face: { string: string },
background: { string: string }, background: { string: string },
stroke: { number: number }, // px
strokeWidth: { number: number }, // px
strokeColor: { string: string }, strokeColor: { string: string },
align: { string: ['horizontal', 'top', 'middle', 'bottom'] }, align: { string: ['horizontal', 'top', 'middle', 'bottom'] },
__type__: { object: object, string: string } __type__: { object: object, string: string }
@ -26844,6 +26919,8 @@ return /******/ (function(modules) { // webpackBootstrap
allOptions.groups.__any__ = allOptions.nodes; allOptions.groups.__any__ = allOptions.nodes;
allOptions.manipulation.controlNodeStyle = allOptions.nodes; allOptions.manipulation.controlNodeStyle = allOptions.nodes;
//allOptions.locale = allOptions.manipulation.locale;
//allOptions.locales = allOptions.manipulation.locales;
var configureOptions = { var configureOptions = {
nodes: { nodes: {
@ -26870,7 +26947,7 @@ return /******/ (function(modules) { // webpackBootstrap
size: [14, 0, 100, 1], // px size: [14, 0, 100, 1], // px
face: ['arial', 'verdana', 'tahoma'], face: ['arial', 'verdana', 'tahoma'],
background: ['color', 'none'], background: ['color', 'none'],
stroke: [0, 0, 50, 1], // px
strokeWidth: [0, 0, 50, 1], // px
strokeColor: ['color', '#ffffff'] strokeColor: ['color', '#ffffff']
}, },
//group: 'string', //group: 'string',
@ -26922,7 +26999,7 @@ return /******/ (function(modules) { // webpackBootstrap
size: [14, 0, 100, 1], // px size: [14, 0, 100, 1], // px
face: ['arial', 'verdana', 'tahoma'], face: ['arial', 'verdana', 'tahoma'],
background: ['color', 'none'], background: ['color', 'none'],
stroke: [1, 0, 50, 1], // px
strokeWidth: [1, 0, 50, 1], // px
strokeColor: ['color', '#ffffff'], strokeColor: ['color', '#ffffff'],
align: ['horizontal', 'top', 'middle', 'bottom'] align: ['horizontal', 'top', 'middle', 'bottom']
}, },
@ -33694,7 +33771,7 @@ return /******/ (function(modules) { // webpackBootstrap
value: function parseOptions(parentOptions, newOptions) { value: function parseOptions(parentOptions, newOptions) {
var allowDeletion = arguments[2] === undefined ? false : arguments[2]; var allowDeletion = arguments[2] === undefined ? false : arguments[2];
var fields = ['color', 'fixed', 'font', 'shadow'];
var fields = ['color', 'fixed', 'shadow'];
util.selectiveNotDeepExtend(fields, parentOptions, newOptions); util.selectiveNotDeepExtend(fields, parentOptions, newOptions);
// merge the shadow options into the parent. // merge the shadow options into the parent.
@ -33882,15 +33959,15 @@ return /******/ (function(modules) { // webpackBootstrap
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.options.font.strokeWidth > 0) {
ctx.lineWidth = this.options.font.strokeWidth;
ctx.strokeStyle = strokeColor; ctx.strokeStyle = strokeColor;
ctx.lineJoin = 'round'; ctx.lineJoin = 'round';
} }
// draw the text // draw the text
for (var i = 0; i < this.lineCount; i++) { for (var i = 0; i < this.lineCount; i++) {
if (this.options.font.stroke > 0) {
if (this.options.font.strokeWidth > 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);

+ 2
- 2
docs/network/edges.html View File

@ -121,7 +121,7 @@ var options = {
size: 14, // px size: 14, // px
face: 'arial', face: 'arial',
background: 'none', background: 'none',
stroke: 1, // px
strokeWidth: 1, // px
strokeColor: '#ffffff', strokeColor: '#ffffff',
align:'horizontal' align:'horizontal'
}, },
@ -342,7 +342,7 @@ network.setOptions(options);
</td> </td>
</tr> </tr>
<tr parent="font" class="hidden"> <tr parent="font" class="hidden">
<td class="indent">font.stroke</td>
<td class="indent">font.strokeWidth</td>
<td class="mid">Number</td> <td class="mid">Number</td>
<td class="mid"><code>0</code></td> <td class="mid"><code>0</code></td>
<td>As an alternative to the background rectangle, a stroke can be drawn around the text. When a value <td>As an alternative to the background rectangle, a stroke can be drawn around the text. When a value

+ 21
- 23
docs/network/manipulation.html View File

@ -113,22 +113,20 @@ network.setOptions(options);
</pre> </pre>
<table class="moduleTable" id="optionTable"> <table class="moduleTable" id="optionTable">
<tr class="header"><td class="name">name</td><td class="type">type</td><td class="default">default</td><td class="description">description</td></tr> <tr class="header"><td class="name">name</td><td class="type">type</td><td class="default">default</td><td class="description">description</td></tr>
<tr><td>enabled</td> <td class="mid">Boolean</td> <td class="mid"><code>false</code></td> <td>Toggle the manipulation system on or off. This property is optional. If you define any of the options below and enabled is undefined, this will be set to true.</td></tr>
<tr><td>initiallyActive</td> <td class="mid">Boolean</td> <td class="mid"><code>true</code></td> <td>Toggle whether the toolbar is visible initially or if only the edit button is visible initially.</td></tr>
<tr><td>locale</td> <td class="mid">String</td> <td class="mid"><code>'en'</code></td> <td>Select the locale. By default, the language is English. If you want to use another language, you will need to define your own locale and refer to it here.</td></tr>
<tr><td>locales</td> <td class="mid">Object</td> <td class="mid">defaultLocales</td> <td>Locales object. By default only <code>'en'</code> and <code>'nl'</code> are supported. Take a look at the <a href="#locales" data-scroll="" data-options="{ &quot;easing&quot;: &quot;easeInCubic&quot; }">locales section below</a> for more explaination on how to customize this.</td></tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','functionality', this);"><td><span parent="functionality" class="right-caret"></span> functionality</td> <td class="mid">Object</td> <td class="mid">Object</td> <td>You can use this object to switch certain functionalities on or off. By default they are all on.</td></tr>
<tr parent="functionality" class="hidden"><td class="indent">functionality.addNode</td> <td class="mid">Boolean</td> <td class="mid"><code>true</code></td> <td>Toggle the adding of nodes.</td></tr>
<tr parent="functionality" class="hidden"><td class="indent">functionality.addEdge</td> <td class="mid">Boolean</td> <td class="mid"><code>true</code></td> <td>Toggle the adding of edges.</td></tr>
<tr parent="functionality" class="hidden"><td class="indent">functionality.editNode</td> <td class="mid">Boolean</td> <td class="mid"><code>true</code></td> <td>Toggle the editing of nodes. Even if this is enabled, it will only be shown if a handler function is set for <code>editNode</code>.</td></tr>
<tr parent="functionality" class="hidden"><td class="indent">functionality.editEdge</td> <td class="mid">Boolean</td> <td class="mid"><code>true</code></td> <td>Toggle the editing of edges.</td></tr>
<tr parent="functionality" class="hidden"><td class="indent">functionality.deleteNode</td> <td class="mid">Boolean</td> <td class="mid"><code>true</code></td> <td>Toggle the deletion of nodes.</td></tr>
<tr parent="functionality" class="hidden"><td class="indent">functionality.deleteEdge</td> <td class="mid">Boolean</td> <td class="mid"><code>true</code></td> <td>Toggle the deletion of edges.</td></tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','handlerFunctions', this);"><td><span parent="handlerFunctions" class="right-caret"></span> handlerFunctions</td> <td class="mid">Object</td> <td class="mid"><code>Object</code></td> <td>These functions are inserted before the action is performed. If a node is going to be added through the manipulation system, the addNode function will be called first. With this, you can provide a gui for your users, abort the process or anything else you want to do.</td></tr>
<tr parent="handlerFunctions" class="hidden"><td class="indent">handlerFunctions.addNode</td> <td class="mid">Function</td> <td class="mid"><code>undefined</code></td> <td>Called when the user clicks the canvas in 'addNode' mode. This function will receive two variables: the properties of the node that can be created and a callback function. If you call the callback function with the properties of the new node, the node will be added. <br><br> Example:
<tr><td>enabled</td> <td class="mid">Boolean</td> <td class="mid"><code>false</code></td> <td>Toggle the manipulation system on or off. This property is optional. If you define any of the options below and enabled is undefined, this will be set to true.</td></tr>
<tr><td>initiallyActive</td> <td class="mid">Boolean</td> <td class="mid"><code>true</code></td> <td>Toggle whether the toolbar is visible initially or if only the edit button is visible initially.</td></tr>
<tr><td>locale</td> <td class="mid">String</td> <td class="mid"><code>'en'</code></td> <td>Select the locale. By default, the language is English. If you want to use another language, you will need to define your own locale and refer to it here.</td></tr>
<tr><td>locales</td> <td class="mid">Object</td> <td class="mid">defaultLocales</td> <td>Locales object. By default only <code>'en'</code> and <code>'nl'</code> are supported. Take a look at the <a href="#locales" data-scroll="" data-options="{ &quot;easing&quot;: &quot;easeInCubic&quot; }">locales section below</a> for more explaination on how to customize this.</td></tr>
<tr><td>addNode</td> <td class="mid">Boolean or Function</td> <td class="mid"><code>true</code></td> <td>You can use these options to switch certain functionalities on or off of attach
a handler function to them. These functions are called before the action is performed. If a node is going to be added through the manipulation system,
the addNode function will be called first. With this, you can provide a gui for your users, abort the process or anything else you want to do. For all except the editNode functionality, these handler functions are optional.
<br><br>
When you supply a boolean, you only toggle the adding of nodes.
When a function is supplied, it will be called when the user clicks the canvas in 'addNode' mode. This function will receive two variables: the properties of the node that can be created and a callback function. If you call the callback function with the properties of the new node, the node will be added. <br><br> Example:
<pre class="code"> <pre class="code">
var options = { var options = {
handlerFunctions: {
manipulation: {
addNode: function(nodeData,callback) { addNode: function(nodeData,callback) {
nodeData.label = 'hello world'; nodeData.label = 'hello world';
callback(nodeData); callback(nodeData);
@ -136,12 +134,12 @@ var options = {
} }
} }
</pre> </pre>
If you do not want the node created, do not call the callback function or call the callback function <code>null</code> or no argument.
</td></tr>
<tr parent="handlerFunctions" class="hidden"><td class="indent">handlerFunctions.addEdge</td> <td class="mid">Function</td> <td class="mid"><code>undefined</code></td> <td>Called when the user drags the new edge from one node to the next in 'addEdge' mode. This function will receive two variables: the properties of the edge that can be created and a callback function. If you call the callback function with the properties of the new edge, the edge will be added. <br><br> Example:
This function changes the label of the new node into 'hello world'. If you do not want the node created, do not call the callback function or call the callback function <code>null</code> or no argument.</td></tr>
<tr><td>addEdge</td> <td class="mid">Boolean or Function</td> <td class="mid"><code>true</code></td> <td>If boolean, toggle the adding of edges.
When a function is supplied, it will be called when the user drags the new edge from one node to the next in 'addEdge' mode. This function will receive two variables: the properties of the edge that can be created and a callback function. If you call the callback function with the properties of the new edge, the edge will be added. <br><br> Example:
<pre class="code"> <pre class="code">
var options = { var options = {
handlerFunctions: {
manipulation: {
addEdge: function(edgeData,callback) { addEdge: function(edgeData,callback) {
if (edgeData.from === edgeData.to) { if (edgeData.from === edgeData.to) {
var r = confirm("Do you want to connect the node to itself?"); var r = confirm("Do you want to connect the node to itself?");
@ -156,11 +154,11 @@ var options = {
} }
} }
</pre> </pre>
This example code will show a popup if you connect a node to itself to ask you if that was what you wanted. If you do not want the edge created, do not call the callback function or call the callback function <code>null</code> or no argument.</td></tr>
<tr parent="handlerFunctions" class="hidden"><td class="indent">handlerFunctions.editNode</td> <td class="mid">Function</td> <td class="mid"><code>undefined</code></td> <td>Called when a node is selected and the 'Edit Node' button on the toolbar is pressed. This function will be called like the <code>addNode</code> function with the node's data and a callback function.</td></tr>
<tr parent="handlerFunctions" class="hidden"><td class="indent">handlerFunctions.editEdge</td> <td class="mid">Function</td> <td class="mid"><code>undefined</code></td> <td>Called when an edge is selcted and the 'Edit Edge' button on the toolbar is pressed. This function will be called in the same way the <code>addEdge</code> function was called. If the callback is not performed, the edge will remain hanging where it was released. To cancel, call the callback function with <code>null</code> as argument or without arguments.</pre></td></tr>
<tr parent="handlerFunctions" class="hidden"><td class="indent">handlerFunctions.deleteNode</td><td class="mid">Function</td> <td class="mid"><code>undefined</code></td> <td>Called when a node is selected and the 'Delete selected' button is pressed.</td></tr>
<tr parent="handlerFunctions" class="hidden"><td class="indent">handlerFunctions.deleteEdge</td><td class="mid">Function</td> <td class="mid"><code>undefined</code></td> <td>Called when an edge is selected and the 'Delete selected' button is pressed.</td></tr>
This example code will show a popup if you connect a node to itself to ask you if that was what you wanted. If you do not want the edge created, do not call the callback function or call the callback function <code>null</code> or no argument.</td></tr></td></tr>
<tr><td>editNode</td> <td class="mid">Function</td> <td class="mid"><code>undefined</code></td> <td>Editing of nodes is only possible when a handling function is supplied. If this is not the case, editing of nodes will be disabled. The function will be called when a node is selected and the 'Edit Node' button on the toolbar is pressed. This function will be called like the <code>addNode</code> function with the node's data and a callback function.</td></tr>
<tr><td>editEdge</td> <td class="mid">Boolean or Function</td> <td class="mid"><code>true</code></td> <td>If boolean, toggle the editing of edges. When a function is supplied, it will be called when an edge is selcted and the 'Edit Edge' button on the toolbar is pressed. This function will be called in the same way the <code>addEdge</code> function was called. If the callback is not performed, the edge will remain hanging where it was released. <b>To cancel, call the callback function with <code>null</code> as argument or without arguments</b>.</td></tr>
<tr><td>deleteNode</td> <td class="mid">Boolean or Function</td> <td class="mid"><code>true</code></td> <td>If boolean, toggle the deletion of nodes. If function, it will be called when a node is selected and the 'Delete selected' button is pressed.</td></tr>
<tr><td>deleteEdge</td> <td class="mid">Boolean or Function</td> <td class="mid"><code>true</code></td> <td>If boolean, toggle the deletion of edges. If function, it will be called when an edge is selected and the 'Delete selected' button is pressed.</td></tr>
<tr><td>controlNodeStyle</td> <td class="mid">Object</td> <td class="mid">Object</td><td>You can supply any styling information you'd like here. All fields described in <a href="./nodes.html">the nodes module</a> are allowed except obviously for id, x, y and fixed. <br><br>Default: <tr><td>controlNodeStyle</td> <td class="mid">Object</td> <td class="mid">Object</td><td>You can supply any styling information you'd like here. All fields described in <a href="./nodes.html">the nodes module</a> are allowed except obviously for id, x, y and fixed. <br><br>Default:
<pre class="code"> <pre class="code">
{ {

+ 2
- 2
docs/network/nodes.html View File

@ -118,7 +118,7 @@ var options = {
size: 14, // px size: 14, // px
face: 'arial', face: 'arial',
background: 'none', background: 'none',
stroke: 0, // px
strokeWidth: 0, // px
strokeColor: '#ffffff', strokeColor: '#ffffff',
align: 'horizontal' align: 'horizontal'
}, },
@ -340,7 +340,7 @@ network.setOptions(options);
</td> </td>
</tr> </tr>
<tr parent="font" class="hidden"> <tr parent="font" class="hidden">
<td class="indent">font.stroke</td>
<td class="indent">font.strokeWidth</td>
<td class="mid">Number</td> <td class="mid">Number</td>
<td class="mid"><code>0</code></td> <td class="mid"><code>0</code></td>
<td>As an alternative to the background rectangle, a stroke can be drawn around the text. When a value <td>As an alternative to the background rectangle, a stroke can be drawn around the text. When a value

+ 6
- 4
docs/network/view.html View File

@ -84,13 +84,15 @@
<pre class="code"> <pre class="code">
{ {
nodes:[Array of nodeIds], nodes:[Array of nodeIds],
duration: Number,
easingFunction: String
animation: { // -------------------> can be a boolean too!
duration: Number
easingFunction: String
}
} }
</pre> </pre>
The nodes can be used to zoom to fit only specific nodes in the view. <br /><br /> The nodes can be used to zoom to fit only specific nodes in the view. <br /><br />
The other options are explained in the <code>moveTo()</code> description above.
All options are optional for zoomExtent.
The other options are explained in the <code>moveTo()</code> description below.
All options are optional for the fit method.
</td></tr> </td></tr>
<tr><td>focus(<br> <tr><td>focus(<br>
&nbsp;&nbsp;&nbsp;<code>String nodeId</code>,<br> &nbsp;&nbsp;&nbsp;<code>String nodeId</code>,<br>

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

@ -47,7 +47,7 @@
nodes: nodes, nodes: nodes,
edges: edges edges: edges
}; };
var options = {};
var options = {nodes:{font:{locale:'en'}}};
var network = new vis.Network(container, data, options); var network = new vis.Network(container, data, options);
</script> </script>

+ 0
- 1
examples/network/27_world_cup_network.html View File

@ -125,7 +125,6 @@ Hide nodes on drag:
// Note: data is coming from ./data/WorldCup2014.js // Note: data is coming from ./data/WorldCup2014.js
network = new vis.Network(container, data, options); network = new vis.Network(container, data, options);
network.fit({duration: 0});
} }
function update() { function update() {

+ 3
- 13
examples/network/35_label_stroke.html View File

@ -25,7 +25,7 @@
<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', font: {stroke: 5, strokeColor: 'white'}},
{id: 1, label: 'Node 1', font: {strokeWidth: 3, strokeColor: 'white'}},
{id: 2, label: 'Node 2'}, {id: 2, label: 'Node 2'},
{id: 3, label: 'Node 3'}, {id: 3, label: 'Node 3'},
{id: 4, label: 'Node 4'}, {id: 4, label: 'Node 4'},
@ -34,7 +34,7 @@
// create an array with edges // create an array with edges
var edges = [ var edges = [
{from: 1, to: 2, label: 'edgeLabel', font: {stroke: 2, strokeColor : '#00ff00'}},
{from: 1, to: 2, label: 'edgeLabel', font: {strokeWidth: 2, strokeColor : '#00ff00'}},
{from: 1, to: 3, label: 'edgeLabel'}, {from: 1, to: 3, label: 'edgeLabel'},
{from: 2, to: 4}, {from: 2, to: 4},
{from: 2, to: 5} {from: 2, to: 5}
@ -49,17 +49,7 @@
var options = { var options = {
nodes : { nodes : {
shape: 'dot', shape: 'dot',
font: {
stroke: 1,
strokeColor: '#d1d1d1'
}
},
edges: {
font: {
stroke: 1,
strokeColor: '#d1d1d1',
//fill: 'none' // TODO: what is the replacement for fill?
}
size: 10
} }
}; };
var network = new vis.Network(container, data, options); var network = new vis.Network(container, data, options);

+ 21
- 0
lib/network/Network.js View File

@ -143,6 +143,8 @@ Network.prototype.setOptions = function (options) {
console.log('%cErrors have been found in the supplied options object. None of the options will be used.', printStyle); console.log('%cErrors have been found in the supplied options object. None of the options will be used.', printStyle);
} }
//this.placeConvenienceOptions(options);
// the hierarchical system can adapt the edges and the physics to it's own options because not all combinations work with the hierarichical system. // the hierarchical system can adapt the edges and the physics to it's own options because not all combinations work with the hierarichical system.
options = this.layoutEngine.setOptions(options.layout, options); options = this.layoutEngine.setOptions(options.layout, options);
@ -202,6 +204,25 @@ Network.prototype.setOptions = function (options) {
} }
}; };
//
///**
// *
// */
//Network.prototype.placeConvenienceOptions = function (options) {
// if (options.locale !== undefined) {
// if (options.manipulation === undefined) {
// options.manipulation = {enabled:false, locale:options.locale};
// }
// else if (typeof options.manipulation === 'boolean') {
// options.manipulation = {enabled: options.manipulation, locale:options.locale};
// }
// else {
// options.manipulation.locale = options.locale;
// }
// }
//}
/** /**
* Update the this.body.nodeIndices with the most recent node index list * Update the this.body.nodeIndices with the most recent node index list

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

@ -40,7 +40,7 @@ class EdgesHandler {
size: 14, // px size: 14, // px
face: 'arial', face: 'arial',
background: 'none', background: 'none',
stroke: 1, // px
strokeWidth: 0, // px
strokeColor: '#ffffff', strokeColor: '#ffffff',
align:'horizontal' align:'horizontal'
}, },

+ 15
- 15
lib/network/modules/ManipulationSystem.js View File

@ -237,7 +237,7 @@ class ManipulationSystem {
this.manipulationDOM = {}; this.manipulationDOM = {};
this._createBackButton(locale); this._createBackButton(locale);
this._createSeperator(); this._createSeperator();
this._createDescription(locale['addDescription'])
this._createDescription(locale['addDescription'] || this.options.locales['en']['addDescription']);
// bind the close button // bind the close button
this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this));
@ -281,7 +281,7 @@ class ManipulationSystem {
} }
} }
else { else {
alert(this.options.locales[this.options.locale]['editClusterError']);
alert(this.options.locales[this.options.locale]['editClusterError'] || this.options.locales['en']['editClusterError']);
} }
} }
else { else {
@ -310,7 +310,7 @@ class ManipulationSystem {
this.manipulationDOM = {}; this.manipulationDOM = {};
this._createBackButton(locale); this._createBackButton(locale);
this._createSeperator(); this._createSeperator();
this._createDescription(locale['edgeDescription']);
this._createDescription(locale['edgeDescription'] || this.options.locales['en']['edgeDescription']);
// bind the close button // bind the close button
this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this));
@ -346,7 +346,7 @@ class ManipulationSystem {
this.manipulationDOM = {}; this.manipulationDOM = {};
this._createBackButton(locale); this._createBackButton(locale);
this._createSeperator(); this._createSeperator();
this._createDescription(locale['editEdgeDescription']);
this._createDescription(locale['editEdgeDescription'] || this.options.locales['en']['editEdgeDescription']);
// bind the close button // bind the close button
this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this)); this._bindHammerToDiv(this.closeDiv, this.toggleEditMode.bind(this));
@ -414,7 +414,7 @@ class ManipulationSystem {
if (selectedNodes.length > 0) { if (selectedNodes.length > 0) {
for (let i = 0; i < selectedNodes.length; i++) { for (let i = 0; i < selectedNodes.length; i++) {
if (this.body.nodes[selectedNodes[i]].isCluster === true) { if (this.body.nodes[selectedNodes[i]].isCluster === true) {
alert(this.options.locales[this.options.locale]['deleteClusterError']);
alert(this.options.locales[this.options.locale]['deleteClusterError'] || this.options.locales['en']['deleteClusterError']);
return; return;
} }
} }
@ -559,7 +559,7 @@ class ManipulationSystem {
// create the contents for the editMode button // create the contents for the editMode button
let locale = this.options.locales[this.options.locale]; let locale = this.options.locales[this.options.locale];
let button = this._createButton('editMode', 'vis-button vis-edit vis-edit-mode', locale['edit']);
let button = this._createButton('editMode', 'vis-button vis-edit vis-edit-mode', locale['edit'] || this.options.locales['en']['edit']);
this.editModeDiv.appendChild(button); this.editModeDiv.appendChild(button);
// bind a hammer listener to the button, calling the function toggleEditMode. // bind a hammer listener to the button, calling the function toggleEditMode.
@ -652,37 +652,37 @@ class ManipulationSystem {
// ---------------------- DOM functions for buttons --------------------------// // ---------------------- DOM functions for buttons --------------------------//
_createAddNodeButton(locale) { _createAddNodeButton(locale) {
let button = this._createButton('addNode', 'vis-button vis-add', locale['addNode']);
let button = this._createButton('addNode', 'vis-button vis-add', locale['addNode'] || this.options.locales['en']['addNode']);
this.manipulationDiv.appendChild(button); this.manipulationDiv.appendChild(button);
this._bindHammerToDiv(button, this.addNodeMode.bind(this)); this._bindHammerToDiv(button, this.addNodeMode.bind(this));
} }
_createAddEdgeButton(locale) { _createAddEdgeButton(locale) {
let button = this._createButton('addEdge', 'vis-button vis-connect', locale['addEdge']);
let button = this._createButton('addEdge', 'vis-button vis-connect', locale['addEdge'] || this.options.locales['en']['addEdge']);
this.manipulationDiv.appendChild(button); this.manipulationDiv.appendChild(button);
this._bindHammerToDiv(button, this.addEdgeMode.bind(this)); this._bindHammerToDiv(button, this.addEdgeMode.bind(this));
} }
_createEditNodeButton(locale) { _createEditNodeButton(locale) {
let button = this._createButton('editNodeMode', 'vis-button vis-edit', locale['editNodeMode']);
let button = this._createButton('editNodeMode', 'vis-button vis-edit', locale['editNode'] || this.options.locales['en']['editNode']);
this.manipulationDiv.appendChild(button); this.manipulationDiv.appendChild(button);
this._bindHammerToDiv(button, this.editNodeMode.bind(this)); this._bindHammerToDiv(button, this.editNodeMode.bind(this));
} }
_createEditEdgeButton(locale) { _createEditEdgeButton(locale) {
let button = this._createButton('editEdge', 'vis-button vis-edit', locale['editEdge']);
let button = this._createButton('editEdge', 'vis-button vis-edit', locale['editEdge'] || this.options.locales['en']['editEdge']);
this.manipulationDiv.appendChild(button); this.manipulationDiv.appendChild(button);
this._bindHammerToDiv(button, this.editEdgeMode.bind(this)); this._bindHammerToDiv(button, this.editEdgeMode.bind(this));
} }
_createDeleteButton(locale) { _createDeleteButton(locale) {
let button = this._createButton('delete', 'vis-button vis-delete', locale['del']);
let button = this._createButton('delete', 'vis-button vis-delete', locale['del'] || this.options.locales['en']['del']);
this.manipulationDiv.appendChild(button); this.manipulationDiv.appendChild(button);
this._bindHammerToDiv(button, this.deleteSelected.bind(this)); this._bindHammerToDiv(button, this.deleteSelected.bind(this));
} }
_createBackButton(locale) { _createBackButton(locale) {
let button = this._createButton('back', 'vis-button vis-back', locale['back']);
let button = this._createButton('back', 'vis-button vis-back', locale['back'] || this.options.locales['en']['back']);
this.manipulationDiv.appendChild(button); this.manipulationDiv.appendChild(button);
this._bindHammerToDiv(button, this.showManipulatorToolbar.bind(this)); this._bindHammerToDiv(button, this.showManipulatorToolbar.bind(this));
} }
@ -884,7 +884,7 @@ class ManipulationSystem {
// perform the connection // perform the connection
if (node !== undefined && this.selectedControlNode !== undefined) { if (node !== undefined && this.selectedControlNode !== undefined) {
if (node.isCluster === true) { if (node.isCluster === true) {
alert(this.options.locales[this.options.locale]['createEdgeError'])
alert(this.options.locales[this.options.locale]['createEdgeError'] || this.options.locales['en']['createEdgeError'])
} }
else { else {
let from = this.body.nodes[this.temporaryIds.nodes[0]]; let from = this.body.nodes[this.temporaryIds.nodes[0]];
@ -925,7 +925,7 @@ class ManipulationSystem {
if (node !== undefined) { if (node !== undefined) {
if (node.isCluster === true) { if (node.isCluster === true) {
alert(this.options.locales[this.options.locale]['createEdgeError'])
alert(this.options.locales[this.options.locale]['createEdgeError'] || this.options.locales['en']['createEdgeError'])
} }
else { else {
// create a node the temporary line can look at // create a node the temporary line can look at
@ -1006,7 +1006,7 @@ class ManipulationSystem {
// perform the connection // perform the connection
if (node !== undefined) { if (node !== undefined) {
if (node.isCluster === true) { if (node.isCluster === true) {
alert(this.options.locales[this.options.locale]['createEdgeError']);
alert(this.options.locales[this.options.locale]['createEdgeError'] || this.options.locales['en']['createEdgeError']);
} }
else { else {
if (this.body.nodes[connectFromId] !== undefined && this.body.nodes[node.id] !== undefined) { if (this.body.nodes[connectFromId] !== undefined && this.body.nodes[node.id] !== undefined) {

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

@ -47,7 +47,7 @@ class NodesHandler {
size: 14, // px size: 14, // px
face: 'arial', face: 'arial',
background: 'none', background: 'none',
stroke: 0, // px
strokeWidth: 0, // px
strokeColor: '#ffffff', strokeColor: '#ffffff',
align: 'horizontal' align: 'horizontal'
}, },

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

@ -89,18 +89,19 @@ class PhysicsEngine {
} }
setOptions(options) { setOptions(options) {
if (options === false) {
this.physicsEnabled = false;
this.stopSimulation();
}
else {
this.physicsEnabled = true;
if (options !== undefined) {
if (options !== undefined) {
if (options === false) {
this.physicsEnabled = false;
this.stopSimulation();
}
else {
this.physicsEnabled = true;
util.selectiveNotDeepExtend(['stabilization'], this.options, options); util.selectiveNotDeepExtend(['stabilization'], this.options, options);
util.mergeOptions(this.options, options, 'stabilization') util.mergeOptions(this.options, options, 'stabilization')
} }
this.init();
} }
this.init();
} }
@ -134,13 +135,14 @@ class PhysicsEngine {
else { else {
this.stabilized = false; this.stabilized = false;
this.ready = true; this.ready = true;
this.body.emitter.emit('fit', {duration: 0}, true)
this.body.emitter.emit('fit', {}, true)
this.startSimulation(); this.startSimulation();
} }
} }
else { else {
this.ready = true; this.ready = true;
this.body.emitter.emit('_redraw');
this.body.emitter.emit('fit');
} }
} }
@ -494,7 +496,7 @@ class PhysicsEngine {
_finalizeStabilization() { _finalizeStabilization() {
this.body.emitter.emit('_allowRedrawRequests'); this.body.emitter.emit('_allowRedrawRequests');
if (this.options.stabilization.fit === true) { if (this.options.stabilization.fit === true) {
this.body.emitter.emit('fit', {duration:0});
this.body.emitter.emit('fit');
} }
if (this.options.stabilization.onlyDynamicEdges === true) { if (this.options.stabilization.onlyDynamicEdges === true) {

+ 50
- 17
lib/network/modules/Validator.js View File

@ -1,6 +1,7 @@
var util = require('../../util'); var util = require('../../util');
let errorFound = false; let errorFound = false;
let allOptions;
let printStyle = 'background: #FFeeee; color: #dd0000'; let printStyle = 'background: #FFeeee; color: #dd0000';
/** /**
* Used to validate options. * Used to validate options.
@ -17,6 +18,7 @@ class Validator {
*/ */
static validate(options, referenceOptions, subObject) { static validate(options, referenceOptions, subObject) {
errorFound = false; errorFound = false;
allOptions = referenceOptions;
let usedOptions = referenceOptions; let usedOptions = referenceOptions;
if (subObject !== undefined) { if (subObject !== undefined) {
usedOptions = referenceOptions[subObject]; usedOptions = referenceOptions[subObject];
@ -55,14 +57,12 @@ class Validator {
else if (referenceOptions[option] === undefined && referenceOptions.__any__ !== undefined) { else if (referenceOptions[option] === undefined && referenceOptions.__any__ !== undefined) {
// __any__ is a wildcard. Any value is accepted and will be further analysed by reference. // __any__ is a wildcard. Any value is accepted and will be further analysed by reference.
if (Validator.getType(options[option]) === 'object') { if (Validator.getType(options[option]) === 'object') {
util.copyAndExtendArray(path, option);
Validator.checkFields(option, options, referenceOptions, '__any__', referenceOptions['__any__'].__type__, path); Validator.checkFields(option, options, referenceOptions, '__any__', referenceOptions['__any__'].__type__, path);
} }
} }
else { else {
// Since all options in the reference are objects, we can check whether they are supposed to be object to look for the __type__ field. // Since all options in the reference are objects, we can check whether they are supposed to be object to look for the __type__ field.
if (referenceOptions[option].__type__ !== undefined) { if (referenceOptions[option].__type__ !== undefined) {
util.copyAndExtendArray(path, option);
// if this should be an object, we check if the correct type has been supplied to account for shorthand options. // if this should be an object, we check if the correct type has been supplied to account for shorthand options.
Validator.checkFields(option, options, referenceOptions, option, referenceOptions[option].__type__, path); Validator.checkFields(option, options, referenceOptions, option, referenceOptions[option].__type__, path);
} }
@ -93,10 +93,12 @@ class Validator {
errorFound = true; errorFound = true;
} }
else if (optionType === 'object') { else if (optionType === 'object') {
path = util.copyAndExtendArray(path, option);
Validator.parse(options[option], referenceOptions[referenceOption], path); Validator.parse(options[option], referenceOptions[referenceOption], path);
} }
} }
else if (optionType === 'object') { else if (optionType === 'object') {
path = util.copyAndExtendArray(path, option);
Validator.parse(options[option], referenceOptions[referenceOption], path); Validator.parse(options[option], referenceOptions[referenceOption], path);
} }
} }
@ -159,31 +161,62 @@ class Validator {
} }
static getSuggestion(option, options, path) { static getSuggestion(option, options, path) {
let closestMatch = '';
let min = 1e9;
let threshold = 8;
for (let op in options) {
let distance = Validator.levenshteinDistance(option, op);
if (min > distance && distance < threshold) {
closestMatch = op;
min = distance;
let localSearch = Validator.findInOptions(option,options,path,false);
let globalSearch = Validator.findInOptions(option,allOptions,[],true);
}
}
let localSearchThreshold = 8;
let globalSearchThreshold = 5;
if (min < threshold) {
console.log('%cUnknown option detected: "' + option + '". Did you mean "' + closestMatch + '"?' + Validator.printLocation(path, option), printStyle);
if (globalSearch.distance <= globalSearchThreshold && localSearch.distance > globalSearch.distance) {
console.log('%cUnknown option detected: "' + option + '" in ' + Validator.printLocation(localSearch.path, option,'') + 'Perhaps it was misplaced? Matching option found at: ' + Validator.printLocation(globalSearch.path, option,''), printStyle);
}
else if (localSearch.distance <= localSearchThreshold) {
console.log('%cUnknown option detected: "' + option + '". Did you mean "' + localSearch.closestMatch + '"?' + Validator.printLocation(localSearch.path, option), printStyle);
} }
else { else {
console.log('%cUnknown option detected: "' + option + '". Did you mean one of these: ' + Validator.print(Object.keys(options)) + Validator.printLocation(path, option), printStyle); console.log('%cUnknown option detected: "' + option + '". Did you mean one of these: ' + Validator.print(Object.keys(options)) + Validator.printLocation(path, option), printStyle);
} }
errorFound = true; errorFound = true;
return closestMatch;
} }
static printLocation(path, option) {
let str = '\n\nProblem value found at: \noptions = {\n';
/**
* traverse the options in search for a match.
* @param option
* @param options
* @param path
* @param recursive
* @returns {{closestMatch: string, path: Array, distance: number}}
*/
static findInOptions(option, options, path, recursive = false) {
let min = 1e9;
let closestMatch = '';
let closestMatchPath = [];
for (let op in options) {
let type = Validator.getType(options[op]);
let distance;
if (type === 'object' && recursive === true) {
let result = Validator.findInOptions(option, options[op], util.copyAndExtendArray(path,op));
if (min > result.distance) {
closestMatch = result.closestMatch;
closestMatchPath = result.path;
min = result.distance;
}
}
else {
distance = Validator.levenshteinDistance(option, op);
if (min > distance) {
closestMatch = op;
closestMatchPath = util.copyArray(path);
min = distance;
}
}
}
return {closestMatch:closestMatch, path:closestMatchPath, distance:min}
}
static printLocation(path, option, prefix = 'Problem value found at: \n') {
let str = '\n\n' + prefix + 'options = {\n';
for (let i = 0; i < path.length; i++) { for (let i = 0; i < path.length; i++) {
for (let j = 0; j < i + 1; j++) { for (let j = 0; j < i + 1; j++) {
str += ' '; str += ' ';

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

@ -144,7 +144,7 @@ class View {
} }
var center = this._findCenter(range); var center = this._findCenter(range);
var animationOptions = {position: center, scale: zoomLevel, animation: options};
var animationOptions = {position: center, scale: zoomLevel, animation: options.animation};
this.moveTo(animationOptions); this.moveTo(animationOptions);
} }

+ 8
- 6
lib/network/modules/components/AllOptions.js View File

@ -36,9 +36,9 @@ let allOptions = {
}, },
edges: { edges: {
arrows: { arrows: {
to: {enabled: {boolean}, scaleFactor: {number}, __type__: {object}},
middle: {enabled: {boolean}, scaleFactor: {number}, __type__: {object}},
from: {enabled: {boolean}, scaleFactor: {number}, __type__: {object}},
to: {enabled: {boolean}, scaleFactor: {number}, __type__: {object, boolean}},
middle: {enabled: {boolean}, scaleFactor: {number}, __type__: {object, boolean}},
from: {enabled: {boolean}, scaleFactor: {number}, __type__: {object, boolean}},
__type__: {string:['from','to','middle'],object} __type__: {string:['from','to','middle'],object}
}, },
color: { color: {
@ -55,7 +55,7 @@ let allOptions = {
size: {number}, // px size: {number}, // px
face: {string}, face: {string},
background: {string}, background: {string},
stroke: {number}, // px
strokeWidth: {number}, // px
strokeColor: {string}, strokeColor: {string},
align: {string:['horizontal','top','middle','bottom']}, align: {string:['horizontal','top','middle','bottom']},
__type__: {object,string} __type__: {object,string}
@ -272,6 +272,8 @@ let allOptions = {
allOptions.groups.__any__ = allOptions.nodes; allOptions.groups.__any__ = allOptions.nodes;
allOptions.manipulation.controlNodeStyle = allOptions.nodes; allOptions.manipulation.controlNodeStyle = allOptions.nodes;
//allOptions.locale = allOptions.manipulation.locale;
//allOptions.locales = allOptions.manipulation.locales;
let configureOptions = { let configureOptions = {
@ -299,7 +301,7 @@ let configureOptions = {
size: [14, 0, 100, 1], // px size: [14, 0, 100, 1], // px
face: ['arial', 'verdana', 'tahoma'], face: ['arial', 'verdana', 'tahoma'],
background: ['color','none'], background: ['color','none'],
stroke: [0, 0, 50, 1], // px
strokeWidth: [0, 0, 50, 1], // px
strokeColor: ['color','#ffffff'] strokeColor: ['color','#ffffff']
}, },
//group: 'string', //group: 'string',
@ -351,7 +353,7 @@ let configureOptions = {
size: [14, 0, 100, 1], // px size: [14, 0, 100, 1], // px
face: ['arial', 'verdana', 'tahoma'], face: ['arial', 'verdana', 'tahoma'],
background: ['color','none'], background: ['color','none'],
stroke: [1, 0, 50, 1], // px
strokeWidth: [1, 0, 50, 1], // px
strokeColor: ['color','#ffffff'], strokeColor: ['color','#ffffff'],
align: ['horizontal', 'top', 'middle', 'bottom'] align: ['horizontal', 'top', 'middle', 'bottom']
}, },

+ 0
- 1
lib/network/modules/components/Node.js View File

@ -163,7 +163,6 @@ class Node {
var fields = [ var fields = [
'color', 'color',
'fixed', 'fixed',
'font',
'shadow' 'shadow'
]; ];
util.selectiveNotDeepExtend(fields, parentOptions, newOptions); util.selectiveNotDeepExtend(fields, parentOptions, newOptions);

+ 3
- 3
lib/network/modules/components/shared/Label.js View File

@ -124,15 +124,15 @@ class Label {
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.options.font.strokeWidth > 0) {
ctx.lineWidth = this.options.font.strokeWidth;
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.options.font.strokeWidth > 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);

+ 15
- 0
lib/util.js View File

@ -555,6 +555,21 @@ exports.copyAndExtendArray = function(arr,newValue) {
return newArr; return newArr;
} }
/**
* Used to extend an array and copy it. This is used to propagate paths recursively.
*
* @param arr
* @param newValue
* @returns {Array}
*/
exports.copyArray = function(arr) {
let newArr = [];
for (let i = 0; i < arr.length; i++) {
newArr.push(arr[i]);
}
return newArr;
}
/** /**
* Retrieve the absolute left value of a DOM element * Retrieve the absolute left value of a DOM element
* @param {Element} elem A dom element, for example a div * @param {Element} elem A dom element, for example a div

Loading…
Cancel
Save