Browse Source

Merge branch 'v4' into es6

flowchartTest
jos 9 years ago
parent
commit
d24d45c444
18 changed files with 5648 additions and 5389 deletions
  1. +16
    -1
      HISTORY.md
  2. +1
    -1
      bower.json
  3. +3
    -5
      dist/vis.css
  4. +5414
    -5318
      dist/vis.js
  5. +1
    -1
      dist/vis.map
  6. +1
    -1
      dist/vis.min.css
  7. +16
    -16
      dist/vis.min.js
  8. +16
    -0
      docs/dataview.html
  9. +2
    -2
      examples/timeline/18_range_overflow.html
  10. +6
    -2
      lib/DataSet.js
  11. +42
    -0
      lib/DataView.js
  12. +18
    -9
      lib/network/Images.js
  13. +6
    -2
      lib/network/Network.js
  14. +46
    -24
      lib/network/mixins/ClusterMixin.js
  15. +3
    -1
      lib/network/mixins/ManipulationMixin.js
  16. +3
    -5
      lib/timeline/component/css/item.css
  17. +1
    -1
      package.json
  18. +53
    -0
      test/DataView.test.js

+ 16
- 1
HISTORY.md View File

@ -2,16 +2,31 @@
http://visjs.org
## not yet released, version 4.0.0-SNAPSHOT
### Timeline
- Fixed range items not being displayed smaller than 10 pixels (twice the
padding). In order to have overflowing text, one should now apply css style
`.vis.timeline .item.range { overflow: visible; }` instead of
`.vis.timeline .item.range .content { overflow: visible; }`.
See example 18_range_overflow.html.
## not yet released, version 3.9.2-SNAPSHOT
### Network
- Added option bindToWindow (default true) to choose whether the keyboard binds are global or to the network div.
- Improved images handling so broken images are shown on all references of images that are broken.
### DataSet
### DataSet/DataView
- Added property `length` holding the total number of items to the `DataSet`
and `DataView`.
- Added a method `refresh()` to the `DataView`, to update filter results.
- Fixed a bug in the `DataSet` returning an empty object instead of `null` when
no item was found when using both a filter and specifying fields.
### Timeline

+ 1
- 1
bower.json View File

