Browse Source

Merge branch 'develop' of https://github.com/almende/vis into develop

revert-3409-performance
Yotam Berkowitz 7 years ago
parent
commit
af16ffcc18
20 changed files with 530 additions and 195 deletions
  1. +1
    -1
      .github/PULL_REQUEST_TEMPLATE.md
  2. +71
    -0
      HISTORY.md
  3. +31
    -11
      docs/timeline/index.html
  4. +4
    -1
      examples/timeline/interaction/rollingMode.html
  5. +2
    -3
      lib/network/gephiParser.js
  6. +2
    -2
      lib/network/modules/EdgesHandler.js
  7. +93
    -25
      lib/network/modules/KamadaKawai.js
  8. +19
    -7
      lib/network/modules/LayoutEngine.js
  9. +18
    -9
      lib/network/modules/components/algorithms/FloydWarshall.js
  10. +41
    -11
      lib/timeline/Core.js
  11. +60
    -24
      lib/timeline/Range.js
  12. +36
    -37
      lib/timeline/TimeStep.js
  13. +2
    -2
      lib/timeline/Timeline.js
  14. +2
    -2
      lib/timeline/component/CustomTime.js
  15. +71
    -39
      lib/timeline/component/Group.js
  16. +16
    -9
      lib/timeline/component/ItemSet.js
  17. +5
    -1
      lib/timeline/optionsTimeline.js
  18. +1
    -0
      misc/we_need_help.md
  19. +11
    -11
      package.json
  20. +44
    -0
      test/TimeStep.test.js

+ 1
- 1
.github/PULL_REQUEST_TEMPLATE.md View File

