Skip to content
This repository has been archived by the owner on Nov 19, 2019. It is now read-only.

Commit

Permalink
Stack nlogn
Browse files Browse the repository at this point in the history
  • Loading branch information
saharki committed Sep 24, 2018
1 parent 67817f0 commit 75333a7
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 19 deletions.
104 changes: 99 additions & 5 deletions dist/timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
* timeline plus
* https://yotamberk.github.io/timeline-plus
*
* @version 2.1.7
* @date 2018-09-18
* @version 2.1.8
* @date 2018-09-25
*
*/

Expand Down Expand Up @@ -7521,7 +7521,7 @@ var Group = function () {
var customOrderedItems = _this.orderedItems.byStart.slice().sort(function (a, b) {
return me.itemSet.options.order(a.data, b.data);
});
_this.shouldBailStackItems = stack.stack(customOrderedItems, margin, true, _this._shouldBailItemsRedraw.bind(_this));
_this.shouldBailStackItems = stack.stack(customOrderedItems, !!_this.itemSet.options.order, margin, true, _this._shouldBailItemsRedraw.bind(_this));
}

_this.visibleItems = _this._updateItemsInRange(_this.orderedItems, _this.visibleItems, range);
Expand All @@ -7539,7 +7539,7 @@ var Group = function () {
stack.stackSubgroupsWithInnerStack(visibleSubgroups, margin, this.subgroups);
} else {
// TODO: ugly way to access options...
this.shouldBailStackItems = stack.stack(this.visibleItems, margin, true, this._shouldBailItemsRedraw.bind(this));
this.shouldBailStackItems = stack.stack(this.visibleItems, !!this.itemSet.options.order, margin, true, this._shouldBailItemsRedraw.bind(this));
}
} else {
// no stacking
Expand Down Expand Up @@ -15627,6 +15627,7 @@ Object.defineProperty(exports, "__esModule", {
});
exports.orderByStart = orderByStart;
exports.orderByEnd = orderByEnd;
exports.orderByEndAndWidth = orderByEndAndWidth;
exports.stack = stack;
exports.substack = substack;
exports.nostack = nostack;
Expand Down Expand Up @@ -15661,6 +15662,20 @@ function orderByEnd(items) {
});
}

/**
* Order items by their end date. If they have no end date, their start date + their width in time units
* is used.
* @param {Item[]} items
*/
function orderByEndAndWidth(items) {
items.sort(function (a, b) {
var aTime = 'end' in a.data ? a.data.end : new Date(a.data.start.getTime() + timeline.range.getMillisecondsPerPixel() * a.width);
var bTime = 'end' in b.data ? b.data.end : new Date(b.data.start.getTime() + timeline.range.getMillisecondsPerPixel() * b.width);

return aTime - bTime;
});
}