@ -1,6 +1,6 @@
{
"name": "vis",
"version": "3.9.2-SNAPSHOT",
"version": "4.0.0-SNAPSHOT",
"main": ["dist/vis.min.js", "dist/vis.min.css"],
"description": "A dynamic, browser-based visualization library.",
"homepage": "http://visjs.org/",

+ 3
- 5
dist/vis.css View File

@ -173,7 +173,7 @@
border-width: 1px;
background-color: #D5DDF6;
display: inline-block;
padding: 5px;
overflow: hidden;
}
.vis.timeline .item.selected {
@ -217,7 +217,6 @@
}
.vis.timeline .item.background {
overflow: hidden;
border: none;
background-color: rgba(213, 221, 246, 0.4);
box-sizing: border-box;
@ -229,13 +228,11 @@
position: relative;
display: inline-block;
max-width: 100%;
overflow: hidden;
}
.vis.timeline .item.background .content {
position: absolute;
display: inline-block;
overflow: hidden;
max-width: 100%;
margin: 5px;
}
@ -250,7 +247,8 @@
.vis.timeline .item .content {
white-space: nowrap;
overflow: hidden;
box-sizing: border-box;
padding: 5px;
}
.vis.timeline .item .delete {

+ 5414
- 5318
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


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


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


+ 16
- 0
docs/dataview.html View File

@ -219,6 +219,22 @@ var data = new vis.DataView(dataset, options)
</td>
</tr>
<tr>
<td>refresh()</td>
<td>none</td>
<td>
Refresh the filter results of a DataView. Useful when the filter function contains dynamic properties, like:
<pre class="prettyprint lang-js">var data = new vis.DataSet(...);
var view = new vis.DataView(data, {
filter: function (item) {
return item.value > threshold;
}
});</pre>
In this example, <code>threshold</code> is an external parameter. When the value of <code>threshold</code> changes, the DataView must be notified that the filter results may have changed by calling <code>DataView.refresh()</code>.
</td>
</tr>
<tr>
<td>
setDataSet(data)

+ 2
- 2
examples/timeline/18_range_overflow.html View File

@ -11,7 +11,7 @@
font-family: sans-serif;
}
.vis.timeline .item.range .content {
.vis.timeline .item.range {
overflow: visible;
}
</style>
@ -22,7 +22,7 @@
In case of ranges being spread over a wide range of time, it can be interesting to have the text contents of the ranges overflow the box. This can be achieved by changing the overflow property of the contents to visible with css:
</p>
<pre>
.vis.timeline .item.range .content {
.vis.timeline .item.range {
overflow: visible;
}
</pre>

+ 6
- 2
lib/DataSet.js View File

@ -649,12 +649,16 @@ DataSet.prototype.map = function (callback, options) {
/**
* Filter the fields of an item
* @param {Object} item
* @param {Object | null} item
* @param {String[]} fields Field names
* @return {Object} filteredItem
* @return {Object | null} filteredItem or null if no item is provided
* @private
*/
DataSet.prototype._filterFields = function (item, fields) {
if (!item) { // item is null
return item;
}
var filteredItem = {};
for (var field in item) {

+ 42
- 0
lib/DataView.js View File

@ -79,6 +79,48 @@ DataView.prototype.setData = function (data) {
}
};
/**
* Refresh the DataView. Useful when the DataView has a filter function
* containing a variable parameter.
*/
DataView.prototype.refresh = function () {
var id;
var ids = this._data.getIds({filter: this._options && this._options.filter});
var newIds = {};
var added = [];
var removed = [];
// check for additions
for (var i = 0; i < ids.length; i++) {
id = ids[i];
newIds[id] = true;
if (!this._ids[id]) {
added.push(id);
this._ids[id] = true;
this.length++;
}
}
// check for removals
for (id in this._ids) {
if (this._ids.hasOwnProperty(id)) {
if (!newIds[id]) {
removed.push(id);
delete this._ids[id];
this.length--;
}
}
}
// trigger events
if (added.length) {
this._trigger('add', {items: added});
}
if (removed.length) {
this._trigger('remove', {items: removed});
}
};
/**
* Get data from the data view
*

+ 18
- 9
lib/network/Images.js View File

@ -52,16 +52,25 @@ Images.prototype.load = function(url, brokenUrl) {
me.callback(this);
}
}
else if (me.imageBroken[url] === true) {
console.error("Could not load brokenImage:", brokenUrl);
delete this.src;
if (me.callback) {
me.callback(this);
}
}
else {
this.src = brokenUrl;
me.imageBroken[url] = true;
if (me.imageBroken[url] === true) {
if (this.src == brokenUrl) {
console.error("Could not load brokenImage:", brokenUrl);
delete this.src;
if (me.callback) {
me.callback(this);
}
}
else {
console.error("Could not load image:", url);
this.src = brokenUrl;
}
}
else {
console.error("Could not load image:", url);
this.src = brokenUrl;
me.imageBroken[url] = true;
}
}
};

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

@ -164,7 +164,8 @@ function Network (container, data, options) {
radius: 1}, // (px PNiC) | growth of the radius per node in cluster.
maxNodeSizeIncrements: 600, // (# increments) | max growth of the width per node in cluster.
activeAreaBoxSize: 80, // (px) | box area around the curser where clusters are popped open.
clusterLevelDifference: 2
clusterLevelDifference: 2,
clusterByZoom: true // enable clustering through zooming in and out
},
navigation: {
enabled: false
@ -873,7 +874,10 @@ Network.prototype._createKeyBinds = function() {
this.keycharm.bind("pagedown",this._zoomOut.bind(me),"keydown");
this.keycharm.bind("pagedown",this._stopZoom.bind(me), "keyup");
}
this.keycharm.bind("1",this.increaseClusterLevel.bind(me), "keydown");
this.keycharm.bind("2",this.decreaseClusterLevel.bind(me), "keydown");
this.keycharm.bind("3",this.forceAggregateHubs.bind(me,true),"keydown");
this.keycharm.bind("4",this.normalizeClusterLevels.bind(me), "keydown");
if (this.constants.dataManipulation.enabled == true) {
this.keycharm.bind("esc",this._createManipulatorBar.bind(me));
this.keycharm.bind("delete",this._deleteSelected.bind(me));

+ 46
- 24
lib/network/mixins/ClusterMixin.js View File

@ -32,22 +32,24 @@ exports.startWithClustering = function() {
exports.clusterToFit = function(maxNumberOfNodes, reposition) {
var numberOfNodes = this.nodeIndices.length;
var maxLevels = 50;
var maxLevels = 2;
var level = 0;
// we first cluster the hubs, then we pull in the outliers, repeat
while (numberOfNodes > maxNumberOfNodes && level < maxLevels) {
if (level % 3 == 0) {
this.forceAggregateHubs(true);
this.normalizeClusterLevels();
console.log("Performing clustering:", level, numberOfNodes, this.clusterSession);
if (level % 3 == 0.0) {
//this.forceAggregateHubs(true);
//this.normalizeClusterLevels();
}
else {
this.increaseClusterLevel(); // this also includes a cluster normalization
//this.increaseClusterLevel(); // this also includes a cluster normalization
}
//this.forceAggregateHubs(true);
numberOfNodes = this.nodeIndices.length;
level += 1;
}
console.log("finished")
// after the clustering we reposition the nodes to reduce the initial chaos
if (level > 0 && reposition == true) {
@ -98,7 +100,7 @@ exports.openCluster = function(node) {
* This calls the updateClustes with default arguments
*/
exports.updateClustersDefault = function() {
if (this.constants.clustering.enabled == true) {
if (this.constants.clustering.enabled == true && this.constants.clustering.clusterByZoom == true) {
this.updateClusters(0,false,false);
}
};
@ -224,7 +226,7 @@ exports._aggregateHubs = function(force) {
/**
* This function is fired by keypress. It forces hubs to form.
* This function forces hubs to form.
*
*/
exports.forceAggregateHubs = function(doNotStart) {
@ -235,6 +237,7 @@ exports.forceAggregateHubs = function(doNotStart) {
// update the index list, dynamic edges and labels
this._updateNodeIndexList();
this._updateCalculationNodes();
this._updateDynamicEdges();
this.updateLabels();
@ -347,7 +350,7 @@ exports._expandClusterNode = function(parentNode, recursive, force, openAll) {
* @private
*/
exports._expelChildFromParent = function(parentNode, containedNodeId, recursive, force, openAll) {
var childNode = parentNode.containedNodes[containedNodeId];
var childNode = parentNode.containedNodes[containedNodeId]
// if child node has been added on smaller scale than current, kick out
if (childNode.formationScale < this.scale || force == true) {
@ -502,10 +505,10 @@ exports._forceClustersByZoom = function() {
var childNode = this.nodes[nodeId];
// the edges can be swallowed by another decrease
if (childNode.dynamicEdgesLength == 1 && childNode.dynamicEdges.length != 0) {
var edge = childNode.dynamicEdges[0];
var parentNode = (edge.toId == childNode.id) ? this.nodes[edge.fromId] : this.nodes[edge.toId];
// group to the largest node
if (childNode.id != parentNode.id) {
if (parentNode.options.mass > childNode.options.mass) {
@ -585,9 +588,14 @@ exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSize
if (absorptionSizeOffset === undefined) {
absorptionSizeOffset = 0;
}
if (hubNode.dynamicEdgesLength < 0) {
console.error(hubNode.dynamicEdgesLength, this.hubThreshold, onlyEqual)
}
// we decide if the node is a hub
if ((hubNode.dynamicEdgesLength >= this.hubThreshold && onlyEqual == false) ||
(hubNode.dynamicEdgesLength == this.hubThreshold && onlyEqual == true)) {
// initialize variables
var dx,dy,length;
var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
@ -600,7 +608,7 @@ exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSize
edgesIdarray.push(hubNode.dynamicEdges[j].id);
}
// if the hub clustering is not forces, we check if one of the edges connected
// if the hub clustering is not forced, we check if one of the edges connected
// to a cluster is small enough based on the constants.clustering.clusterEdgeThreshold
if (force == false) {
allowCluster = false;
@ -625,17 +633,24 @@ exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSize
// start the clustering if allowed
if ((!force && allowCluster) || force) {
// we loop over all edges INITIALLY connected to this hub
var children = [];
var childrenIds = {};
// we loop over all edges INITIALLY connected to this hub to get a list of the childNodes
for (j = 0; j < amountOfInitialEdges; j++) {
edge = this.edges[edgesIdarray[j]];
// the edge can be clustered by this function in a previous loop
if (edge !== undefined) {
var childNode = this.nodes[(edge.fromId == hubNode.id) ? edge.toId : edge.fromId];
// we do not want hubs to merge with other hubs nor do we want to cluster itself.
if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) &&
(childNode.id != hubNode.id)) {
this._addToCluster(hubNode,childNode,force);
}
var childNode = this.nodes[(edge.fromId == hubNode.id) ? edge.toId : edge.fromId];
if (childrenIds[childNode.id] === undefined) {
childrenIds[childNode.id] = true;
children.push(childNode);
}
}
for (j = 0; j < children.length; j++) {
var childNode = children[j];
// we do not want hubs to merge with other hubs nor do we want to cluster itself.
if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) &&
(childNode.id != hubNode.id)) {
this._addToCluster(hubNode,childNode,force);
}
}
}
@ -655,14 +670,16 @@ exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSize
exports._addToCluster = function(parentNode, childNode, force) {
// join child node in the parent node
parentNode.containedNodes[childNode.id] = childNode;
//console.log(parentNode.id, childNode.id)
// manage all the edges connected to the child and parent nodes
for (var i = 0; i < childNode.dynamicEdges.length; i++) {
var edge = childNode.dynamicEdges[i];
if (edge.toId == parentNode.id || edge.fromId == parentNode.id) { // edge connected to parentNode
//console.log("COLLECT",parentNode.id, childNode.id, edge.toId, edge.fromId)
this._addToContainedEdges(parentNode,childNode,edge);
}
else {
//console.log("REWIRE",parentNode.id, childNode.id, edge.toId, edge.fromId)
this._connectEdgeToCluster(parentNode,childNode,edge);
}
}
@ -690,7 +707,6 @@ exports._addToCluster = function(parentNode, childNode, force) {
// forced clusters only open from screen size and double tap
if (force == true) {
// parentNode.formationScale = Math.pow(1 - (1.0/11.0),this.clusterSession+3);
parentNode.formationScale = 0;
}
else {
@ -739,6 +755,10 @@ exports._updateDynamicEdges = function() {
}
}
}
if (node.dynamicEdgesLength < correction) {
console.error("overshoot", node.dynamicEdgesLength, correction)
}
node.dynamicEdgesLength -= correction;
}
};
@ -894,12 +914,14 @@ exports._connectEdgeBackToChild = function(parentNode, childNode) {
* @private
*/
exports._validateEdges = function(parentNode) {
var dynamicEdges = []
for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
var edge = parentNode.dynamicEdges[i];
if (parentNode.id != edge.toId && parentNode.id != edge.fromId) {
parentNode.dynamicEdges.splice(i,1);
if (parentNode.id == edge.toId || parentNode.id == edge.fromId) {
dynamicEdges.push(edge);
}
}
parentNode.dynamicEdges = dynamicEdges;
};

+ 3
- 1
lib/network/mixins/ManipulationMixin.js View File

@ -301,10 +301,12 @@ exports._createAddEdgeToolbar = function() {
this.cachedFunctions["_manipulationReleaseOverload"] = this._manipulationReleaseOverload;
this.cachedFunctions["_handleDragStart"] = this._handleDragStart;
this.cachedFunctions["_handleDragEnd"] = this._handleDragEnd;
this.cachedFunctions["_handleOnHold"] = this._handleOnHold;
this._handleTouch = this._handleConnect;
this._manipulationReleaseOverload = function () {};
this._handleOnHold = function () {};
this._handleDragStart = function () {};
this._handleDragEnd = this._finishConnect;
this._handleDragEnd = this._finishConnect;
// redraw to show the unselect
this._redraw();

+ 3
- 5
lib/timeline/component/css/item.css View File

@ -6,7 +6,7 @@
border-width: 1px;
background-color: #D5DDF6;
display: inline-block;
padding: 5px;
overflow: hidden;
}
.vis.timeline .item.selected {
@ -50,7 +50,6 @@
}
.vis.timeline .item.background {
overflow: hidden;
border: none;
background-color: rgba(213, 221, 246, 0.4);
box-sizing: border-box;
@ -62,13 +61,11 @@
position: relative;
display: inline-block;
max-width: 100%;
overflow: hidden;
}
.vis.timeline .item.background .content {
position: absolute;
display: inline-block;
overflow: hidden;
max-width: 100%;
margin: 5px;
}
@ -83,7 +80,8 @@
.vis.timeline .item .content {
white-space: nowrap;
overflow: hidden;
box-sizing: border-box;
padding: 5px;
}
.vis.timeline .item .delete {

+ 1
- 1
package.json View File

@ -1,6 +1,6 @@
{
"name": "vis",
"version": "3.9.2-SNAPSHOT",
"version": "4.0.0-SNAPSHOT",
"description": "A dynamic, browser-based visualization library.",
"homepage": "http://visjs.org/",
"repository": {

+ 53
- 0
test/DataView.test.js View File

@ -96,4 +96,57 @@ describe('DataView', function () {
assert.equal(group2.length, 0);
});
it('should refresh a DataView with filter', function () {
var data = new DataSet([
{id:1, value:2},
{id:2, value:4},
{id:3, value:7}
]);
var threshold = 5;
// create a view. The view has a filter with a dynamic property `threshold`
var view = new DataView(data, {
filter: function (item) {
return item.value < threshold;
}
});
var added, updated, removed;
view.on('add', function (event, props) {added = added.concat(props.items)});
view.on('update', function (event, props) {updated = updated.concat(props.items)});
view.on('remove', function (event, props) {removed = removed.concat(props.items)});
assert.deepEqual(view.get(), [
{id:1, value:2},
{id:2, value:4}
]);
// change the threshold to 3
added = [];
updated = [];
removed = [];
threshold = 3;
view.refresh();
assert.deepEqual(view.get(), [{id:1, value:2}]);
assert.deepEqual(added, []);
assert.deepEqual(updated, []);
assert.deepEqual(removed, [2]);
// change threshold to 8
added = [];
updated = [];
removed = [];
threshold = 8;
view.refresh();
assert.deepEqual(view.get(), [
{id:1, value:2},
{id:2, value:4},
{id:3, value:7}
]);
assert.deepEqual(added, [2, 3]);
assert.deepEqual(updated, []);
assert.deepEqual(removed, []);
})
});

Loading…
Cancel
Save