diff --git a/src/timeline/component/Group.js b/src/timeline/component/Group.js index e2d75c87..acdd9028 100644 --- a/src/timeline/component/Group.js +++ b/src/timeline/component/Group.js @@ -66,6 +66,13 @@ Group.prototype.getBackgroundHeight = function getBackgroundHeight() { return this.itemSet.height; }; +/** + * Repaint this group + */ +Group.prototype.repaint = function repaint() { + +}; + /** * Show this group: attach to the DOM */ diff --git a/src/timeline/component/ItemSet.js b/src/timeline/component/ItemSet.js index 842bb0ed..baa136d2 100644 --- a/src/timeline/component/ItemSet.js +++ b/src/timeline/component/ItemSet.js @@ -273,17 +273,19 @@ ItemSet.prototype.getFrame = function getFrame() { * 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. * + * @param {{byStart: Item[], byEnd: Item[]}} orderedItems + * @param {{start: number, end: number}} range * @param {Boolean} byEnd * @returns {number} * @private */ -ItemSet.prototype._binarySearch = function _binarySearch(byEnd) { - var array = [] +ItemSet.prototype._binarySearch = function _binarySearch(orderedItems, range, byEnd) { + var array = []; var byTime = byEnd ? "end" : "start"; - if (byEnd == true) {array = this.orderedItems.byEnd; } - else {array = this.orderedItems.byStart;} + if (byEnd == true) {array = orderedItems.byEnd; } + else {array = orderedItems.byStart;} - var interval = this.range.end - this.range.start; + var interval = range.end - range.start; var found = false; var low = 0; @@ -293,7 +295,7 @@ ItemSet.prototype._binarySearch = function _binarySearch(byEnd) { if (high == 0) {guess = -1;} else if (high == 1) { - if ((array[guess].data[byTime] > this.range.start - interval) && (array[guess].data[byTime] < this.range.end)) { + if ((array[guess].data[byTime] > range.start - interval) && (array[guess].data[byTime] < range.end)) { guess = 0; } else { @@ -303,11 +305,11 @@ ItemSet.prototype._binarySearch = function _binarySearch(byEnd) { else { high -= 1; while (found == false) { - if ((array[guess].data[byTime] > this.range.start - interval) && (array[guess].data[byTime] < this.range.end)) { + if ((array[guess].data[byTime] > range.start - interval) && (array[guess].data[byTime] < range.end)) { found = true; } else { - if (array[guess].data[byTime] < this.range.start - interval) { // it is too small --> increase low + if (array[guess].data[byTime] < range.start - interval) { // it is too small --> increase low low = Math.floor(0.5*(high+low)); } else { // it is too big --> decrease high @@ -326,21 +328,22 @@ ItemSet.prototype._binarySearch = function _binarySearch(byEnd) { } } return guess; -} +}; /** * this function checks if an item is invisible. If it is NOT we make it visible and add it to the global visible items. If it is, return true. * - * @param {itemRange | itemPoint | itemBox} item + * @param {Item} item + * @param {Item[]} visibleItems * @returns {boolean} * @private */ -ItemSet.prototype._checkIfInvisible = function _checkIfInvisible(item) { +ItemSet.prototype._checkIfInvisible = function _checkIfInvisible(item, visibleItems) { if (item.isVisible(this.range)) { if (!item.displayed) item.show(); item.repositionX(); - if (this.visibleItems.indexOf(item) == -1) { - this.visibleItems.push(item); + if (visibleItems.indexOf(item) == -1) { + visibleItems.push(item); } return false; } @@ -355,7 +358,7 @@ ItemSet.prototype._checkIfInvisible = function _checkIfInvisible(item) { * this one is for brute forcing and hiding. * * @param {Item} item - * @param {array} visibleItems + * @param {Array} visibleItems * @private */ ItemSet.prototype._checkIfVisible = function _checkIfVisible(item, visibleItems) { @@ -392,51 +395,9 @@ ItemSet.prototype.repaint = function repaint() { this.lastVisibleInterval = visibleInterval; this.lastWidth = this.width; - var newVisibleItems = []; - var item; - var orderedItems = this.orderedItems; + this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, this.range); - // first check if the items that were in view previously are still in view. - // this handles the case for the ItemRange that is both before and after the current one. - if (this.visibleItems.length > 0) { - for (i = 0; i < this.visibleItems.length; i++) { - this._checkIfVisible(this.visibleItems[i], newVisibleItems); - } - } - this.visibleItems = newVisibleItems; - - // If there were no visible items previously, use binarySearch to find a visible ItemPoint or ItemRange (based on startTime) - if (this.visibleItems.length == 0) { - initialPosByStart = this._binarySearch(false); - } - else { - initialPosByStart = orderedItems.byStart.indexOf(this.visibleItems[0]); - } - - // use visible search to find a visible ItemRange (only based on endTime) - var initialPosByEnd = this._binarySearch(true); - - // if we found a initial ID to use, trace it up and down until we meet an invisible item. - if (initialPosByStart != -1) { - for (i = initialPosByStart; i >= 0; i--) { - if (this._checkIfInvisible(orderedItems.byStart[i])) {break;} - } - for (i = initialPosByStart + 1; i < orderedItems.byStart.length; i++) { - if (this._checkIfInvisible(orderedItems.byStart[i])) {break;} - } - } - - // if we found a initial ID to use, trace it up and down until we meet an invisible item. - if (initialPosByEnd != -1) { - for (i = initialPosByEnd; i >= 0; i--) { - if (this._checkIfInvisible(orderedItems.byEnd[i])) {break;} - } - for (i = initialPosByEnd + 1; i < orderedItems.byEnd.length; i++) { - if (this._checkIfInvisible(orderedItems.byEnd[i])) {break;} - } - } - - // reposition visible items vertically + // reposition visible items vertically. //this.stack.order(this.visibleItems); // TODO: improve ordering var force = this.stackDirty || zoomed; // force re-stacking of all items if true this.stack.stack(this.visibleItems, force); @@ -491,6 +452,61 @@ ItemSet.prototype.repaint = function repaint() { return this._isResized(); }; +/** + * Update the visible items + * @param {{byStart: Item[], byEnd: Item[]}} orderedItems All items ordered by start date and by end date + * @param {Item[]} visibleItems The previously visible items. + * @param {{start: number, end: number}} range Visible range + * @return {Item[]} visibleItems The new visible items. + * @private + */ +ItemSet.prototype._updateVisibleItems = function _updateVisibleItems(orderedItems, visibleItems, range) { + var initialPosByStart, + newVisibleItems = [], + i; + + // first check if the items that were in view previously are still in view. + // this handles the case for the ItemRange that is both before and after the current one. + if (visibleItems.length > 0) { + for (i = 0; i < visibleItems.length; i++) { + this._checkIfVisible(visibleItems[i], newVisibleItems); + } + } + + // If there were no visible items previously, use binarySearch to find a visible ItemPoint or ItemRange (based on startTime) + if (newVisibleItems.length == 0) { + initialPosByStart = this._binarySearch(orderedItems, range, false); + } + else { + initialPosByStart = orderedItems.byStart.indexOf(newVisibleItems[0]); + } + + // use visible search to find a visible ItemRange (only based on endTime) + var initialPosByEnd = this._binarySearch(orderedItems, range, true); + + // if we found a initial ID to use, trace it up and down until we meet an invisible item. + if (initialPosByStart != -1) { + for (i = initialPosByStart; i >= 0; i--) { + if (this._checkIfInvisible(orderedItems.byStart[i], newVisibleItems)) {break;} + } + for (i = initialPosByStart + 1; i < orderedItems.byStart.length; i++) { + if (this._checkIfInvisible(orderedItems.byStart[i], newVisibleItems)) {break;} + } + } + + // if we found a initial ID to use, trace it up and down until we meet an invisible item. + if (initialPosByEnd != -1) { + for (i = initialPosByEnd; i >= 0; i--) { + if (this._checkIfInvisible(orderedItems.byEnd[i], newVisibleItems)) {break;} + } + for (i = initialPosByEnd + 1; i < orderedItems.byEnd.length; i++) { + if (this._checkIfInvisible(orderedItems.byEnd[i], newVisibleItems)) {break;} + } + } + + return newVisibleItems; +}; + /** * Get the foreground container element * @return {HTMLElement} foreground diff --git a/src/timeline/component/item/Item.js b/src/timeline/component/item/Item.js index f8a46828..064d8c6a 100644 --- a/src/timeline/component/item/Item.js +++ b/src/timeline/component/item/Item.js @@ -58,6 +58,16 @@ Item.prototype.setParent = function setParent(parent) { } }; +/** + * Check whether this item is visible inside given range + * @returns {{start: Number, end: Number}} range with a timestamp for start and end + * @returns {boolean} True if visible + */ +Item.prototype.isVisible = function isVisible (range) { + // Should be implemented by Item implementations + return false; +}; + /** * Show the Item in the DOM (when not already visible) * @return {Boolean} changed diff --git a/src/timeline/component/item/ItemPoint.js b/src/timeline/component/item/ItemPoint.js index 3fe16be1..eb6cca3f 100644 --- a/src/timeline/component/item/ItemPoint.js +++ b/src/timeline/component/item/ItemPoint.js @@ -40,9 +40,10 @@ ItemPoint.prototype = new Item (null, null); */ ItemPoint.prototype.isVisible = function isVisible (range) { // determine visibility - var interval = (range.end - range.start); - return (this.data.start > range.start - interval) && (this.data.start < range.end); -} + // TODO: account for the real width of the item. Right now we just add 1/4 to the window + var interval = (range.end - range.start) / 4; + return (this.data.start > range.start - interval) && (this.data.start < range.end + interval); +}; /** * Repaint the item