Browse Source

Optimised a serious slowdown on performance since hidden dates. Clarified binary search

v3_develop
Alex de Mulder 9 years ago
parent
commit
6ded752e03
5 changed files with 5092 additions and 5111 deletions
  1. +10
    -1
      HISTORY.md
  2. +4990
    -5004
      dist/vis.js
  3. +2
    -2
      lib/timeline/Core.js
  4. +49
    -37
      lib/timeline/DateUtil.js
  5. +41
    -67
      lib/util.js

+ 10
- 1
HISTORY.md View File

@ -8,6 +8,7 @@ http://visjs.org
- Fixed height of background items when having a fixed or max height defined.
- Fixed only one item being dragged when multiple items are selected.
- Optimised a serious slowdown on performance since hidden dates.
### Network
@ -20,7 +21,15 @@ http://visjs.org
- Throw error message when items are added before groups.
- Made graphHeight automatic if height is defined AND if graphHeight is smaller than the center panel when height is defined as well.
- Added new verticalDrag event for internal use, allowing the vertical scrolling of the grid lines on drag.
- Fixed moving legend when postioned on the bottom and vertical dragging
- Fixed moving legend when postioned on the bottom and vertical dragging.
- Optimised a serious slowdown on performance since hidden dates.
- Accepted a large pull request from @cdjackson adding the following features (thank you!):
- Titles on the DataAxis to explain what units you are using.
- A style field for groups and datapoints so you can dynamically change styles.
- A precision option to manually set the amount of decimals.
- Two new examples showing the new features.
## 2014-10-28, version 3.6.3

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


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

