Browse Source

added a pretty amazing option checker to network :)

flowchartTest
Alex de Mulder 9 years ago
parent
commit
bf274fd51e
12 changed files with 4191 additions and 2996 deletions
  1. +3577
    -2968
      dist/vis.js
  2. +1
    -1
      dist/vis.map
  3. +22
    -15
      dist/vis.min.js
  4. +1
    -0
      docs/css/newdocs.css
  5. +3
    -4
      examples/network/01_basic_usage.html
  6. +9
    -0
      lib/network/Network.js
  7. +5
    -5
      lib/network/modules/ConfigurationSystem.js
  8. +256
    -0
      lib/network/modules/Validator.js
  9. +288
    -0
      lib/network/modules/components/AllOptions.js
  10. +1
    -0
      lib/network/modules/components/Edge.js
  11. +6
    -2
      lib/network/modules/components/Node.js
  12. +22
    -1
      lib/util.js

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


+ 1
- 1
dist/vis.map
File diff suppressed because it is too large
View File


+ 22
- 15
dist/vis.min.js
File diff suppressed because it is too large
View File


+ 1
- 0
docs/css/newdocs.css View File

@ -80,6 +80,7 @@ td.properties {
} }
p { p {
min-width:400px;
max-width:1000px; max-width:1000px;
} }

+ 3
- 4
examples/network/01_basic_usage.html View File

@ -44,10 +44,9 @@
edges: edges edges: edges
}; };
var options = { var options = {
manipulation: true,
groups:{'bla':{color:{background:'red'}, borderWidth:5}}
// physics:{stabilization:true}
manipulation: 'hello',
physics:{barnesHut:{gravitationslPull:34}, solver:'banana'},
groups:{'bla':{color:{backsground:'red'}, borderWidth:5}}
} }
var network = new vis.Network(container, data, options); var network = new vis.Network(container, data, options);
// network.setOptions({nodes:{color:'red'}}) // network.setOptions({nodes:{color:'red'}})

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

