|  | // Utility functions for ordering and stacking of items | 
						
						
							|  | var EPSILON = 0.001; // used when checking collisions, to prevent round-off errors | 
						
						
							|  |  | 
						
						
							|  | /** | 
						
						
							|  |  * Order items by their start data | 
						
						
							|  |  * @param {Item[]} items | 
						
						
							|  |  */ | 
						
						
							|  | exports.orderByStart = function(items) { | 
						
						
							|  |   items.sort(function (a, b) { | 
						
						
							|  |     return a.data.start - b.data.start; | 
						
						
							|  |   }); | 
						
						
							|  | }; | 
						
						
							|  | 
 | 
						
						
							|  | /** | 
						
						
							|  |  * Order items by their end date. If they have no end date, their start date | 
						
						
							|  |  * is used. | 
						
						
							|  |  * @param {Item[]} items | 
						
						
							|  |  */ | 
						
						
							|  | exports.orderByEnd = function(items) { | 
						
						
							|  |   items.sort(function (a, b) { | 
						
						
							|  |     var aTime = ('end' in a.data) ? a.data.end : a.data.start, | 
						
						
							|  |         bTime = ('end' in b.data) ? b.data.end : b.data.start; | 
						
						
							|  | 
 | 
						
						
							|  |     return aTime - bTime; | 
						
						
							|  |   }); | 
						
						
							|  | }; | 
						
						
							|  | 
 | 
						
						
							|  | /** | 
						
						
							|  |  * Adjust vertical positions of the items such that they don't overlap each | 
						
						
							|  |  * other. | 
						
						
							|  |  * @param {Item[]} items | 
						
						
							|  |  *            All visible items | 
						
						
							|  |  * @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 | 
						
						
							|  |  */ | 
						
						
							|  | exports.stack = function(items, margin, force) { | 
						
						
							|  |   if (force) { | 
						
						
							|  |     // reset top position of all items | 
						
						
							|  |     for (var i = 0; i < items.length; i++) { | 
						
						
							|  |       items[i].top = null; | 
						
						
							|  |     } | 
						
						
							|  |   } | 
						
						
							|  | 
 | 
						
						
							|  |   // calculate new, non-overlapping positions | 
						
						
							|  |   for (var i = 0; i < items.length; i++) { | 
						
						
							|  |     var item = items[i]; | 
						
						
							|  |     if (item.stack && item.top === null) { | 
						
						
							|  |       // initialize top position | 
						
						
							|  |       item.top = margin.axis; | 
						
						
							|  | 
 | 
						
						
							|  |       do { | 
						
						
							|  |         // TODO: optimize checking for overlap. when there is a gap without items, | 
						
						
							|  |         //       you only need to check for items from the next item on, not from zero | 
						
						
							|  |         var collidingItem = null; | 
						
						
							|  |         for (var j = 0, jj = items.length; j < jj; j++) { | 
						
						
							|  |           var other = items[j]; | 
						
						
							|  |           if (other.top !== null && other !== item && other.stack && exports.collision(item, other, margin.item, other.options.rtl)) { | 
						
						
							|  |             collidingItem = other; | 
						
						
							|  |             break; | 
						
						
							|  |           } | 
						
						
							|  |         } | 
						
						
							|  | 
 | 
						
						
							|  |         if (collidingItem != null) { | 
						
						
							|  |           // There is a collision. Reposition the items above the colliding element | 
						
						
							|  |           item.top = collidingItem.top + collidingItem.height + margin.item.vertical; | 
						
						
							|  |         } | 
						
						
							|  |       } while (collidingItem); | 
						
						
							|  |     } | 
						
						
							|  |   } | 
						
						
							|  | }; | 
						
						
							|  | 
 | 
						
						
							|  | 
 | 
						
						
							|  | /** | 
						
						
							|  |  * Adjust vertical positions of the items without stacking them | 
						
						
							|  |  * @param {Item[]} items | 
						
						
							|  |  *            All visible items | 
						
						
							|  |  * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin | 
						
						
							|  |  *            Margins between items and between items and the axis. | 
						
						
							|  | * @param {subgroups[]} subgroups | 
						
						
							|  |  *            All subgroups | 
						
						
							|  |  */ | 
						
						
							|  |  exports.nostack = function(items, margin, subgroups, stackSubgroups) { | 
						
						
							|  |   for (var i = 0; i < items.length; i++) { | 
						
						
							|  |     if (items[i].data.subgroup == undefined) { | 
						
						
							|  |       items[i].top = margin.item.vertical; | 
						
						
							|  |     } else if (items[i].data.subgroup !== undefined && stackSubgroups) { | 
						
						
							|  |       var newTop = 0; | 
						
						
							|  |       for (var subgroup in subgroups) { | 
						
						
							|  |         if (subgroups.hasOwnProperty(subgroup)) { | 
						
						
							|  |           if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroups[items[i].data.subgroup].index) { | 
						
						
							|  |             newTop += subgroups[subgroup].height; | 
						
						
							|  |             subgroups[items[i].data.subgroup].top = newTop; | 
						
						
							|  |           } | 
						
						
							|  |         } | 
						
						
							|  |       } | 
						
						
							|  |       items[i].top = newTop + 0.5 * margin.item.vertical; | 
						
						
							|  |     } | 
						
						
							|  |   } | 
						
						
							|  |   if (!stackSubgroups) { | 
						
						
							|  |     exports.stackSubgroups(items, margin, subgroups) | 
						
						
							|  |   } | 
						
						
							|  | }; | 
						
						
							|  | 
 | 
						
						
							|  | /** | 
						
						
							|  |  * Adjust vertical positions of the subgroups such that they don't overlap each | 
						
						
							|  |  * other. | 
						
						
							|  |  * @param {subgroups[]} subgroups | 
						
						
							|  |  *            All subgroups | 
						
						
							|  |  * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin | 
						
						
							|  |  *            Margins between items and between items and the axis. | 
						
						
							|  |  */ | 
						
						
							|  | exports.stackSubgroups = function(items, margin, subgroups) { | 
						
						
							|  |   for (var subgroup in subgroups) { | 
						
						
							|  |     if (subgroups.hasOwnProperty(subgroup)) { | 
						
						
							|  | 
 | 
						
						
							|  | 
 | 
						
						
							|  |       subgroups[subgroup].top = 0; | 
						
						
							|  |       do { | 
						
						
							|  |         // TODO: optimize checking for overlap. when there is a gap without items, | 
						
						
							|  |         //       you only need to check for items from the next item on, not from zero | 
						
						
							|  |         var collidingItem = null; | 
						
						
							|  |         for (var otherSubgroup in subgroups) { | 
						
						
							|  |           if (subgroups[otherSubgroup].top !== null && otherSubgroup !== subgroup && subgroups[subgroup].index > subgroups[otherSubgroup].index && exports.collisionByTimes(subgroups[subgroup], subgroups[otherSubgroup])) { | 
						
						
							|  |             collidingItem = subgroups[otherSubgroup]; | 
						
						
							|  |             break; | 
						
						
							|  |           } | 
						
						
							|  |         } | 
						
						
							|  | 
 | 
						
						
							|  |         if (collidingItem != null) { | 
						
						
							|  |           // There is a collision. Reposition the subgroups above the colliding element | 
						
						
							|  |           subgroups[subgroup].top = collidingItem.top + collidingItem.height; | 
						
						
							|  |         } | 
						
						
							|  |       } while (collidingItem); | 
						
						
							|  |     } | 
						
						
							|  |   } | 
						
						
							|  |   for (var i = 0; i < items.length; i++) { | 
						
						
							|  |     if (items[i].data.subgroup !== undefined) { | 
						
						
							|  |       items[i].top = subgroups[items[i].data.subgroup].top + 0.5 * margin.item.vertical; | 
						
						
							|  |     } | 
						
						
							|  |   } | 
						
						
							|  | } | 
						
						
							|  | 
 | 
						
						
							|  | /** | 
						
						
							|  |  * Test if the two provided items collide | 
						
						
							|  |  * The items must have parameters left, width, top, and height. | 
						
						
							|  |  * @param {Item} a          The first item | 
						
						
							|  |  * @param {Item} b          The second item | 
						
						
							|  |  * @param {{horizontal: number, vertical: number}} margin | 
						
						
							|  |  *                          An object containing a horizontal and vertical | 
						
						
							|  |  *                          minimum required margin. | 
						
						
							|  |  * @param {boolean} rtl | 
						
						
							|  |  * @return {boolean}        true if a and b collide, else false | 
						
						
							|  |  */ | 
						
						
							|  | exports.collision = function(a, b, margin, rtl) { | 
						
						
							|  |   if (rtl) { | 
						
						
							|  |     return  ((a.right - margin.horizontal + EPSILON)  < (b.right + b.width) && | 
						
						
							|  |     (a.right + a.width + margin.horizontal - EPSILON) > b.right && | 
						
						
							|  |     (a.top - margin.vertical + EPSILON)              < (b.top + b.height) && | 
						
						
							|  |     (a.top + a.height + margin.vertical - EPSILON)   > b.top); | 
						
						
							|  |   } else { | 
						
						
							|  |     return ((a.left - margin.horizontal + EPSILON)   < (b.left + b.width) && | 
						
						
							|  |     (a.left + a.width + margin.horizontal - EPSILON) > b.left && | 
						
						
							|  |     (a.top - margin.vertical + EPSILON)              < (b.top + b.height) && | 
						
						
							|  |     (a.top + a.height + margin.vertical - EPSILON)   > b.top); | 
						
						
							|  |   } | 
						
						
							|  | }; | 
						
						
							|  | 
 | 
						
						
							|  | /** | 
						
						
							|  |  * Test if the two provided objects collide | 
						
						
							|  |  * The objects must have parameters start, end, top, and height. | 
						
						
							|  |  * @param {Object} a          The first Object | 
						
						
							|  |  * @param {Object} b          The second Object | 
						
						
							|  |  * @return {boolean}        true if a and b collide, else false | 
						
						
							|  |  */ | 
						
						
							|  | exports.collisionByTimes = function(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 ) | 
						
						
							|  |   ) | 
						
						
							|  | }
 |