@ -638,7 +638,7 @@ Core.prototype.getCurrentTime = function() {
*/
// TODO: move this function to Range
Core.prototype._toTime = function(x) {
return DateUtil.toTime(this.body, this.range, x, this.props.center.width);
return DateUtil.toTime(this, x, this.props.center.width);
};
/**
@ -649,7 +649,7 @@ Core.prototype._toTime = function(x) {
*/
// TODO: move this function to Range
Core.prototype._toGlobalTime = function(x) {
return DateUtil.toTime(this.body, this.range, x, this.props.root.width);
return DateUtil.toTime(this, x, this.props.root.width);
//var conversion = this.range.conversion(this.props.root.width);
//return new Date(x / conversion.scale + conversion.offset);
};

+ 49
- 37
lib/timeline/DateUtil.js View File

@ -249,28 +249,28 @@ exports.stepOverHiddenDates = function(timeStep, previousTime) {
};
/**
* Used in TimeStep to avoid the hidden times.
* @param timeStep
* @param previousTime
*/
exports.checkFirstStep = function(timeStep) {
var stepInHidden = false;
var currentValue = timeStep.current.valueOf();
for (var i = 0; i < timeStep.hiddenDates.length; i++) {
var startDate = timeStep.hiddenDates[i].start;
var endDate = timeStep.hiddenDates[i].end;
if (currentValue >= startDate && currentValue < endDate) {
stepInHidden = true;
break;
}
}
if (stepInHidden == true && currentValue <= timeStep._end.valueOf()) {
var newValue = moment(endDate);
timeStep.current = newValue.toDate();
}
};
///**
// * Used in TimeStep to avoid the hidden times.
// * @param timeStep
// * @param previousTime
// */
//exports.checkFirstStep = function(timeStep) {
// var stepInHidden = false;
// var currentValue = timeStep.current.valueOf();
// for (var i = 0; i < timeStep.hiddenDates.length; i++) {
// var startDate = timeStep.hiddenDates[i].start;
// var endDate = timeStep.hiddenDates[i].end;
// if (currentValue >= startDate && currentValue < endDate) {
// stepInHidden = true;
// break;
// }
// }
//
// if (stepInHidden == true && currentValue <= timeStep._end.valueOf()) {
// var newValue = moment(endDate);
// timeStep.current = newValue.toDate();
// }
//};
/**
* replaces the Core toScreen methods
@ -280,16 +280,22 @@ exports.checkFirstStep = function(timeStep) {
* @returns {number}
*/
exports.toScreen = function(Core, time, width) {
var hidden = exports.isHidden(time, Core.body.hiddenDates)
if (hidden.hidden == true) {
time = hidden.startDate;
if (Core.body.hiddenDates.length == 0) {
var conversion = Core.range.conversion(width);
return (time.valueOf() - conversion.offset) * conversion.scale;
}
else {
var hidden = exports.isHidden(time, Core.body.hiddenDates)
if (hidden.hidden == true) {
time = hidden.startDate;
}
var duration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end);
time = exports.correctTimeForHidden(Core.body.hiddenDates, Core.range, time);
var duration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end);
time = exports.correctTimeForHidden(Core.body.hiddenDates, Core.range, time);
var conversion = Core.range.conversion(width, duration);
return (time.valueOf() - conversion.offset) * conversion.scale;
var conversion = Core.range.conversion(width, duration);
return (time.valueOf() - conversion.offset) * conversion.scale;
}
};
@ -301,14 +307,20 @@ exports.toScreen = function(Core, time, width) {
* @param width
* @returns {Date}
*/
exports.toTime = function(body, range, x, width) {
var hiddenDuration = exports.getHiddenDurationBetween(body.hiddenDates, range.start, range.end);
var totalDuration = range.end - range.start - hiddenDuration;
var partialDuration = totalDuration * x / width;
var accumulatedHiddenDuration = exports.getAccumulatedHiddenDuration(body.hiddenDates,range, partialDuration);
var newTime = new Date(accumulatedHiddenDuration + partialDuration + range.start);
return newTime;
exports.toTime = function(Core, x, width) {
if (Core.body.hiddenDates.length == 0) {
var conversion = Core.range.conversion(width);
return new Date(x / conversion.scale + conversion.offset);
}
else {
var hiddenDuration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end);
var totalDuration = Core.range.end - Core.range.start - hiddenDuration;
var partialDuration = totalDuration * x / width;
var accumulatedHiddenDuration = exports.getAccumulatedHiddenDuration(Core.body.hiddenDates, Core.range, partialDuration);
var newTime = new Date(accumulatedHiddenDuration + partialDuration + Core.range.start);
return newTime;
}
};

+ 41
- 67
lib/util.js View File

@ -1138,14 +1138,8 @@ exports.mergeOptions = function (mergeTarget, options, option) {
/**
* This function does a binary search for a visible item. The user can select either the this.orderedItems.byStart or .byEnd
* arrays. This is done by giving a boolean value true if you want to use the byEnd.
* This is done to be able to select the correct if statement (we do not want to check if an item is visible, we want to check
* if the time we selected (start or end) is within the current range).
*
* The trick is that every interval has to either enter the screen at the initial load or by dragging. The case of the RangeItem that is
* before and after the current range is handled by simply checking if it was in view before and if it is again. For all the rest,
* either the start OR end time has to be in the range.
* This function does a binary search for a visible item in a sorted list. If we find a visible item, the code that uses
* this function will then iterate in both directions over this sorted list to find all visible items.
*
* @param {Item[]} orderedItems Items ordered by start
* @param {{start: number, end: number}} range
@ -1183,14 +1177,9 @@ exports.binarySearch = function(orderedItems, range, field, field2) {
};
/**
* This function does a binary search for a visible item. The user can select either the this.orderedItems.byStart or .byEnd
* arrays. This is done by giving a boolean value true if you want to use the byEnd.
* This is done to be able to select the correct if statement (we do not want to check if an item is visible, we want to check
* if the time we selected (start or end) is within the current range).
*
* The trick is that every interval has to either enter the screen at the initial load or by dragging. The case of the RangeItem that is
* before and after the current range is handled by simply checking if it was in view before and if it is again. For all the rest,
* either the start OR end time has to be in the range.
* This function does a binary search for a specific value in a sorted array. If it does not exist but is in between of
* two values, we return either the one before or the one after, depending on user input
* If it is found, we return the index, else -1.
*
* @param {Array} orderedItems
* @param {{start: number, end: number}} target
@ -1202,73 +1191,58 @@ exports.binarySearch = function(orderedItems, range, field, field2) {
exports.binarySearchGeneric = function(orderedItems, target, field, sidePreference) {
var maxIterations = 10000;
var iteration = 0;
var array = orderedItems;
var found = false;
var low = 0;
var high = array.length;
var high = orderedItems.length - 1;
var newLow = low;
var newHigh = high;
var guess = Math.floor(0.5*(high+low));
var newGuess;
var prevValue, value, nextValue;
if (high == 0) {guess = -1;}
else if (high == 1) {
value = array[guess][field];
if (value == target) {
guess = 0;
}
else {
guess = -1;
}
var prevValue, value, nextValue, middle;
if (orderedItems.length == 0) {
return -1; // did not find the target value
}
else if (orderedItems.length == 1) {
if (orderedItems[0][field] == target) {return 0;} // if the value matched the value we look for, return index 0
else {return -1;}
}
else {
high -= 1;
while (found == false && iteration < maxIterations) {
prevValue = array[Math.max(0,guess - 1)][field];
value = array[guess][field];
nextValue = array[Math.min(array.length-1,guess + 1)][field];
if (value == target || prevValue < target && value > target || value < target && nextValue > target) {
found = true;
if (value != target) {
if (sidePreference == 'before') {
if (prevValue < target && value > target) {
guess = Math.max(0,guess - 1);
}
}
else {
if (value < target && nextValue > target) {
guess = Math.min(array.length-1,guess + 1);
}
}
}
while (iteration < maxIterations) {
// get a new guess
middle = Math.floor(0.5*(high+low));
prevValue = orderedItems[Math.max(0,middle - 1)][field];
value = orderedItems[middle][field];
nextValue = orderedItems[Math.min(orderedItems.length-1,middle + 1)][field];
if (value == target) { // we found the target
return middle;
}
else {
else if (prevValue < target && value > target) { // target is in between of the previous and the current
return sidePreference == 'before' ? Math.max(0,middle - 1) : middle;
}
else if (value < target && nextValue > target) { // target is in between of the current and the next
return sidePreference == 'before' ? middle : Math.min(orderedItems.length-1,middle + 1);
}
else { // didnt find the target, we need to change our boundaries.
if (value < target) { // it is too small --> increase low
newLow = Math.floor(0.5*(high+low));
newLow = middle;
}
else { // it is too big --> decrease high
newHigh = Math.floor(0.5*(high+low));
newHigh = middle;
}
newGuess = Math.floor(0.5*(high+low));
// not in list;
if (low == newLow && high == newHigh) {
guess = -1;
found = true;
if (low == newLow && high == newHigh) { // if we did not find it and our new boundaries are the same as the old, the target is not in the list
return -1;
}
else {
high = newHigh; low = newLow;
guess = Math.floor(0.5*(high+low));
else { // we have new boundaries, lets use them!
high = newHigh;
low = newLow;
}
}
iteration++;
}
if (iteration >= maxIterations) {
console.log("BinarySearch too many iterations. Aborting.");
}
}
return guess;
// didnt find anything. Return the last guess.
console.log("BinarySearchGeneric did too many iterations. Aborting.");
return middle;
};
/**

Loading…
Cancel
Save