vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

190 lines
5.8 KiB

12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
  1. /**
  2. * @constructor Stack
  3. * Stacks items on top of each other.
  4. * @param {ItemSet} itemset
  5. * @param {Object} [options]
  6. */
  7. function Stack (itemset, options) {
  8. this.itemset = itemset;
  9. this.options = options || {};
  10. this.defaultOptions = {
  11. order: function (a, b) {
  12. //return (b.width - a.width) || (a.left - b.left); // TODO: cleanup
  13. // Order: ranges over non-ranges, ranged ordered by width, and
  14. // lastly ordered by start.
  15. if (a instanceof ItemRange) {
  16. if (b instanceof ItemRange) {
  17. var aInt = (a.data.end - a.data.start);
  18. var bInt = (b.data.end - b.data.start);
  19. return (aInt - bInt) || (a.data.start - b.data.start);
  20. }
  21. else {
  22. return -1;
  23. }
  24. }
  25. else {
  26. if (b instanceof ItemRange) {
  27. return 1;
  28. }
  29. else {
  30. return (a.data.start - b.data.start);
  31. }
  32. }
  33. },
  34. margin: {
  35. item: 10
  36. }
  37. };
  38. this.ordered = []; // ordered items
  39. }
  40. /**
  41. * Set options for the stack
  42. * @param {Object} options Available options:
  43. * {ItemSet} itemset
  44. * {Number} margin
  45. * {function} order Stacking order
  46. */
  47. Stack.prototype.setOptions = function setOptions (options) {
  48. util.extend(this.options, options);
  49. // TODO: register on data changes at the connected itemset, and update the changed part only and immediately
  50. };
  51. /**
  52. * Stack the items such that they don't overlap. The items will have a minimal
  53. * distance equal to options.margin.item.
  54. */
  55. Stack.prototype.update = function update() {
  56. this._order();
  57. this._stack();
  58. };
  59. /**
  60. * Order the items. If a custom order function has been provided via the options,
  61. * then this will be used.
  62. * @private
  63. */
  64. Stack.prototype._order = function _order () {
  65. var items = this.itemset.items;
  66. if (!items) {
  67. throw new Error('Cannot stack items: ItemSet does not contain items');
  68. }
  69. // TODO: store the sorted items, to have less work later on
  70. var ordered = [];
  71. var index = 0;
  72. // items is a map (no array)
  73. util.forEach(items, function (item) {
  74. if (item.visible) {
  75. ordered[index] = item;
  76. index++;
  77. }
  78. });
  79. //if a customer stack order function exists, use it.
  80. var order = this.options.order || this.defaultOptions.order;
  81. if (!(typeof order === 'function')) {
  82. throw new Error('Option order must be a function');
  83. }
  84. ordered.sort(order);
  85. this.ordered = ordered;
  86. };
  87. /**
  88. * Adjust vertical positions of the events such that they don't overlap each
  89. * other.
  90. * @private
  91. */
  92. Stack.prototype._stack = function _stack () {
  93. var i,
  94. iMax,
  95. ordered = this.ordered,
  96. options = this.options,
  97. orientation = options.orientation || this.defaultOptions.orientation,
  98. axisOnTop = (orientation == 'top'),
  99. margin;
  100. if (options.margin && options.margin.item !== undefined) {
  101. margin = options.margin.item;
  102. }
  103. else {
  104. margin = this.defaultOptions.margin.item
  105. }
  106. // calculate new, non-overlapping positions
  107. for (i = 0, iMax = ordered.length; i < iMax; i++) {
  108. var item = ordered[i];
  109. var collidingItem = null;
  110. do {
  111. // TODO: optimize checking for overlap. when there is a gap without items,
  112. // you only need to check for items from the next item on, not from zero
  113. collidingItem = this.checkOverlap(ordered, i, 0, i - 1, margin);
  114. if (collidingItem != null) {
  115. // There is a collision. Reposition the event above the colliding element
  116. if (axisOnTop) {
  117. item.top = collidingItem.top + collidingItem.height + margin;
  118. }
  119. else {
  120. item.top = collidingItem.top - item.height - margin;
  121. }
  122. }
  123. } while (collidingItem);
  124. }
  125. };
  126. /**
  127. * Check if the destiny position of given item overlaps with any
  128. * of the other items from index itemStart to itemEnd.
  129. * @param {Array} items Array with items
  130. * @param {int} itemIndex Number of the item to be checked for overlap
  131. * @param {int} itemStart First item to be checked.
  132. * @param {int} itemEnd Last item to be checked.
  133. * @return {Object | null} colliding item, or undefined when no collisions
  134. * @param {Number} margin A minimum required margin.
  135. * If margin is provided, the two items will be
  136. * marked colliding when they overlap or
  137. * when the margin between the two is smaller than
  138. * the requested margin.
  139. */
  140. Stack.prototype.checkOverlap = function checkOverlap (items, itemIndex,
  141. itemStart, itemEnd, margin) {
  142. var collision = this.collision;
  143. // we loop from end to start, as we suppose that the chance of a
  144. // collision is larger for items at the end, so check these first.
  145. var a = items[itemIndex];
  146. for (var i = itemEnd; i >= itemStart; i--) {
  147. var b = items[i];
  148. if (collision(a, b, margin)) {
  149. if (i != itemIndex) {
  150. return b;
  151. }
  152. }
  153. }
  154. return null;
  155. };
  156. /**
  157. * Test if the two provided items collide
  158. * The items must have parameters left, width, top, and height.
  159. * @param {Component} a The first item
  160. * @param {Component} b The second item
  161. * @param {Number} margin A minimum required margin.
  162. * If margin is provided, the two items will be
  163. * marked colliding when they overlap or
  164. * when the margin between the two is smaller than
  165. * the requested margin.
  166. * @return {boolean} true if a and b collide, else false
  167. */
  168. Stack.prototype.collision = function collision (a, b, margin) {
  169. return ((a.left - margin) < (b.left + b.width) &&
  170. (a.left + a.width + margin) > b.left &&
  171. (a.top - margin) < (b.top + b.height) &&
  172. (a.top + a.height + margin) > b.top);
  173. };