@ -24,6 +24,8 @@ import SelectionHandler from "./modules/SelectionHandler";
import LayoutEngine from "./modules/LayoutEngine"; import LayoutEngine from "./modules/LayoutEngine";
import ManipulationSystem from "./modules/ManipulationSystem"; import ManipulationSystem from "./modules/ManipulationSystem";
import ConfigurationSystem from "./modules/ConfigurationSystem"; import ConfigurationSystem from "./modules/ConfigurationSystem";
import Validator from "./modules/Validator";
import {printStyle} from "./modules/Validator";
/** /**
* @constructor Network * @constructor Network
@ -132,6 +134,13 @@ Emitter(Network.prototype);
*/ */
Network.prototype.setOptions = function (options) { Network.prototype.setOptions = function (options) {
if (options !== undefined) { if (options !== undefined) {
let errorFound = Validator.validate(options);
if (errorFound === true) {
options = {};
console.log('%cErrors have been found in the supplied options object. None of the options will be used.', printStyle);
}
// 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);

+ 5
- 5
lib/network/modules/ConfigurationSystem.js View File

@ -195,7 +195,7 @@ class ConfigurationSystem {
select: true, select: true,
selectConnectedEdges: true selectConnectedEdges: true
}, },
renderer: {
rendering: {
hideEdgesOnDrag: false, hideEdgesOnDrag: false,
hideNodesOnDrag: false hideNodesOnDrag: false
} }
@ -209,7 +209,7 @@ class ConfigurationSystem {
manipulation:{}, manipulation:{},
physics:{}, physics:{},
selection:{}, selection:{},
renderer:{},
rendering:{},
configure: false, configure: false,
configureContainer: undefined configureContainer: undefined
}; };
@ -241,7 +241,7 @@ class ConfigurationSystem {
util.deepExtend(this.actualOptions.manipulation, this.network.manipulation.options, true); util.deepExtend(this.actualOptions.manipulation, this.network.manipulation.options, true);
util.deepExtend(this.actualOptions.physics, this.network.physics.options, true); util.deepExtend(this.actualOptions.physics, this.network.physics.options, true);
util.deepExtend(this.actualOptions.selection, this.network.selectionHandler.selection, true); util.deepExtend(this.actualOptions.selection, this.network.selectionHandler.selection, true);
util.deepExtend(this.actualOptions.renderer, this.network.renderer.selection, true);
util.deepExtend(this.actualOptions.rendering, this.network.renderer.selection, true);
this.container = this.network.body.container; this.container = this.network.body.container;
@ -608,7 +608,7 @@ class ConfigurationSystem {
for (let subObj in obj) { for (let subObj in obj) {
if (obj.hasOwnProperty(subObj)) { if (obj.hasOwnProperty(subObj)) {
let item = obj[subObj]; let item = obj[subObj];
let newPath = this._addToPath(path, subObj);
let newPath = util.copyAndExtendArray(path, subObj);
let value = this._getValue(newPath); let value = this._getValue(newPath);
if (item instanceof Array) { if (item instanceof Array) {
@ -632,7 +632,7 @@ class ConfigurationSystem {
if (draw === true) { if (draw === true) {
// initially collapse options with an disabled enabled option. // initially collapse options with an disabled enabled option.
if (item.enabled !== undefined) { if (item.enabled !== undefined) {
let enabledPath = this._addToPath(newPath, 'enabled');
let enabledPath = util.copyAndExtendArray(newPath, 'enabled');
let enabledValue = this._getValue(enabledPath); let enabledValue = this._getValue(enabledPath);
if (enabledValue === true) { if (enabledValue === true) {
let label = this._makeLabel(subObj, newPath, true); let label = this._makeLabel(subObj, newPath, true);

+ 256
- 0
lib/network/modules/Validator.js View File

@ -0,0 +1,256 @@
var util = require('../../util');
import allOptions from './components/AllOptions.js'
let errorFound = false;
let printStyle = 'background: #FFeeee; color: #dd0000';
/**
* Used to validate options.
*/
class Validator {
constructor() {
}
/**
* Main function to be called
* @param options
* @param subObject
* @returns {boolean}
*/
static validate(options, subObject) {
errorFound = false;
let usedOptions = allOptions;
if (subObject !== undefined) {
usedOptions = allOptions[subObject];
}
Validator.parse(options, usedOptions, []);
return errorFound;
}
/**
* Will traverse an object recursively and check every value
* @param options
* @param referenceOptions
* @param path
*/
static parse(options, referenceOptions, path) {
for (let option in options) {
if (options.hasOwnProperty(option)) {
Validator.check(option, options, referenceOptions, path);
}
}
}
/**
* Check every value. If the value is an object, call the parse function on that object.
* @param option
* @param options
* @param referenceOptions
* @param path
*/
static check(option, options, referenceOptions, path) {
if (referenceOptions[option] === undefined && referenceOptions.__any__ === undefined) {
Validator.getSuggestion(option, referenceOptions, path);
}
else if (referenceOptions[option] === undefined && referenceOptions.__any__ !== undefined) {
// __any__ is a wildcard. Any value is accepted and will be further analysed by reference.
if (Validator.getType(options[option]) === 'object') {
util.copyAndExtendArray(path, option);
Validator.checkFields(option, options, referenceOptions, '__any__', referenceOptions['__any__'].__type__, path, true);
}
}
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.
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.
Validator.checkFields(option, options, referenceOptions, option, referenceOptions[option].__type__, path, true);
}
else {
Validator.checkFields(option, options, referenceOptions, option, referenceOptions[option], path, false);
}
}
}
/**
*
* @param {String} option | the option property
* @param {Object} options | The supplied options object
* @param {Object} referenceOptions | The reference options containing all options and their allowed formats
* @param {String} referenceOption | Usually this is the same as option, except when handling an __any__ tag.
* @param {String} refOptionType | This is the type object from the reference options
* @param path
* @param recursive
*/
static checkFields(option, options, referenceOptions, referenceOption, refOptionObj, path, recursive) {
let optionType = Validator.getType(options[option]);
let refOptionType = refOptionObj[optionType];
if (refOptionType !== undefined) {
// if the type is correct, we check if it is supposed to be one of a few select values
if (Validator.getType(refOptionType) === 'array') {
if (refOptionType.indexOf(options[option]) === -1) {
console.log('%cInvalid option detected in "' + option + '".' +
' Allowed values are:' + Validator.print(refOptionType) + ' not "' + options[option] + '". ' + Validator.printLocation(path, option), printStyle);
errorFound = true;
}
else if (recursive === true) {
Validator.parse(options[option], referenceOptions[referenceOption], path);
}
}
else if (recursive === true) {
Validator.parse(options[option], referenceOptions[referenceOption], path);
}
}
else {
// type of the field is incorrect
console.log('%cInvalid type received for "' + option + '". Expected: ' + Validator.print(Object.keys(referenceOptions[referenceOption].__type__)) + '. Received [' + optionType + '] "' + options[option] + '"' + Validator.printLocation(path, option), printStyle);
errorFound = true;
}
}
static getType(object) {
var type = typeof object;
if (type === 'object') {
if (object === null) {
return 'null';
}
if (object instanceof Boolean) {
return 'boolean';
}
if (object instanceof Number) {
return 'number';
}
if (object instanceof String) {
return 'string';
}
if (Array.isArray(object)) {
return 'array';
}
if (object instanceof Date) {
return 'date';
}
if (object.nodeType !== undefined) {
return 'dom'
}
return 'object';
}
else if (type === 'number') {
return 'number';
}
else if (type === 'boolean') {
return 'boolean';
}
else if (type === 'string') {
return 'string';
}
else if (type === undefined) {
return 'undefined';
}
return type;
}
static getSuggestion(option, options, path) {
let closestMatch = '';
let min = 1e9;
let threshold = 10;
for (let op in options) {
let distance = Validator.levenshteinDistance(option, op);
if (min > distance && distance < threshold) {
closestMatch = op;
min = distance;
}
}
if (min < threshold) {
console.log('%cUnknown option detected: "' + option + '". Did you mean "' + closestMatch + '"?' + Validator.printLocation(path, option), printStyle);
}
else {
console.log('%cUnknown option detected: "' + option + '". Did you mean one of these: ' + Validator.print(Object.keys(options)) + Validator.printLocation(path, option), printStyle);
}
errorFound = true;
return closestMatch;
}
static printLocation(path, option) {
let str = '\n\nProblem value found at: \noptions = {\n';
for (let i = 0; i < path.length; i++) {
for (let j = 0; j < i + 1; j++) {
str += ' ';
}
str += path[i] + ': {\n'
}
for (let j = 0; j < path.length + 1; j++) {
str += ' ';
}
str += option + '\n';
for (let i = 0; i < path.length + 1; i++) {
for (let j = 0; j < path.length - i; j++) {
str += ' ';
}
str += '}\n'
}
return str + '\n\n';
}
static print(options) {
return JSON.stringify(options).replace(/(\")|(\[)|(\])|(,"__type__")/g, "").replace(/(\,)/g, ', ')
}
// Compute the edit distance between the two given strings
// http://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#JavaScript
/*
Copyright (c) 2011 Andrei Mackenzie
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
static levenshteinDistance(a, b) {
if (a.length === 0) return b.length;
if (b.length === 0) return a.length;
var matrix = [];
// increment along the first column of each row
var i;
for (i = 0; i <= b.length; i++) {
matrix[i] = [i];
}
// increment each column in the first row
var j;
for (j = 0; j <= a.length; j++) {
matrix[0][j] = j;
}
// Fill in the rest of the matrix
for (i = 1; i <= b.length; i++) {
for (j = 1; j <= a.length; j++) {
if (b.charAt(i - 1) == a.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
} else {
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
Math.min(matrix[i][j - 1] + 1, // insertion
matrix[i - 1][j] + 1)); // deletion
}
}
}
return matrix[b.length][a.length];
}
;
}
export default Validator;
export {printStyle}

+ 288
- 0
lib/network/modules/components/AllOptions.js View File

@ -0,0 +1,288 @@
/**
* __any__
* __type__
*/
let string = 'string';
let boolean = 'boolean';
let number = 'number';
let array = 'array';
let object = 'object';
let dom = 'dom';
let fn = 'function';
let undef = 'undefined';
let allOptions = {
canvas: {
width: {string},
height: {string},
__type__: {object}
},
rendering: {
hideEdgesOnDrag: {boolean},
hideNodesOnDrag: {boolean},
__type__: {object}
},
clustering: {},
configuration: {
filter: {boolean,string:['nodes','edges','layout','physics','manipulation','interaction','selection','rendering'],array},
container: {dom},
__type__: {object,string,array,boolean}
},
edges: {
arrows: {
to: {enabled: {boolean}, scaleFactor: {number}, __type__: {object}},
middle: {enabled: {boolean}, scaleFactor: {number}, __type__: {object}},
from: {enabled: {boolean}, scaleFactor: {number}, __type__: {object}},
__type__: {string:['from','to','middle'],object}
},
color: {
color: {string},
highlight: {string},
hover: {string},
inherit: {string:['from','to','both'],boolean},
opacity: {number},
__type__: {object}
},
dashes: {
enabled: {boolean},
pattern: {array},
__type__: {boolean,object}
},
font: {
color: {string},
size: {number}, // px
face: {string},
background: {string},
stroke: {number}, // px
strokeColor: {string},
align: {string:['horizontal','top','middle','bottom']},
__type__: {object}
},
hidden: {boolean},
hoverWidth: {fn,number},
label: {string,undef},
length: {number,undef},
physics: {boolean},
scaling: {
min: {number},
max: {number},
label: {
enabled: {boolean},
min: {number},
max: {number},
maxVisible: {number},
drawThreshold: {number},
__type__: {object,boolean}
},
customScalingFunction: {fn},
__type__: {object}
},
selectionWidth: {fn,number},
selfReferenceSize: {number},
shadow: {
enabled: {boolean},
size: {number},
x: {number},
y: {number},
__type__: {object,boolean}
},
smooth: {
enabled: {boolean},
dynamic: {boolean},
type: {string},
roundness: {number},
__type__: {object,boolean}
},
title: {string, undef},
width: {number},
value: {number, undef},
__type__: {object}
},
groups: {
useDefaultGroups: {boolean},
__any__: ['__ref__','nodes'],
__type__: {object}
},
interaction: {
dragNodes: {boolean},
dragView: {boolean},
zoomView: {boolean},
hoverEnabled: {boolean},
navigationButtons: {boolean},
tooltipDelay: {number},
keyboard: {
enabled: {boolean},
speed: {x: {number}, y: {number}, zoom: {number}, __type__: {object}},
bindToWindow: {boolean},
__type__: {object,boolean}
},
__type__: {object}
},
layout: {
randomSeed: undefined,
hierarchical: {
enabled: {boolean},
levelSeparation: {number},
direction: {string:['UD','DU','LR','RL']}, // UD, DU, LR, RL
sortMethod: {string:['hubsize','directed']}, // hubsize, directed
__type__: {object,boolean}
},
__type__: {object}
},
manipulation: {
enabled: {boolean},
initiallyActive: {boolean},
locale: {string},
locales: {object},
functionality: {
addNode: {boolean},
addEdge: {boolean},
editNode: {boolean},
editEdge: {boolean},
deleteNode: {boolean},
deleteEdge: {boolean},
__type__: {object}
},
handlerFunctions: {
addNode: {fn,undef},
addEdge: {fn,undef},
editNode: {fn,undef},
editEdge: {fn,undef},
deleteNode: {fn,undef},
deleteEdge: {fn,undef},
__type__: {object}
},
controlNodeStyle: ['__ref__','nodes'],
__type__: {object,boolean}
},
nodes: {
borderWidth: {number},
borderWidthSelected: {number,undef},
brokenImage: {string,undef},
color: {
border: {string},
background: {string},
highlight: {
border: {string},
background: {string},
__type__: {object,string}
},
hover: {
border: {string},
background: {string},
__type__: {object,string}
},
__type__: {object,string}
},
fixed: {
x: {boolean},
y: {boolean},
__type__: {object,boolean}
},
font: {
color: {string},
size: {number}, // px
face: {string},
background: {string},
stroke: {number}, // px
strokeColor: {string},
__type__: {object}
},
group: {string,number,undef},
hidden: {boolean},
icon: {
face: {string},
code: {string}, //'\uf007',
size: {number}, //50,
color: {string},
__type__: {object}
},
id: {string, number},
image: {string,undef}, // --> URL
label: {string,undef},
level: {number,undef},
mass: {number},
physics: {boolean},
scaling: {
min: {number},
max: {number},
label: {
enabled: {boolean},
min: {number},
max: {number},
maxVisible: {number},
drawThreshold: {number},
__type__: {object, boolean}
},
customScalingFunction: {fn},
__type__: {object}
},
shadow: {
enabled: {boolean},
size: {number},
x: {number},
y: {number},
__type__: {object,boolean}
},
shape: {string:['ellipse', 'circle', 'database', 'box', 'text','image', 'circularImage','diamond', 'dot', 'star', 'triangle','triangleDown', 'square','icon']},
size: {number},
title: {string,undef},
value: {number,undef},
x: {number},
y: {number},
__type__: {object}
},
physics: {
barnesHut: {
gravitationalConstant: {number},
centralGravity: {number},
springLength: {number},
springConstant: {number},
damping: {number},
__type__: {object}
},
repulsion: {
centralGravity: {number},
springLength: {number},
springConstant: {number},
nodeDistance: {number},
damping: {number},
__type__: {object}
},
hierarchicalRepulsion: {
centralGravity: {number},
springLength: {number},
springConstant: {number},
nodeDistance: {number},
damping: {number},
__type__: {object}
},
maxVelocity: {number},
minVelocity: {number}, // px/s
solver: {string:['barnesHut','repulsion','hierarchicalRepulsion']},
stabilization: {
enabled: {boolean},
iterations: {number}, // maximum number of iteration to stabilize
updateInterval: {number},
onlyDynamicEdges: {boolean},
fit: {boolean},
__type__: {object,boolean}
},
timestep: {number},
__type__: {object,boolean}
},
selection: {
select: {boolean},
selectConnectedEdges: {boolean},
__type__: {object}
},
view: {},
__type__: {object}
};
allOptions.groups.__any__ = allOptions.nodes;
allOptions.manipulation.controlNodeStyle = allOptions.nodes;
export default allOptions;

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

@ -70,6 +70,7 @@ class Edge {
if (options.from !== undefined) {this.fromId = options.from;} if (options.from !== undefined) {this.fromId = options.from;}
if (options.to !== undefined) {this.toId = options.to;} if (options.to !== undefined) {this.toId = options.to;}
if (options.title !== undefined) {this.title = options.title;} if (options.title !== undefined) {this.title = options.title;}
if (options.value !== undefined) {options.value = parseInt(options.value);}
// A node is connected when it has a from and to node that both exist in the network.body.nodes. // A node is connected when it has a from and to node that both exist in the network.body.nodes.

+ 6
- 2
lib/network/modules/components/Node.js View File

@ -16,6 +16,9 @@ import Star from './nodes/shapes/Star'
import Text from './nodes/shapes/Text' import Text from './nodes/shapes/Text'
import Triangle from './nodes/shapes/Triangle' import Triangle from './nodes/shapes/Triangle'
import TriangleDown from './nodes/shapes/TriangleDown' import TriangleDown from './nodes/shapes/TriangleDown'
import Validator from "../Validator";
import {printStyle} from "../Validator";
/** /**
* @class Node * @class Node
@ -116,9 +119,10 @@ class Node {
throw "Node must have an id"; throw "Node must have an id";
} }
if (options.x !== undefined) {this.x = options.x; this.predefinedPosition = true;}
if (options.y !== undefined) {this.y = options.y; this.predefinedPosition = true;}
if (options.x !== undefined) {this.x = parseInt(options.x); this.predefinedPosition = true;}
if (options.y !== undefined) {this.y = parseInt(options.y); this.predefinedPosition = true;}
if (options.size !== undefined) {this.baseSize = options.size;} if (options.size !== undefined) {this.baseSize = options.size;}
if (options.value !== undefined) {options.value = parseInt(options.value);}
// this transforms all shorthands into fully defined options // this transforms all shorthands into fully defined options
Node.parseOptions(this.options,options); Node.parseOptions(this.options,options);

+ 22
- 1
lib/util.js View File

@ -489,7 +489,7 @@ exports.getType = function(object) {
var type = typeof object; var type = typeof object;
if (type == 'object') { if (type == 'object') {
if (object == null) {
if (object === null) {
return 'null'; return 'null';
} }
if (object instanceof Boolean) { if (object instanceof Boolean) {
@ -518,10 +518,31 @@ exports.getType = function(object) {
else if (type == 'string') { else if (type == 'string') {
return 'String'; return 'String';
} }
else if (type === undefined) {
return 'undefined';
}
return type; return type;
}; };
/**
* Used to extend an array and copy it. This is used to propagate paths recursively.
*
* @param arr
* @param newValue
* @returns {Array}
*/
exports.copyAndExtendArray = function(arr,newValue) {
let newArr = [];
for (let i = 0; i < arr.length; i++) {
newArr.push(arr[i]);
}
newArr.push(newValue);
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