diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1a43ae70..9f9ae19b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -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). diff --git a/HISTORY.md b/HISTORY.md index 98831743..23e8c72f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -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 diff --git a/docs/timeline/index.html b/docs/timeline/index.html index b69d05bd..4fb318ec 100644 --- a/docs/timeline/index.html +++ b/docs/timeline/index.html @@ -468,10 +468,10 @@ var groups = [ Array of group ids nested in the group. Nested groups will appear under this nesting group. - showNestedGroups + showNested Boolean no - Assuming the group has nested groups, this will set the initial state of the group - shown or collapsed. The showNestedGroups is defaulted to true. + Assuming the group has nested groups, this will set the initial state of the group - shown or collapsed. The showNested is defaulted to true. @@ -955,13 +955,29 @@ function (option, path) { Orientation of the timeline items: 'top' or 'bottom' (default). Determines whether items are aligned to the top or bottom of the Timeline. - - rollingMode + + rollingMode + Object + Object + Specify how the timeline implements rolling mode. + + + rollingMode.follow boolean false 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. + + rollingMode.offset + Number + '0.5' + + 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%) + + + rtl boolean @@ -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: vis-item-overflow which is overflow:hidden. - + throttleRedraw number 0 - This option is DEPRICATED and no longer supported. It will be removed in the next MAJOR release. + This option is DEPRECATED and no longer supported. It will be removed in the next MAJOR release. @@ -1308,12 +1324,13 @@ document.getElementById('myTimeline').onclick = function (event) { - moveTo(time [, options]) + moveTo(time [, options, callback]) none Move the window such that given time is centered on screen. Parameter time can be a Date, Number, or String. Available options: + A callback function can be passed as an optional parameter. This function will be called at the end of moveTo function. @@ -1422,12 +1439,13 @@ document.getElementById('myTimeline').onclick = function (event) { - setWindow(start, end [, options]) + setWindow(start, end [, options, callback]) none Set the current visible window. The parameters start and end can be a Date, Number, or String. If the parameter value of start or end is null, the parameter will be left unchanged. Available options: + A callback function can be passed as an optional parameter. This function will be called at the end of setWindow function. @@ -1439,21 +1457,23 @@ document.getElementById('myTimeline').onclick = function (event) { - zoomIn(percentage [, options]) + zoomIn(percentage [, options, callback]) none Zoom in the current visible window. The parameter percentage can be a Number and must be between 0 and 1. If the parameter value of percentage is null, the window will be left unchanged. Available options: + A callback function can be passed as an optional parameter. This function will be called at the end of zoomIn function. - zoomOut(percentage [, options]) + zoomOut(percentage [, options, callback]) none Zoom out the current visible window. The parameter percentage can be a Number and must be between 0 and 1. If the parameter value of percentage is null, the window will be left unchanged. Available options: + A callback function can be passed as an optional parameter. This function will be called at the end of zoomOut function. @@ -1991,7 +2011,7 @@ var options = { Weekdayvis-monday, vis-tuesday, vis-wednesday, vis-thursday, vis-friday, vis-saturday, vis-sunday - Daysvis-date1, vis-date2, ..., vis-date31 + Daysvis-day1, vis-day2, ..., vis-day31 Monthsvis-january, vis-february, vis-march, vis-april, vis-may, vis-june, vis-july, vis-august, vis-september, vis-october, vis-november, vis-december diff --git a/examples/timeline/interaction/rollingMode.html b/examples/timeline/interaction/rollingMode.html index ebe3fcfb..199d3e39 100644 --- a/examples/timeline/interaction/rollingMode.html +++ b/examples/timeline/interaction/rollingMode.html @@ -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 diff --git a/lib/network/gephiParser.js b/lib/network/gephiParser.js index 124d6b45..66da1eb6 100644 --- a/lib/network/gephiParser.js +++ b/lib/network/gephiParser.js @@ -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; \ No newline at end of file +exports.parseGephi = parseGephi; diff --git a/lib/network/modules/EdgesHandler.js b/lib/network/modules/EdgesHandler.js index 549cf598..f5afc13b 100644 --- a/lib/network/modules/EdgesHandler.js +++ b/lib/network/modules/EdgesHandler.js @@ -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; } diff --git a/lib/network/modules/KamadaKawai.js b/lib/network/modules/KamadaKawai.js index 63d620ff..50e84c83 100644 --- a/lib/network/modules/KamadaKawai.js +++ b/lib/network/modules/KamadaKawai.js @@ -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; \ No newline at end of file diff --git a/lib/network/modules/LayoutEngine.js b/lib/network/modules/LayoutEngine.js index 166781e1..09468955 100644 --- a/lib/network/modules/LayoutEngine.js +++ b/lib/network/modules/LayoutEngine.js @@ -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); diff --git a/lib/network/modules/components/algorithms/FloydWarshall.js b/lib/network/modules/components/algorithms/FloydWarshall.js index e0d3f92d..42983755 100644 --- a/lib/network/modules/components/algorithms/FloydWarshall.js +++ b/lib/network/modules/components/algorithms/FloydWarshall.js @@ -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; } } } diff --git a/lib/timeline/Core.js b/lib/timeline/Core.js index 01bf3993..4a6533f7 100644 --- a/lib/timeline/Core.js +++ b/lib/timeline/Core.js @@ -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); }; /** diff --git a/lib/timeline/Range.js b/lib/timeline/Range.js index ced39db8..f568bc58 100644 --- a/lib/timeline/Range.js +++ b/lib/timeline/Range.js @@ -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; diff --git a/lib/timeline/TimeStep.js b/lib/timeline/TimeStep.js index d0f230f6..e3f6ff7b 100644 --- a/lib/timeline/TimeStep.js +++ b/lib/timeline/TimeStep.js @@ -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; diff --git a/lib/timeline/Timeline.js b/lib/timeline/Timeline.js index 6cc4b068..02d633a4 100644 --- a/lib/timeline/Timeline.js +++ b/lib/timeline/Timeline.js @@ -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 }); } }; diff --git a/lib/timeline/component/CustomTime.js b/lib/timeline/component/CustomTime.js index 6aa6502a..e3e60dc1 100644 --- a/lib/timeline/component/CustomTime.js +++ b/lib/timeline/component/CustomTime.js @@ -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(); diff --git a/lib/timeline/component/Group.js b/lib/timeline/component/Group.js index 883d8580..58920dfa 100644 --- a/lib/timeline/component/Group.js +++ b/lib/timeline/component/Group.js @@ -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; diff --git a/lib/timeline/component/ItemSet.js b/lib/timeline/component/ItemSet.js index d3f670fe..23798ecf 100644 --- a/lib/timeline/component/ItemSet.js +++ b/lib/timeline/component/ItemSet.js @@ -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) { diff --git a/lib/timeline/optionsTimeline.js b/lib/timeline/optionsTimeline.js index 6251bcac..41ac8d0b 100644 --- a/lib/timeline/optionsTimeline.js +++ b/lib/timeline/optionsTimeline.js @@ -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}, diff --git a/misc/we_need_help.md b/misc/we_need_help.md index 7eb941bc..bba818a2 100644 --- a/misc/we_need_help.md +++ b/misc/we_need_help.md @@ -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) diff --git a/package.json b/package.json index f7698cd3..2695c51f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/test/TimeStep.test.js b/test/TimeStep.test.js new file mode 100644 index 00000000..4ab3b2ca --- /dev/null +++ b/test/TimeStep.test.js @@ -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"); + }); +}); \ No newline at end of file