@ -3,7 +3,7 @@
Please make sure to check the following requirements before creating a pull request:
* [ ] All pull requests must be to the [develop branch](https://github.com/almende/vis/tree/develop). Pull requests to the `master` branch will be closed!
* [ ] Make sure your changes are based on the latest version of the [develop branch](https://github.com/almende/vis/tree/develop). (Use e.g. `git fetch && git rebase origin develop` to update you feature branch).
* [ ] Make sure your changes are based on the latest version of the [develop branch](https://github.com/almende/vis/tree/develop). (Use e.g. `git fetch && git rebase origin develop` to update your feature branch).
* [ ] Provide an additional or update an example to demonstrate your changes or new features.
* [ ] Update the documentation if you introduced new behavior or changed existing behavior.
* [ ] Reference issue numbers of issues that your pull request addresses. (If you write something like `fixes #1781` in your git commit message this issue gets closed automatically by merging your pull request).

+ 71
- 0
HISTORY.md View File

@ -1,6 +1,77 @@
# vis.js history
http://visjs.org
## 2017-03-19, version 4.19.1
### General
* FIX: #2685 Fixed babel dependencies (#2875)
### Timeline / Graph2D
* FIX #2809: Fix docs typo in "showNested" (#2879)
* FIX #2594: Fixes for removing and adding items to subgroups (#2821)
* FIX: Allow nested groups to be removed (#2852)
## 2017-03-18, version 4.19.0
### General
- FIX: Fix eslint problem on Travis. (#2744)
- added support for eslint (#2695)
- Trivial typo fix in how_to_help doc. (#2714)
- add link to a mentioned example (#2709)
- FEAT: use babel preset2015 for custom builds (#2678)
- FIX: use babel version compatible with webpack@1.14 (#2693)
- FEAT: run mocha tests in travis ci (#2687)
- Add note that PRs should be submitted against the `develop` branch (#2623)
- FIX: Fixes instanceof Object statements for objects from other windows and iFrames. (#2631)
- removed google-analytics from all examples (#2670)
- do not ignore test folder (#2648)
- updated dependencies and devDependencies (#2649)
- general improvements (#2652)
### Network
- FEAT: Improve the performance of the network layout engine (#2729)
- FEAT: Allow for image nodes to have a selected or broken image (#2601)
### Timeline / Graph2D
- FIX #2842: Prevent redirect to blank after drag and drop in FF (#2871)
- FIX #2810: Nested groups do not use "groupOrder" (#2817)
- FIX #2795: fix date for custom format function (#2826)
- FIX #2689: Add animation options for zoomIn/zoomOut funtions (#2830)
- FIX #2800: Removed all "Object.assign" from examples (#2829)
- FIX #2725: Background items positioning when orientation: top (#2831)
- FEAT: Added data as argument to the template function (#2802)
- FIX #2827: Update "progress bar" example to reflect values (#2828)
- FIX #2672: Item events original event (#2704)
- FIX #2696: Update serialization example to use ISOString dates (#2789)
- FIX #2790: Update examples to use ISOString format (#2791)
- FEAT: Added support to supply an end-time to bar charts to have them scale (#2760)
- FIX #1982, #1417: Modify redraw logic to treat scroll as needing restack (#2774)
- FEAT: Initial tests for timeline ItemSet (#2750)
- FIX #2720: Problems with option editable (#2743, #2796, #2806)
- FIX: Range.js "event" is undeclared (#2749)
- FEAT: added new locales for french and espanol (#2723)
- FIX: fixes timestep next issue (#2732)
- FEAT: #2647 Dynamic rolling mode option (#2705)
- FIX #2679: TypeError: Cannot read property 'hasOwnProperty' of null (#2735)
- Add initial tests for Timeline PointItem (#2716)
- FIX #778: Tooltip does not work with background items in timeline (#2703)
- FIX #2598: Flickering onUpdateTimeTooltip (#2702)
- FEAT: refactor tooltip to only use one dom-element (#2662)
- FEAT: Change setCustomTimeTitle title parameter to be a string or a function (#2611)
### Graph3D
- FEAT #2769: Graph3d tooltip styling (#2780)
- FEAT #2540: Adjusted graph3d doc for autoscaling (#2812)
- FIX #2536: 3d bar graph data array unsorted (#2803)
- FEAT: Added showX(YZ)Axis options to Graph3d (#2686)
## 2017-01-29, version 4.18.1

+ 31
- 11
docs/timeline/index.html View File

@ -468,10 +468,10 @@ var groups = [
<td>Array of group ids nested in the group. Nested groups will appear under this nesting group.</td>
</tr>
<tr>
<td>showNestedGroups</td>
<td>showNested</td>
<td>Boolean</td>
<td>no</td>
<td>Assuming the group has nested groups, this will set the initial state of the group - shown or collapsed. The <code>showNestedGroups</code> is defaulted to <code>true</code>.</td>
<td>Assuming the group has nested groups, this will set the initial state of the group - shown or collapsed. The <code>showNested</code> is defaulted to <code>true</code>.</td>
</tr>
</table>
@ -955,13 +955,29 @@ function (option, path) {
<td>Orientation of the timeline items: 'top' or 'bottom' (default). Determines whether items are aligned to the top or bottom of the Timeline.</td>
</tr>
<tr>
<td>rollingMode</td>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','rollingMode', this);">
<td><span parent="rollingMode" class="right-caret"></span> rollingMode</td>
<td>Object</td>
<td><code>Object</code></td>
<td>Specify how the timeline implements rolling mode.</td>
</tr>
<tr parent="rollingMode" class="hidden">
<td class="indent">rollingMode.follow</td>
<td>boolean</td>
<td><code>false</code></td>
<td>If true, the timeline will initial in a rolling mode - the current time will always be centered. I the user drags the timeline, the timeline will go out of rolling mode and a toggle button will appear. Clicking that button will go back to rolling mode. Zooming in rolling mode will zoom in to the center without consideration of the mouse position.</td>
</tr>
<tr parent="rollingMode" class="hidden">
<td class="indent">rollingMode.offset</td>
<td>Number</td>
<td><code>'0.5'</code></td>
<td>
Set how far from the left the rolling mode is implemented from. A percentage (i.e. a decimal between 0 and 1)
Defaults to the middle or 0.5 (50%)
</td>
</tr>
<tr>
<td>rtl</td>
<td>boolean</td>
@ -1055,11 +1071,11 @@ function (option, path) {
This would be used as an additional way to add content that is constant in size with the visible frame of the item and does not get visibly hidden with the item's internal container: <code>vis-item-overflow</code> which is <code>overflow:hidden</code>.</td>
</tr>
<tr class='depricated'>
<tr class='deprecated'>
<td>throttleRedraw</td>
<td>number</td>
<td><code>0</code></td>
<td>This option is <b>DEPRICATED</b> and no longer supported. It will be removed in the next MAJOR release.</td>
<td>This option is <b>DEPRECATED</b> and no longer supported. It will be removed in the next MAJOR release.</td>
</tr>
<tr class='toggle collapsible' onclick="toggleTable('optionTable','timeAxis', this);">
@ -1308,12 +1324,13 @@ document.getElementById('myTimeline').onclick = function (event) {
</tr>
<tr>
<td>moveTo(time [, options])</td>
<td>moveTo(time [, options, callback])</td>
<td>none</td>
<td>Move the window such that given time is centered on screen. Parameter <code>time</code> can be a <code>Date</code>, <code>Number</code>, or <code>String</code>. Available options:
<ul>
<li><code>animation: boolean or {duration: number, easingFunction: string}</code><br>If true (default) or an Object, the range is animated smoothly to the new window. An object can be provided to specify duration and easing function. Default duration is 500 ms, and default easing function is <code>'easeInOutQuad'</code>. Available easing functions: <code>"linear"</code>, <code>"easeInQuad"</code>, <code>"easeOutQuad"</code>, <code>"easeInOutQuad"</code>, <code>"easeInCubic"</code>, <code>"easeOutCubic"</code>, <code>"easeInOutCubic"</code>, <code>"easeInQuart"</code>, <code>"easeOutQuart"</code>, <code>"easeInOutQuart"</code>, <code>"easeInQuint"</code>, <code>"easeOutQuint"</code>, <code>"easeInOutQuint"</code>.</li>
</ul>
A callback <code>function</code> can be passed as an optional parameter. This function will be called at the end of moveTo function.
</td>
</tr>
@ -1422,12 +1439,13 @@ document.getElementById('myTimeline').onclick = function (event) {
</tr>
<tr>
<td>setWindow(start, end [, options])</td>
<td>setWindow(start, end [, options, callback])</td>
<td>none</td>
<td>Set the current visible window. The parameters <code>start</code> and <code>end</code> can be a <code>Date</code>, <code>Number</code>, or <code>String</code>. If the parameter value of <code>start</code> or <code>end</code> is null, the parameter will be left unchanged. Available options:
<ul>
<li><code>animation: boolean or {duration: number, easingFunction: string}</code><br>If true (default) or an Object, the range is animated smoothly to the new window. An object can be provided to specify duration and easing function. Default duration is 500 ms, and default easing function is <code>'easeInOutQuad'</code>. Available easing functions: <code>"linear"</code>, <code>"easeInQuad"</code>, <code>"easeOutQuad"</code>, <code>"easeInOutQuad"</code>, <code>"easeInCubic"</code>, <code>"easeOutCubic"</code>, <code>"easeInOutCubic"</code>, <code>"easeInQuart"</code>, <code>"easeOutQuart"</code>, <code>"easeInOutQuart"</code>, <code>"easeInQuint"</code>, <code>"easeOutQuint"</code>, <code>"easeInOutQuint"</code>.</li>
</ul>
A callback <code>function</code> can be passed as an optional parameter. This function will be called at the end of setWindow function.
</td>
</tr>
@ -1439,21 +1457,23 @@ document.getElementById('myTimeline').onclick = function (event) {
</tr>
<tr>
<td>zoomIn(percentage [, options])</td>
<td>zoomIn(percentage [, options, callback])</td>
<td>none</td>
<td>Zoom in the current visible window. The parameter <code>percentage</code> can be a <code>Number</code> and must be between 0 and 1. If the parameter value of <code>percentage</code> is null, the window will be left unchanged. Available options:
<ul>
<li><code>animation: boolean or {duration: number, easingFunction: string}</code><br>If true (default) or an Object, the range is animated smoothly to the new window. An object can be provided to specify duration and easing function. Default duration is 500 ms, and default easing function is <code>'easeInOutQuad'</code>. Available easing functions: <code>"linear"</code>, <code>"easeInQuad"</code>, <code>"easeOutQuad"</code>, <code>"easeInOutQuad"</code>, <code>"easeInCubic"</code>, <code>"easeOutCubic"</code>, <code>"easeInOutCubic"</code>, <code>"easeInQuart"</code>, <code>"easeOutQuart"</code>, <code>"easeInOutQuart"</code>, <code>"easeInQuint"</code>, <code>"easeOutQuint"</code>, <code>"easeInOutQuint"</code>.</li>
</ul>
A callback <code>function</code> can be passed as an optional parameter. This function will be called at the end of zoomIn function.
</td>
</tr>
<tr>
<td>zoomOut(percentage [, options])</td>
<td>zoomOut(percentage [, options, callback])</td>
<td>none</td>
<td>Zoom out the current visible window. The parameter <code>percentage</code> can be a <code>Number</code> and must be between 0 and 1. If the parameter value of <code>percentage</code> is null, the window will be left unchanged. Available options:
<ul>
<li><code>animation: boolean or {duration: number, easingFunction: string}</code><br>If true (default) or an Object, the range is animated smoothly to the new window. An object can be provided to specify duration and easing function. Default duration is 500 ms, and default easing function is <code>'easeInOutQuad'</code>. Available easing functions: <code>"linear"</code>, <code>"easeInQuad"</code>, <code>"easeOutQuad"</code>, <code>"easeInOutQuad"</code>, <code>"easeInCubic"</code>, <code>"easeOutCubic"</code>, <code>"easeInOutCubic"</code>, <code>"easeInQuart"</code>, <code>"easeOutQuart"</code>, <code>"easeInOutQuart"</code>, <code>"easeInQuint"</code>, <code>"easeOutQuint"</code>, <code>"easeInOutQuint"</code>.</li>
</ul>
A callback <code>function</code> can be passed as an optional parameter. This function will be called at the end of zoomOut function.
</td>
</tr>
@ -1991,7 +2011,7 @@ var options = {
<td>Weekday</td><td><code>vis-monday</code>, <code>vis-tuesday</code>, <code>vis-wednesday</code>, <code>vis-thursday</code>, <code>vis-friday</code>, <code>vis-saturday</code>, <code>vis-sunday</code></td>
</tr>
<tr>
<td>Days</td><td><code>vis-date1</code>, <code>vis-date2</code>, ..., <code>vis-date31</code></td>
<td>Days</td><td><code>vis-day1</code>, <code>vis-day2</code>, ..., <code>vis-day31</code></td>
</tr>
<tr>
<td>Months</td><td><code>vis-january</code>, <code>vis-february</code>, <code>vis-march</code>, <code>vis-april</code>, <code>vis-may</code>, <code>vis-june</code>, <code>vis-july</code>, <code>vis-august</code>, <code>vis-september</code>, <code>vis-october</code>, <code>vis-november</code>, <code>vis-december</code></td>

+ 4
- 1
examples/timeline/interaction/rollingMode.html View File

@ -33,7 +33,10 @@
var options = {
start: new Date(),
end: new Date(new Date().getTime() + 1000000),
rollingMode: true
rollingMode: {
follow: true,
offset: 0.5
}
};
// create a Timeline

+ 2
- 3
lib/network/gephiParser.js View File

@ -45,11 +45,10 @@ function parseGephi(gephiJSON, optionsObj) {
var gNode = gNodes[i];
node['id'] = gNode.id;
node['attributes'] = gNode.attributes;
node['title'] = gNode.title;
node['x'] = gNode.x;
node['y'] = gNode.y;
node['label'] = gNode.label;
node['title'] = gNode.attributes !== undefined ? gNode.attributes.title : undefined;
node['title'] = gNode.attributes !== undefined ? gNode.attributes.title : gNode.title;
if (options.nodes.parseColor === true) {
node['color'] = gNode.color;
}
@ -64,4 +63,4 @@ function parseGephi(gephiJSON, optionsObj) {
return {nodes:nodes, edges:edges};
}
exports.parseGephi = parseGephi;
exports.parseGephi = parseGephi;

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

@ -385,8 +385,8 @@ class EdgesHandler {
let nodeList = [];
if (this.body.edges[edgeId] !== undefined) {
let edge = this.body.edges[edgeId];
if (edge.fromId) {nodeList.push(edge.fromId);}
if (edge.toId) {nodeList.push(edge.toId);}
if (edge.fromId !== undefined) {nodeList.push(edge.fromId);}
if (edge.toId !== undefined) {nodeList.push(edge.toId);}
}
return nodeList;
}

+ 93
- 25
lib/network/modules/KamadaKawai.js View File

@ -49,11 +49,14 @@ class KamadaKawai {
// get the K Matrix
this._createK_matrix(D_matrix);
// initial E Matrix
this._createE_matrix();
// calculate positions
let threshold = 0.01;
let innerThreshold = 1;
let iterations = 0;
let maxIterations = Math.max(1000,Math.min(10*this.body.nodeIndices.length,6000));
let maxIterations = Math.max(1000, Math.min(10 * this.body.nodeIndices.length, 6000));
let maxInnerIterations = 5;
let maxEnergy = 1e9;
@ -64,10 +67,10 @@ class KamadaKawai {
[highE_nodeId, maxEnergy, dE_dx, dE_dy] = this._getHighestEnergyNode(ignoreClusters);
delta_m = maxEnergy;
subIterations = 0;
while(delta_m > innerThreshold && subIterations < maxInnerIterations) {
while (delta_m > innerThreshold && subIterations < maxInnerIterations) {
subIterations += 1;
this._moveNode(highE_nodeId, dE_dx, dE_dy);
[delta_m,dE_dx,dE_dy] = this._getEnergy(highE_nodeId);
[delta_m, dE_dx, dE_dy] = this._getEnergy(highE_nodeId);
}
}
}
@ -87,7 +90,7 @@ class KamadaKawai {
for (let nodeIdx = 0; nodeIdx < nodesArray.length; nodeIdx++) {
let m = nodesArray[nodeIdx];
// by not evaluating nodes with predefined positions we should only move nodes that have no positions.
if ((nodes[m].predefinedPosition === false || nodes[m].isCluster === true && ignoreClusters === true) || nodes[m].options.fixed.x === true || nodes[m].options.fixed.y === true) {
if ((nodes[m].predefinedPosition === false || nodes[m].isCluster === true && ignoreClusters === true) || nodes[m].options.fixed.x === true || nodes[m].options.fixed.y === true) {
let [delta_m,dE_dx,dE_dy] = this._getEnergy(m);
if (maxEnergy < delta_m) {
maxEnergy = delta_m;
@ -108,24 +111,7 @@ class KamadaKawai {
* @private
*/
_getEnergy(m) {
let nodesArray = this.body.nodeIndices;
let nodes = this.body.nodes;
let x_m = nodes[m].x;
let y_m = nodes[m].y;
let dE_dx = 0;
let dE_dy = 0;
for (let iIdx = 0; iIdx < nodesArray.length; iIdx++) {
let i = nodesArray[iIdx];
if (i !== m) {
let x_i = nodes[i].x;
let y_i = nodes[i].y;
let denominator = 1.0 / Math.sqrt(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2));
dE_dx += this.K_matrix[m][i] * ((x_m - x_i) - this.L_matrix[m][i] * (x_m - x_i) * denominator);
dE_dy += this.K_matrix[m][i] * ((y_m - y_i) - this.L_matrix[m][i] * (y_m - y_i) * denominator);
}
}
let [dE_dx,dE_dy] = this.E_sums[m];
let delta_m = Math.sqrt(Math.pow(dE_dx, 2) + Math.pow(dE_dy, 2));
return [delta_m, dE_dx, dE_dy];
}
@ -147,15 +133,20 @@ class KamadaKawai {
let x_m = nodes[m].x;
let y_m = nodes[m].y;
let km = this.K_matrix[m];
let lm = this.L_matrix[m];
for (let iIdx = 0; iIdx < nodesArray.length; iIdx++) {
let i = nodesArray[iIdx];
if (i !== m) {
let x_i = nodes[i].x;
let y_i = nodes[i].y;
let kmat = km[i];
let lmat = lm[i];
let denominator = 1.0 / Math.pow(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2), 1.5);
d2E_dx2 += this.K_matrix[m][i] * (1 - this.L_matrix[m][i] * Math.pow(y_m - y_i, 2) * denominator);
d2E_dxdy += this.K_matrix[m][i] * (this.L_matrix[m][i] * (x_m - x_i) * (y_m - y_i) * denominator);
d2E_dy2 += this.K_matrix[m][i] * (1 - this.L_matrix[m][i] * Math.pow(x_m - x_i, 2) * denominator);
d2E_dx2 += kmat * (1 - lmat * Math.pow(y_m - y_i, 2) * denominator);
d2E_dxdy += kmat * (lmat * (x_m - x_i) * (y_m - y_i) * denominator);
d2E_dy2 += kmat * (1 - lmat * Math.pow(x_m - x_i, 2) * denominator);
}
}
// make the variable names easier to make the solving of the linear system easier to read
@ -168,6 +159,9 @@ class KamadaKawai {
// move the node
nodes[m].x += dx;
nodes[m].y += dy;
// Recalculate E_matrix (should be incremental)
this._updateE_matrix(m);
}
@ -208,8 +202,82 @@ class KamadaKawai {
}
}
/**
* Create matrix with all energies between nodes
* @private
*/
_createE_matrix() {
let nodesArray = this.body.nodeIndices;
let nodes = this.body.nodes;
this.E_matrix = {};
this.E_sums = {};
for (let mIdx = 0; mIdx < nodesArray.length; mIdx++) {
this.E_matrix[nodesArray[mIdx]] = [];
}
for (let mIdx = 0; mIdx < nodesArray.length; mIdx++) {
let m = nodesArray[mIdx];
let x_m = nodes[m].x;
let y_m = nodes[m].y;
let dE_dx = 0;
let dE_dy = 0;
for (let iIdx = mIdx; iIdx < nodesArray.length; iIdx++) {
let i = nodesArray[iIdx];
if (i !== m) {
let x_i = nodes[i].x;
let y_i = nodes[i].y;
let denominator = 1.0 / Math.sqrt(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2));
this.E_matrix[m][iIdx] = [
this.K_matrix[m][i] * ((x_m - x_i) - this.L_matrix[m][i] * (x_m - x_i) * denominator),
this.K_matrix[m][i] * ((y_m - y_i) - this.L_matrix[m][i] * (y_m - y_i) * denominator)
];
this.E_matrix[i][mIdx] = this.E_matrix[m][iIdx];
dE_dx += this.E_matrix[m][iIdx][0];
dE_dy += this.E_matrix[m][iIdx][1];
}
}
//Store sum
this.E_sums[m] = [dE_dx, dE_dy];
}
}
//Update method, just doing single column (rows are auto-updated) (update all sums)
_updateE_matrix(m) {
let nodesArray = this.body.nodeIndices;
let nodes = this.body.nodes;
let colm = this.E_matrix[m];
let kcolm = this.K_matrix[m];
let lcolm = this.L_matrix[m];
let x_m = nodes[m].x;
let y_m = nodes[m].y;
let dE_dx = 0;
let dE_dy = 0;
for (let iIdx = 0; iIdx < nodesArray.length; iIdx++) {
let i = nodesArray[iIdx];
if (i !== m) {
//Keep old energy value for sum modification below
let cell = colm[iIdx];
let oldDx = cell[0];
let oldDy = cell[1];
//Calc new energy:
let x_i = nodes[i].x;
let y_i = nodes[i].y;
let denominator = 1.0 / Math.sqrt(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2));
let dx = kcolm[i] * ((x_m - x_i) - lcolm[i] * (x_m - x_i) * denominator);
let dy = kcolm[i] * ((y_m - y_i) - lcolm[i] * (y_m - y_i) * denominator);
colm[iIdx] = [dx, dy];
dE_dx += dx;
dE_dy += dy;
//add new energy to sum of each column
let sum = this.E_sums[i];
sum[0] += (dx-oldDx);
sum[1] += (dy-oldDy);
}
}
//Store sum at -1 index
this.E_sums[m] = [dE_dx, dE_dy];
}
}
export default KamadaKawai;

+ 19
- 7
lib/network/modules/LayoutEngine.js View File

@ -161,9 +161,9 @@ class LayoutEngine {
positionInitially(nodesArray) {
if (this.options.hierarchical.enabled !== true) {
this.randomSeed = this.initialRandomSeed;
let radius = nodesArray.length + 50;
for (let i = 0; i < nodesArray.length; i++) {
let node = nodesArray[i];
let radius = 10 * 0.1 * nodesArray.length + 10;
let angle = 2 * Math.PI * this.seededRandom();
if (node.x === undefined) {
node.x = radius * Math.cos(angle);
@ -196,34 +196,46 @@ class LayoutEngine {
if (positionDefined < 0.5 * this.body.nodeIndices.length) {
let MAX_LEVELS = 10;
let level = 0;
let clusterThreshold = 100;
let clusterThreshold = 150;
//Performance enhancement, during clustering edges need only be simple straight lines. These options don't propagate outside the clustering phase.
let clusterOptions = {
clusterEdgeProperties:{
smooth: {
enabled: false
}
}
};
// if there are a lot of nodes, we cluster before we run the algorithm.
if (this.body.nodeIndices.length > clusterThreshold) {
let startLength = this.body.nodeIndices.length;
while (this.body.nodeIndices.length > clusterThreshold) {
while (this.body.nodeIndices.length > clusterThreshold && level <= MAX_LEVELS) {
//console.time("clustering")
level += 1;
let before = this.body.nodeIndices.length;
// if there are many nodes we do a hubsize cluster
if (level % 3 === 0) {
this.body.modules.clustering.clusterBridges();
this.body.modules.clustering.clusterBridges(clusterOptions);
}
else {
this.body.modules.clustering.clusterOutliers();
this.body.modules.clustering.clusterOutliers(clusterOptions);
}
let after = this.body.nodeIndices.length;
if ((before == after && level % 3 !== 0) || level > MAX_LEVELS) {
if (before == after && level % 3 !== 0) {
this._declusterAll();
this.body.emitter.emit("_layoutFailed");
console.info("This network could not be positioned by this version of the improved layout algorithm. Please disable improvedLayout for better performance.");
return;
}
//console.timeEnd("clustering")
//console.log(level,after)
//console.log(before,level,after);
}
// increase the size of the edges
this.body.modules.kamadaKawai.setOptions({springLength: Math.max(150, 2 * startLength)})
}
if (level > MAX_LEVELS){
console.info("The clustering didn't succeed within the amount of interations allowed, progressing with partial result.");
}
// position the system for these nodes and edges
this.body.modules.kamadaKawai.solve(this.body.nodeIndices, this.body.edgeIndices, true);

+ 18
- 9
lib/network/modules/components/algorithms/FloydWarshall.js View File

@ -4,7 +4,8 @@
class FloydWarshall {
constructor(){}
constructor() {
}
getDistances(body, nodesArray, edgesArray) {
let D_matrix = {};
@ -12,11 +13,11 @@ class FloydWarshall {
// prepare matrix with large numbers
for (let i = 0; i < nodesArray.length; i++) {
D_matrix[nodesArray[i]] = {};
D_matrix[nodesArray[i]] = {};
let node = nodesArray[i];
let cell = {};
D_matrix[node] = cell;
for (let j = 0; j < nodesArray.length; j++) {
D_matrix[nodesArray[i]][nodesArray[j]] = (i == j ? 0 : 1e9);
D_matrix[nodesArray[i]][nodesArray[j]] = (i == j ? 0 : 1e9);
cell[nodesArray[j]] = (i == j ? 0 : 1e9);
}
}
@ -34,10 +35,18 @@ class FloydWarshall {
// Adapted FloydWarshall based on unidirectionality to greatly reduce complexity.
for (let k = 0; k < nodeCount; k++) {
for (let i = 0; i < nodeCount-1; i++) {
for (let j = i+1; j < nodeCount; j++) {
D_matrix[nodesArray[i]][nodesArray[j]] = Math.min(D_matrix[nodesArray[i]][nodesArray[j]],D_matrix[nodesArray[i]][nodesArray[k]] + D_matrix[nodesArray[k]][nodesArray[j]])
D_matrix[nodesArray[j]][nodesArray[i]] = D_matrix[nodesArray[i]][nodesArray[j]];
let knode = nodesArray[k];
let kcolm = D_matrix[knode];
for (let i = 0; i < nodeCount - 1; i++) {
let inode = nodesArray[i];
let icolm = D_matrix[inode];
for (let j = i + 1; j < nodeCount; j++) {
let jnode = nodesArray[j];
let jcolm = D_matrix[jnode];
let val = Math.min(icolm[jnode], icolm[knode] + kcolm[jnode]);
icolm[jnode] = val;
jcolm[inode] = val;
}
}
}

+ 41
- 11
lib/timeline/Core.js View File

@ -245,6 +245,9 @@ Core.prototype._create = function (container) {
}
function handleDrop(event) {
// prevent redirect to blank page - Firefox
if(event.preventDefault) { event.preventDefault(); }
if(event.stopPropagation) { event.stopPropagation(); }
// return when dropping non-vis items
try {
var itemData = JSON.parse(event.dataTransfer.getData("text"))
@ -610,8 +613,9 @@ Core.prototype.getVisibleItems = function() {
* provided to specify duration and easing function.
* Default duration is 500 ms, and default easing
* function is 'easeInOutQuad'.
* @param {Function} a callback funtion to be executed at the end of this function
*/
Core.prototype.fit = function(options) {
Core.prototype.fit = function(options, callback) {
var range = this.getDataRange();
// skip range set if there is no min and max date
@ -624,7 +628,7 @@ Core.prototype.fit = function(options) {
var min = new Date(range.min.valueOf() - interval * 0.01);
var max = new Date(range.max.valueOf() + interval * 0.01);
var animation = (options && options.animation !== undefined) ? options.animation : true;
this.range.setRange(min, max, animation);
this.range.setRange(min, max, { animation: animation }, callback);
};
/**
@ -657,17 +661,28 @@ Core.prototype.getDataRange = function() {
* provided to specify duration and easing function.
* Default duration is 500 ms, and default easing
* function is 'easeInOutQuad'.
* @param {Function} a callback funtion to be executed at the end of this function
*/
Core.prototype.setWindow = function(start, end, options) {
Core.prototype.setWindow = function(start, end, options, callback) {
if (typeof arguments[2] == "function") {
callback = arguments[2]
options = {};
}
var animation;
if (arguments.length == 1) {
var range = arguments[0];
animation = (range.animation !== undefined) ? range.animation : true;
this.range.setRange(range.start, range.end, animation);
this.range.setRange(range.start, range.end, { animation: animation });
}
else if (arguments.length == 2 && typeof arguments[1] == "function") {
var range = arguments[0];
callback = arguments[1];
animation = (range.animation !== undefined) ? range.animation : true;
this.range.setRange(range.start, range.end, { animation: animation }, callback);
}
else {
animation = (options && options.animation !== undefined) ? options.animation : true;
this.range.setRange(start, end, animation);
this.range.setRange(start, end, { animation: animation }, callback);
}
};
@ -681,8 +696,13 @@ Core.prototype.setWindow = function(start, end, options) {
* provided to specify duration and easing function.
* Default duration is 500 ms, and default easing
* function is 'easeInOutQuad'.
* @param {Function} a callback funtion to be executed at the end of this function
*/
Core.prototype.moveTo = function(time, options) {
Core.prototype.moveTo = function(time, options, callback) {
if (typeof arguments[1] == "function") {
callback = arguments[1]
options = {};
}
var interval = this.range.end - this.range.start;
var t = util.convert(time, 'Date').valueOf();
@ -690,7 +710,7 @@ Core.prototype.moveTo = function(time, options) {
var end = t + interval / 2;
var animation = (options && options.animation !== undefined) ? options.animation : true;
this.range.setRange(start, end, animation);
this.range.setRange(start, end, { animation: animation }, callback);
};
/**
@ -715,9 +735,14 @@ Core.prototype.getWindow = function() {
* provided to specify duration and easing function.
* Default duration is 500 ms, and default easing
* function is 'easeInOutQuad'.
* @param {Function} a callback funtion to be executed at the end of this function
*/
Core.prototype.zoomIn = function(percentage, options) {
Core.prototype.zoomIn = function(percentage, options, callback) {
if (!percentage || percentage < 0 || percentage > 1) return
if (typeof arguments[1] == "function") {
callback = arguments[1]
options = {};
}
var range = this.getWindow();
var start = range.start.valueOf();
var end = range.end.valueOf();
@ -727,7 +752,7 @@ Core.prototype.zoomIn = function(percentage, options) {
var newStart = start + distance;
var newEnd = end - distance;
this.setWindow(newStart, newEnd, options);
this.setWindow(newStart, newEnd, options, callback);
};
/**
@ -740,9 +765,14 @@ Core.prototype.zoomIn = function(percentage, options) {
* provided to specify duration and easing function.
* Default duration is 500 ms, and default easing
* function is 'easeInOutQuad'.
* @param {Function} a callback funtion to be executed at the end of this function
*/
Core.prototype.zoomOut = function(percentage, options) {
Core.prototype.zoomOut = function(percentage, options, callback) {
if (!percentage || percentage < 0 || percentage > 1) return
if (typeof arguments[1] == "function") {
callback = arguments[1]
options = {};
}
var range = this.getWindow();
var start = range.start.valueOf();
var end = range.end.valueOf();
@ -750,7 +780,7 @@ Core.prototype.zoomOut = function(percentage, options) {
var newStart = start - interval * percentage / 2;
var newEnd = end + interval * percentage / 2;
this.setWindow(newStart, newEnd, options);
this.setWindow(newStart, newEnd, options, callback);
};
/**

+ 60
- 24
lib/timeline/Range.js View File

@ -45,7 +45,11 @@ function Range(body, options) {
min: null,
max: null,
zoomMin: 10, // milliseconds
zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000 // milliseconds
zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds
rollingMode: {
follow: false,
offset: 0.5
}
};
this.options = util.extend({}, this.defaultOptions);
this.props = {
@ -94,11 +98,11 @@ Range.prototype.setOptions = function (options) {
// copy the options that we know
var fields = [
'animation', 'direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable',
'moment', 'activate', 'hiddenDates', 'zoomKey', 'rtl', 'showCurrentTime', 'rollMode', 'horizontalScroll'
'moment', 'activate', 'hiddenDates', 'zoomKey', 'rtl', 'showCurrentTime', 'rollingMode', 'horizontalScroll'
];
util.selectiveExtend(fields, this.options, options);
if (options.rollingMode) {
if (options.rollingMode && options.rollingMode.follow) {
this.startRolling();
}
if ('start' in options || 'end' in options) {
@ -134,11 +138,14 @@ Range.prototype.startRolling = function() {
var interval = me.end - me.start;
var t = util.convert(new Date(), 'Date').valueOf();
var start = t - interval / 2;
var end = t + interval / 2;
var start = t - interval * (me.options.rollingMode.offset);
var end = t + interval * (1 - me.options.rollingMode.offset);
var animation = (me.options && me.options.animation !== undefined) ? me.options.animation : true;
me.setRange(start, end, false);
var options = {
animation: false
};
me.setRange(start, end, options);
// determine interval to refresh
var scale = me.conversion(me.body.domProps.center.width).scale;
@ -169,29 +176,36 @@ Range.prototype.stopRolling = function() {
* Set a new start and end range
* @param {Date | Number | String} [start]
* @param {Date | Number | String} [end]
* @param {boolean | {duration: number, easingFunction: string}} [animation=false]
* If true (default), the range is animated
* @param {Object} options Available options:
* {Boolean | {duration: number, easingFunction: string}} [animation=false]
* If true, the range is animated
* smoothly to the new window. An object can be
* provided to specify duration and easing function.
* Default duration is 500 ms, and default easing
* function is 'easeInOutQuad'.
* @param {Boolean} [byUser=false]
* {Boolean} [byUser=false]
* {Event} event Mouse event
* {Function} a callback funtion to be executed at the end of this function
*
*/
Range.prototype.setRange = function(start, end, animation, byUser, event) {
if (byUser !== true) {
byUser = false;
Range.prototype.setRange = function(start, end, options, callback) {
if (!options) {
options = {};
}
if (options.byUser !== true) {
options.byUser = false;
}
var finalStart = start != undefined ? util.convert(start, 'Date').valueOf() : null;
var finalEnd = end != undefined ? util.convert(end, 'Date').valueOf() : null;
this._cancelAnimation();
if (animation) { // true or an Object
if (options.animation) { // true or an Object
var me = this;
var initStart = this.start;
var initEnd = this.end;
var duration = (typeof animation === 'object' && 'duration' in animation) ? animation.duration : 500;
var easingName = (typeof animation === 'object' && 'easingFunction' in animation) ? animation.easingFunction : 'easeInOutQuad';
var duration = (typeof options.animation === 'object' && 'duration' in options.animation) ? options.animation.duration : 500;
var easingName = (typeof options.animation === 'object' && 'easingFunction' in options.animation) ? options.animation.easingFunction : 'easeInOutQuad';
var easingFunction = util.easingFunctions[easingName];
if (!easingFunction) {
throw new Error('Unknown easing function ' + JSON.stringify(easingName) + '. ' +
@ -217,8 +231,8 @@ Range.prototype.setRange = function(start, end, animation, byUser, event) {
var params = {
start: new Date(me.start),
end: new Date(me.end),
byUser:byUser,
event: event
byUser: options.byUser,
event: options.event
}
if (changed) {
@ -228,6 +242,7 @@ Range.prototype.setRange = function(start, end, animation, byUser, event) {
if (done) {
if (anyChanged) {
me.body.emitter.emit('rangechanged', params);
if (callback) { return callback() }
}
}
else {
@ -247,11 +262,12 @@ Range.prototype.setRange = function(start, end, animation, byUser, event) {
var params = {
start: new Date(this.start),
end: new Date(this.end),
byUser:byUser,
event: event
byUser: options.byUser,
event: options.event
};
this.body.emitter.emit('rangechange', params);
this.body.emitter.emit('rangechanged', params);
if (callback) { return callback() }
}
}
};
@ -600,7 +616,12 @@ Range.prototype._onMouseWheel = function(event) {
var newStart = this.start - diff;
var newEnd = this.end - diff;
this.setRange(newStart, newEnd, false, true, event);
var options = {
animation: false,
byUser: true,
event: event
}
this.setRange(newStart, newEnd, options);
}
return;
}
@ -630,7 +651,7 @@ Range.prototype._onMouseWheel = function(event) {
// calculate center, the date to zoom around
var pointerDate
if (this.rolling) {
pointerDate = (this.start + this.end) / 2;
pointerDate = this.start + ((this.end - this.start) * this.options.rollingMode.offset);
} else {
var pointer = this.getPointer({x: event.clientX, y: event.clientY}, this.body.dom.center);
pointerDate = this._pointerToDate(pointer);
@ -698,7 +719,12 @@ Range.prototype._onPinch = function (event) {
newEnd = safeEnd;
}
this.setRange(newStart, newEnd, false, true, event);
var options = {
animation: false,
byUser: true,
event: event
}
this.setRange(newStart, newEnd, options);
this.startToFront = false; // revert to default
this.endToFront = true; // revert to default
@ -802,7 +828,12 @@ Range.prototype.zoom = function(scale, center, delta, event) {
newEnd = safeEnd;
}
this.setRange(newStart, newEnd, false, true, event);
var options = {
animation: false,
byUser: true,
event: event
}
this.setRange(newStart, newEnd, options);
this.startToFront = false; // revert to default
this.endToFront = true; // revert to default
@ -843,7 +874,12 @@ Range.prototype.moveTo = function(moveTo) {
var newStart = this.start - diff;
var newEnd = this.end - diff;
this.setRange(newStart, newEnd, false, true, null);
var options = {
animation: false,
byUser: true,
event: null
}
this.setRange(newStart, newEnd, options);
};
module.exports = Range;

+ 36
- 37
lib/timeline/TimeStep.js View File

@ -560,6 +560,7 @@ TimeStep.prototype.getClassName = function() {
var m = this.moment(this.current);
var current = m.locale ? m.locale('en') : m.lang('en'); // old versions of moment have .lang() function
var step = this.step;
var classNames = [];
function even(value) {
return (value / step % 2 == 0) ? ' vis-even' : ' vis-odd';
@ -592,51 +593,49 @@ TimeStep.prototype.getClassName = function() {
switch (this.scale) {
case 'millisecond':
return today(current) +
even(current.milliseconds()).trim();
classNames.push(today(current));
classNames.push(even(current.milliseconds()));
break;
case 'second':
return today(current) +
even(current.seconds()).trim();
classNames.push(today(current));
classNames.push(even(current.seconds()));
break;
case 'minute':
return today(current) +
even(current.minutes()).trim();
classNames.push(today(current));
classNames.push(even(current.minutes()));
break;
case 'hour':
return 'vis-h' + current.hours() +
(this.step == 4 ? '-h' + (current.hours() + 4) : '') +
today(current) +
even(current.hours());
classNames.push('vis-h' + current.hours() + this.step == 4 ? '-h' + (current.hours() + 4) : '');
classNames.push(today(current));
classNames.push(even(current.hours()));
break;
case 'weekday':
return 'vis-' + current.format('dddd').toLowerCase() +
today(current) +
currentWeek(current) +
even(current.date());
classNames.push('vis-' + current.format('dddd').toLowerCase());
classNames.push(today(current));
classNames.push(currentWeek(current));
classNames.push(even(current.date()));
break;
case 'day':
return 'vis-day' + current.date() +
' vis-' + current.format('MMMM').toLowerCase() +
today(current) +
currentMonth(current) +
(this.step <= 2 ? today(current) : '') +
(this.step <= 2 ? ' vis-' + current.format('dddd').toLowerCase() : '' + even(current.date() - 1));
classNames.push('vis-day' + current.date());
classNames.push('vis-' + current.format('MMMM').toLowerCase());
classNames.push(today(current));
classNames.push(currentMonth(current));
classNames.push(this.step <= 2 ? today(current) : '');
classNames.push(this.step <= 2 ? 'vis-' + current.format('dddd').toLowerCase() : '');
classNames.push(even(current.date() - 1));
break;
case 'month':
return 'vis-' + current.format('MMMM').toLowerCase() +
currentMonth(current) +
even(current.month());
classNames.push('vis-' + current.format('MMMM').toLowerCase());
classNames.push(currentMonth(current));
classNames.push(even(current.month()));
break;
case 'year':
var year = current.year();
return 'vis-year' + year +
currentYear(current) +
even(year);
default:
return '';
classNames.push('vis-year' + current.year());
classNames.push(currentYear(current));
classNames.push(even(current.year()));
break;
}
return classNames.filter(String).join(" ");
};
module.exports = TimeStep;

+ 2
- 2
lib/timeline/Timeline.js View File

@ -382,7 +382,7 @@ Timeline.prototype.focus = function(id, options) {
var interval = Math.max((this.range.end - this.range.start), (end - start) * 1.1);
var animation = (options && options.animation !== undefined) ? options.animation : true;
this.range.setRange(middle - interval / 2, middle + interval / 2, animation);
this.range.setRange(middle - interval / 2, middle + interval / 2, { animation: animation });
}
};
@ -409,7 +409,7 @@ Timeline.prototype.fit = function (options) {
else {
// exactly fit the items (plus a small margin)
range = this.getItemRange();
this.range.setRange(range.min, range.max, animation);
this.range.setRange(range.min, range.max, { animation: animation });
}
};

+ 2
- 2
lib/timeline/component/CustomTime.js View File

@ -217,7 +217,7 @@ CustomTime.prototype._onDrag = function (event) {
this.body.emitter.emit('timechange', {
id: this.options.id,
time: new Date(this.customTime.valueOf()),
event: util.elementsCensor(event)
event: event
});
event.stopPropagation();
@ -235,7 +235,7 @@ CustomTime.prototype._onDragEnd = function (event) {
this.body.emitter.emit('timechanged', {
id: this.options.id,
time: new Date(this.customTime.valueOf()),
event: util.elementsCensor(event)
event: event
});
event.stopPropagation();

+ 71
- 39
lib/timeline/component/Group.js View File

@ -133,21 +133,34 @@ Group.prototype.setData = function(data) {
}
if (data && data.nestedGroups) {
if (data.showNested == false) {
this.showNested = false;
} else {
this.showNested = true;
if (!this.nestedGroups || this.nestedGroups != data.nestedGroups) {
this.nestedGroups = data.nestedGroups;
}
if (data.showNested !== undefined || this.showNested === undefined) {
if (data.showNested == false) {
this.showNested = false;
} else {
this.showNested = true;
}
}
util.addClassName(this.dom.label, 'vis-nesting-group');
var collapsedDirClassName = this.itemSet.options.rtl ? 'collapsed-rtl' : 'collapsed'
if (this.showNested) {
util.removeClassName(this.dom.label, 'collapsed');
util.removeClassName(this.dom.label, collapsedDirClassName);
util.addClassName(this.dom.label, 'expanded');
} else {
util.removeClassName(this.dom.label, 'expanded');
var collapsedDirClassName = this.itemSet.options.rtl ? 'collapsed-rtl' : 'collapsed'
util.addClassName(this.dom.label, collapsedDirClassName);
}
} else if (this.nestedGroups) {
this.nestedGroups = null;
var collapsedDirClassName = this.itemSet.options.rtl ? 'collapsed-rtl' : 'collapsed'
util.removeClassName(this.dom.label, collapsedDirClassName);
util.removeClassName(this.dom.label, 'expanded');
util.removeClassName(this.dom.label, 'vis-nesting-group');
}
if (data && data.nestedInGroup) {
@ -425,30 +438,9 @@ Group.prototype.add = function(item) {
// add to
if (item.data.subgroup !== undefined) {
if (this.subgroups[item.data.subgroup] === undefined) {
this.subgroups[item.data.subgroup] = {
height:0,
top: 0,
start: item.data.start,
end: item.data.end,
visible: false,
index:this.subgroupIndex,
items: []
};
this.subgroupIndex++;
}
if (new Date(item.data.start) < new Date(this.subgroups[item.data.subgroup].start)) {
this.subgroups[item.data.subgroup].start = item.data.start;
}
if (new Date(item.data.end) > new Date(this.subgroups[item.data.subgroup].end)) {
this.subgroups[item.data.subgroup].end = item.data.end;
}
this.subgroups[item.data.subgroup].items.push(item);
this._addToSubgroup(item);
this.orderSubgroups();
}
this.orderSubgroups();
if (this.visibleItems.indexOf(item) == -1) {
var range = this.itemSet.body.range; // TODO: not nice accessing the range like this
@ -456,6 +448,34 @@ Group.prototype.add = function(item) {
}
};
Group.prototype._addToSubgroup = function(item, subgroupId) {
subgroupId = subgroupId || item.data.subgroup;
if (subgroupId != undefined && this.subgroups[subgroupId] === undefined) {
this.subgroups[subgroupId] = {
height:0,
top: 0,
start: item.data.start,
end: item.data.end,
visible: false,
index:this.subgroupIndex,
items: []
};
this.subgroupIndex++;
}
if (new Date(item.data.start) < new Date(this.subgroups[subgroupId].start)) {
this.subgroups[subgroupId].start = item.data.start;
}
if (new Date(item.data.end) > new Date(this.subgroups[subgroupId].end)) {
this.subgroups[subgroupId].end = item.data.end;
}
this.subgroups[subgroupId].items.push(item);
};
Group.prototype._updateSubgroupsSizes = function () {
var me = this;
if (me.subgroups) {
@ -526,22 +546,30 @@ Group.prototype.remove = function(item) {
if (index != -1) this.visibleItems.splice(index, 1);
if(item.data.subgroup !== undefined){
var subgroup = this.subgroups[item.data.subgroup];
this._removeFromSubgroup(item);
this.orderSubgroups();
}
};
Group.prototype._removeFromSubgroup = function(item, subgroupId) {
subgroupId = subgroupId || item.data.subgroup;
if (subgroupId != undefined) {
var subgroup = this.subgroups[subgroupId];
if (subgroup){
var itemIndex = subgroup.items.indexOf(item);
subgroup.items.splice(itemIndex,1);
if (!subgroup.items.length){
delete this.subgroups[item.data.subgroup];
this.subgroupIndex--;
} else {
this._updateSubgroupsSizes();
// Check the item is actually in this subgroup. How should items not in the group be handled?
if (itemIndex >= 0) {
subgroup.items.splice(itemIndex,1);
if (!subgroup.items.length){
delete this.subgroups[subgroupId];
} else {
this._updateSubgroupsSizes();
}
}
this.orderSubgroups();
}
}
};
/**
* Remove an item from the corresponding DataSet
* @param {Item} item
@ -722,6 +750,10 @@ Group.prototype._checkIfVisibleWithReference = function(item, visibleItems, visi
}
};
Group.prototype.changeSubgroup = function(item, oldSubgroup, newSubgroup) {
this._removeFromSubgroup(item, oldSubgroup);
this._addToSubgroup(item, newSubgroup);
this.orderSubgroups();
};
module.exports = Group;

+ 16
- 9
lib/timeline/component/ItemSet.js View File

@ -546,7 +546,7 @@ ItemSet.prototype.getVisibleItems = function() {
for (var groupId in this.groups) {
if (this.groups.hasOwnProperty(groupId)) {
var group = this.groups[groupId];
var rawVisibleItems = group.visibleItems;
var rawVisibleItems = group.isVisible ? group.visibleItems : [];
// filter the "raw" set with visibleItems into a set which is really
// visible by pixels
@ -1199,21 +1199,28 @@ ItemSet.prototype._updateItem = function(item, itemData) {
var oldGroupId = item.data.group;
var oldSubGroupId = item.data.subgroup;
if (oldGroupId != itemData.group) {
var oldGroup = this.groups[oldGroupId];
if (oldGroup) oldGroup.remove(item);
}
// update the items data (will redraw the item when displayed)
item.setData(itemData);
var groupId = this._getGroupId(item.data);
var group = this.groups[groupId];
var group = this.groups[groupId];
if (!group) {
item.groupShowing = false;
item.groupShowing = false;
} else if (group && group.data && group.data.showNested) {
item.groupShowing = true;
item.groupShowing = true;
}
// update group
if (oldGroupId != item.data.group || oldSubGroupId != item.data.subgroup) {
var oldGroup = this.groups[oldGroupId];
if (oldGroup) oldGroup.remove(item);
if (group) group.add(item);
if (group) {
if (oldGroupId != item.data.group) {
group.add(item);
} else if (oldSubGroupId != item.data.subgroup) {
group.changeSubgroup(item, oldSubGroupId);
}
}
};
@ -1644,7 +1651,7 @@ ItemSet.prototype._onDragEnd = function (event) {
ItemSet.prototype._onGroupClick = function (event) {
var group = this.groupFromTarget(event);
if (!group.nestedGroups) return;
if (!group || !group.nestedGroups) return;
var groupsData = this.groupsData;
if (this.groupsData instanceof DataView) {

+ 5
- 1
lib/timeline/optionsTimeline.js View File

@ -26,7 +26,11 @@ let allOptions = {
//globals :
align: {string},
rtl: { 'boolean': bool, 'undefined': 'undefined'},
rollingMode: { 'boolean': bool, 'undefined': 'undefined'},
rollingMode: {
follow: { 'boolean': bool },
offset: {number,'undefined': 'undefined'},
__type__: {object}
},
verticalScroll: { 'boolean': bool, 'undefined': 'undefined'},
horizontalScroll: { 'boolean': bool, 'undefined': 'undefined'},
autoResize: { 'boolean': bool},

+ 1
- 0
misc/we_need_help.md View File

@ -16,3 +16,4 @@ If you have shown some commitment to the project you can ask [@ludost](//github.
* [@Tooa](//github.com/Tooa)
* [@eymiha](//github.com/eymiha)
* [@bradh](//github.com/bradh)
* [@wimrijnders](//github.com/wimrijnders)

+ 11
- 11
package.json View File

@ -1,6 +1,6 @@
{
"name": "vis",
"version": "4.18.1-SNAPSHOT",
"version": "4.19.1-SNAPSHOT",
"description": "A dynamic, browser-based visualization library.",
"homepage": "http://visjs.org/",
"license": "(Apache-2.0 OR MIT)",
@ -28,25 +28,25 @@
"lint": "eslint lib",
"watch": "gulp watch",
"watch-dev": "gulp watch --bundle"
},
},
"dependencies": {
"babel-core": "^6.6.5",
"babel-loader": "^6.2.4",
"babel-polyfill": "^6.22.0",
"babel-plugin-transform-es3-member-expression-literals": "^6.22.0",
"babel-plugin-transform-es3-property-literals": "^6.8.0",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-es2015": "^6.6.0",
"babel-runtime": "^6.22.0",
"emitter-component": "^1.1.1",
"moment": "^2.17.1",
"moment": "^2.18.1",
"propagating-hammerjs": "^1.4.6",
"hammerjs": "^2.0.8",
"keycharm": "^0.2.0"
},
"devDependencies": {
"async": "^2.1.4",
"babel-core": "^6.6.5",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.2.4",
"babel-polyfill": "^6.22.0",
"babel-plugin-transform-es3-member-expression-literals": "^6.22.0",
"babel-plugin-transform-es3-property-literals": "^6.8.0",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-es2015": "^6.6.0",
"babel-runtime": "^6.22.0",
"babelify": "^7.3.0",
"clean-css": "^4.0.2",
"eslint": "^3.15.0",

+ 44
- 0
test/TimeStep.test.js View File

@ -0,0 +1,44 @@
var assert = require('assert');
var vis = require('../dist/vis');
var jsdom = require('mocha-jsdom')
var moment = vis.moment;
var timeline = vis.timeline;
var TimeStep = timeline.TimeStep;
var TestSupport = require('./TestSupport');
describe('TimeStep', function () {
jsdom();
it('should work with just start and end dates', function () {
var timestep = new TimeStep(new Date(2017, 3, 3), new Date(2017, 3, 5));
assert.equal(timestep.autoScale, true, "should autoscale if scale not specified");
assert.equal(timestep.scale, "day", "should default to day scale if scale not specified");
assert.equal(timestep.step, 1, "should default to 1 day step if scale not specified");
});
it('should work with specified scale (just under 1 second)', function () {
var timestep = new TimeStep(new Date(2017, 3, 3), new Date(2017, 3, 5), 999);
assert.equal(timestep.scale, "second", "should have right scale");
assert.equal(timestep.step, 1, "should have right step size");
});
// TODO: check this - maybe should work for 1000?
it('should work with specified scale (1 second)', function () {
var timestep = new TimeStep(new Date(2017, 3, 3), new Date(2017, 3, 5), 1001);
assert.equal(timestep.scale, "second", "should have right scale");
assert.equal(timestep.step, 5, "should have right step size");
});
it('should work with specified scale (2 seconds)', function () {
var timestep = new TimeStep(new Date(2017, 3, 3), new Date(2017, 3, 5), 2000);
assert.equal(timestep.scale, "second", "should have right scale");
assert.equal(timestep.step, 5, "should have right step size");
});
it('should work with specified scale (5 seconds)', function () {
var timestep = new TimeStep(new Date(2017, 3, 3), new Date(2017, 3, 5), 5001);
assert.equal(timestep.scale, "second", "should have right scale");
assert.equal(timestep.step, 10, "should have right step size");
});
});

Loading…
Cancel
Save