/**
* Adjust vertical positions of the items such that they don't overlap each
* other.
Expand All @@ -15675,7 +15690,7 @@ function orderByEnd(items) {
* bailing function
* @return {boolean} shouldBail
*/
function stack(items, margin, force, shouldBailItemsRedrawFunction) {
function _oldStack(items, margin, force, shouldBailItemsRedrawFunction) {
if (force) {
// reset top position of all items
for (var i = 0; i < items.length; i++) {
Expand Down Expand Up @@ -15720,6 +15735,82 @@ function stack(items, margin, force, shouldBailItemsRedrawFunction) {
return shouldBail;
}

/**
* Adjust vertical positions of the items such that they don't overlap each
* other.
* @param {Item[]} items
* All visible items
* @param {boolean} isOrdered
* If true, items are pre-ordered and old stacking algorithm will be used.
* otherwise, the new stacking algorithm will be used.
* @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
* Margins between items and between items and the axis.
* @param {boolean} [force=false]
* If true, all items will be repositioned. If false (default), only
* items having a top===null will be re-stacked
* @param {function} shouldBailItemsRedrawFunction
* bailing function
* @return {boolean} shouldBail
*/
function stack(items, isOrdered, margin, force, shouldBailItemsRedrawFunction) {

//_oldStack algorithm is used when it's required to maintain the order of the items.
//In case _oldStack function is called, time complexity will be O(n^2) instead of O(n*log(n)).
if (isOrdered) {
return _oldStack(items, margin, force, shouldBailItemsRedrawFunction);
}

if (force) {
// reset top position of all items
for (var i = 0; i < items.length; i++) {
items[i].top = null;
}
}

orderByEndAndWidth(items);

var shouldBail = false;
var collisionQueue = [];

// calculate new, non-overlapping positions
for (var i = 0; i < items.length; i++) {
var item = items[i];

if (item && item.stack) {

// initialize top position
if (item.top == null) {
item.top = margin.axis;
}

shouldBail = shouldBailItemsRedrawFunction() || false;

if (shouldBail) {
return true;
}

var firstItemToCollide = collisionQueue[0];
var lastItem = collisionQueue[collisionQueue.length - 1];

if (!item.stack) {
item.top = margin.axis;
} else if (item && item !== firstItemToCollide && item.stack && firstItemToCollide && firstItemToCollide.top !== null) {
// If the item collides with the first item in the collisionQueue - which is the item that has the earliest end date, increase item top.
// else, set top as the top of the first item in the collisionQueue.
if (!collision(firstItemToCollide, item, margin.item, item.options.rtl)) {
item.top = firstItemToCollide.top;
collisionQueue.shift();
} else if (lastItem && lastItem.top) {
item.top = lastItem.top + lastItem.height + margin.item.vertical;
}
}
collisionQueue.push(item);
}
}

return shouldBail;
}

/**
* Adjust vertical positions of the items within a single subgroup such that they
* don't overlap each other.
Expand Down Expand Up @@ -15929,6 +16020,9 @@ function collisionByTimes(a, b) {
return a.start <= b.start && a.end >= b.start && a.top < b.top + b.height && a.top + a.height > b.top || b.start <= a.start && b.end >= a.start && b.top < a.top + a.height && b.top + b.height > a.top;
}

// WEBPACK FOOTER //
// ./lib/timeline/Stack.js

/***/ }),
/* 78 */
/***/ (function(module, exports, __webpack_require__) {
Expand Down
2 changes: 1 addition & 1 deletion dist/timeline.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/timeline.map

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions dist/timeline.min.js

Large diffs are not rendered by default.

96 changes: 94 additions & 2 deletions lib/timeline/Stack.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ export function orderByEnd(items) {
});
}

/**
* Order items by their end date. If they have no end date, their start date + their width in time units
* is used.
* @param {Item[]} items
*/
export function orderByEndAndWidth(items) {
items.sort((a, b) => {
const aTime = ('end' in a.data) ? a.data.end : new Date(a.data.start.getTime() + timeline.range.getMillisecondsPerPixel() * a.width);
const bTime = ('end' in b.data) ? b.data.end : new Date(b.data.start.getTime() + timeline.range.getMillisecondsPerPixel() * b.width);

return aTime - bTime;
});
}

/**
* Adjust vertical positions of the items such that they don't overlap each
* other.
Expand All @@ -37,7 +51,7 @@ export function orderByEnd(items) {
* bailing function
* @return {boolean} shouldBail
*/
export function stack(items, margin, force, shouldBailItemsRedrawFunction) {
function _oldStack(items, margin, force, shouldBailItemsRedrawFunction) {
if (force) {
// reset top position of all items
for (var i = 0; i < items.length; i++) {
Expand Down Expand Up @@ -79,6 +93,80 @@ export function stack(items, margin, force, shouldBailItemsRedrawFunction) {
return shouldBail;
}

/**
* Adjust vertical positions of the items such that they don't overlap each
* other.
* @param {Item[]} items
* All visible items
* @param {boolean} isOrdered
* If true, items are pre-ordered and old stacking algorithm will be used.
* otherwise, the new stacking algorithm will be used.
* @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
* Margins between items and between items and the axis.
* @param {boolean} [force=false]
* If true, all items will be repositioned. If false (default), only
* items having a top===null will be re-stacked
* @param {function} shouldBailItemsRedrawFunction
* bailing function
* @return {boolean} shouldBail
*/
export function stack(items, isOrdered, margin, force, shouldBailItemsRedrawFunction) {

// _oldStack algorithm is used when it's required to maintain the order of the items.
// In case _oldStack function is called, time complexity will be O(n^2) instead of O(n*log(n)).
if(isOrdered) {
return _oldStack(items, margin, force, shouldBailItemsRedrawFunction)
}

if (force) {
// reset top position of all items
for (var i = 0; i < items.length; i++) {
items[i].top = null;
}
}

orderByEndAndWidth(items);

let shouldBail = false;
let collisionQueue = []

// calculate new, non-overlapping positions
for (var i = 0; i < items.length; i++) {
const item = items[i];

if (item && item.stack) {

// initialize top position
if (item.top == null) { item.top = margin.axis; }

shouldBail = shouldBailItemsRedrawFunction() || false;

if (shouldBail) { return true; }

const firstItemToCollide = collisionQueue[0]
const lastItem = collisionQueue[collisionQueue.length - 1]

if (!item.stack) {
item.top = margin.axis;
}
else if (item && item !== firstItemToCollide && item.stack && firstItemToCollide && firstItemToCollide.top !== null) {
// If the item collides with the first item in the collisionQueue - which is the item that has the earliest end date, increase item top.
// else, set top as the top of the first item in the collisionQueue.
if (!collision(firstItemToCollide, item, margin.item, item.options.rtl)) {
item.top = firstItemToCollide.top;
collisionQueue.shift()
}
else if(lastItem && lastItem.top) {
item.top = lastItem.top + lastItem.height + margin.item.vertical;
}
}
collisionQueue.push(item)
}
}

return shouldBail;
}

/**
* Adjust vertical positions of the items within a single subgroup such that they
* don't overlap each other.
Expand Down Expand Up @@ -295,4 +383,8 @@ export function collision(a, b, margin, rtl) {
export function collisionByTimes(a, b) {
return (a.start <= b.start && a.end >= b.start && a.top < (b.top + b.height) && (a.top + a.height) > b.top ) ||
(b.start <= a.start && b.end >= a.start && b.top < (a.top + a.height) && (b.top + b.height) > a.top );
}
}


// WEBPACK FOOTER //
// ./lib/timeline/Stack.js
4 changes: 2 additions & 2 deletions lib/timeline/component/Group.js
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ class Group {
else {
// order all items and force a restacking
const customOrderedItems = this.orderedItems.byStart.slice().sort((a, b) => me.itemSet.options.order(a.data, b.data));
this.shouldBailStackItems = stack.stack(customOrderedItems, margin, true, this._shouldBailItemsRedraw.bind(this));
this.shouldBailStackItems = stack.stack(customOrderedItems, !!this.itemSet.options.order, margin, true, this._shouldBailItemsRedraw.bind(this));
}

this.visibleItems = this._updateItemsInRange(this.orderedItems, this.visibleItems, range);
Expand All @@ -385,7 +385,7 @@ class Group {
}
else {
// TODO: ugly way to access options...
this.shouldBailStackItems = stack.stack(this.visibleItems, margin, true, this._shouldBailItemsRedraw.bind(this));
this.shouldBailStackItems = stack.stack(this.visibleItems, !!this.itemSet.options.order, margin, true, this._shouldBailItemsRedraw.bind(this));
}
} else {
// no stacking
Expand Down

0 comments on commit 75333a7

Please sign in to comment.