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.

208 lines
6.8 KiB

  1. var Hammer = require('../../../module/hammer');
  2. var Item = require('./Item');
  3. var BackgroundGroup = require('../BackgroundGroup');
  4. var RangeItem = require('./RangeItem');
  5. /**
  6. * @constructor BackgroundItem
  7. * @extends Item
  8. * @param {Object} data Object containing parameters start, end
  9. * content, className.
  10. * @param {{toScreen: function, toTime: function}} conversion
  11. * Conversion functions from time to screen and vice versa
  12. * @param {Object} [options] Configuration options
  13. * // TODO: describe options
  14. */
  15. // TODO: implement support for the BackgroundItem just having a start, then being displayed as a sort of an annotation
  16. function BackgroundItem (data, conversion, options) {
  17. this.props = {
  18. content: {
  19. width: 0
  20. }
  21. };
  22. this.overflow = false; // if contents can overflow (css styling), this flag is set to true
  23. // validate data
  24. if (data) {
  25. if (data.start == undefined) {
  26. throw new Error('Property "start" missing in item ' + data.id);
  27. }
  28. if (data.end == undefined) {
  29. throw new Error('Property "end" missing in item ' + data.id);
  30. }
  31. }
  32. Item.call(this, data, conversion, options);
  33. this.emptyContent = false;
  34. }
  35. BackgroundItem.prototype = new Item (null, null, null);
  36. BackgroundItem.prototype.baseClassName = 'item background';
  37. BackgroundItem.prototype.stack = false;
  38. /**
  39. * Check whether this item is visible inside given range
  40. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  41. * @returns {boolean} True if visible
  42. */
  43. BackgroundItem.prototype.isVisible = function(range) {
  44. // determine visibility
  45. return (this.data.start < range.end) && (this.data.end > range.start);
  46. };
  47. /**
  48. * Repaint the item
  49. */
  50. BackgroundItem.prototype.redraw = function() {
  51. var dom = this.dom;
  52. if (!dom) {
  53. // create DOM
  54. this.dom = {};
  55. dom = this.dom;
  56. // background box
  57. dom.box = document.createElement('div');
  58. // className is updated in redraw()
  59. // contents box
  60. dom.content = document.createElement('div');
  61. dom.content.className = 'content';
  62. dom.box.appendChild(dom.content);
  63. // Note: we do NOT attach this item as attribute to the DOM,
  64. // such that background items cannot be selected
  65. //dom.box['timeline-item'] = this;
  66. this.dirty = true;
  67. }
  68. // append DOM to parent DOM
  69. if (!this.parent) {
  70. throw new Error('Cannot redraw item: no parent attached');
  71. }
  72. if (!dom.box.parentNode) {
  73. var background = this.parent.dom.background;
  74. if (!background) {
  75. throw new Error('Cannot redraw item: parent has no background container element');
  76. }
  77. background.appendChild(dom.box);
  78. }
  79. this.displayed = true;
  80. // Update DOM when item is marked dirty. An item is marked dirty when:
  81. // - the item is not yet rendered
  82. // - the item's data is changed
  83. // - the item is selected/deselected
  84. if (this.dirty) {
  85. this._updateContents(this.dom.content);
  86. this._updateTitle(this.dom.content);
  87. this._updateDataAttributes(this.dom.content);
  88. this._updateStyle(this.dom.box);
  89. // update class
  90. var className = (this.data.className ? (' ' + this.data.className) : '') +
  91. (this.selected ? ' selected' : '');
  92. dom.box.className = this.baseClassName + className;
  93. // determine from css whether this box has overflow
  94. this.overflow = window.getComputedStyle(dom.content).overflow !== 'hidden';
  95. // recalculate size
  96. this.props.content.width = this.dom.content.offsetWidth;
  97. this.height = 0; // set height zero, so this item will be ignored when stacking items
  98. this.dirty = false;
  99. }
  100. };
  101. /**
  102. * Show the item in the DOM (when not already visible). The items DOM will
  103. * be created when needed.
  104. */
  105. BackgroundItem.prototype.show = RangeItem.prototype.show;
  106. /**
  107. * Hide the item from the DOM (when visible)
  108. * @return {Boolean} changed
  109. */
  110. BackgroundItem.prototype.hide = RangeItem.prototype.hide;
  111. /**
  112. * Reposition the item horizontally
  113. * @Override
  114. */
  115. BackgroundItem.prototype.repositionX = RangeItem.prototype.repositionX;
  116. /**
  117. * Reposition the item vertically
  118. * @Override
  119. */
  120. BackgroundItem.prototype.repositionY = function(margin) {
  121. var onTop = this.options.orientation === 'top';
  122. this.dom.content.style.top = onTop ? '' : '0';
  123. this.dom.content.style.bottom = onTop ? '0' : '';
  124. var height;
  125. // special positioning for subgroups
  126. if (this.data.subgroup !== undefined) {
  127. var itemSubgroup = this.data.subgroup;
  128. var subgroups = this.parent.subgroups;
  129. var subgroupIndex = subgroups[itemSubgroup].index;
  130. // if the orientation is top, we need to take the difference in height into account.
  131. if (onTop == true) {
  132. // the first subgroup will have to account for the distance from the top to the first item.
  133. height = this.parent.subgroups[itemSubgroup].height + margin.item.vertical;
  134. height += subgroupIndex == 0 ? margin.axis - 0.5*margin.item.vertical : 0;
  135. var newTop = this.parent.top;
  136. for (var subgroup in subgroups) {
  137. if (subgroups.hasOwnProperty(subgroup)) {
  138. if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroupIndex) {
  139. newTop += subgroups[subgroup].height + margin.item.vertical;
  140. }
  141. }
  142. }
  143. // the others will have to be offset downwards with this same distance.
  144. newTop += subgroupIndex != 0 ? margin.axis - 0.5 * margin.item.vertical : 0;
  145. this.dom.box.style.top = newTop + 'px';
  146. this.dom.box.style.bottom = '';
  147. }
  148. // and when the orientation is bottom:
  149. else {
  150. var newTop = this.parent.top;
  151. for (var subgroup in subgroups) {
  152. if (subgroups.hasOwnProperty(subgroup)) {
  153. if (subgroups[subgroup].visible == true && subgroups[subgroup].index > subgroupIndex) {
  154. newTop += subgroups[subgroup].height + margin.item.vertical;
  155. }
  156. }
  157. }
  158. height = this.parent.subgroups[itemSubgroup].height + margin.item.vertical;
  159. this.dom.box.style.top = newTop + 'px';
  160. this.dom.box.style.bottom = '';
  161. }
  162. }
  163. // and in the case of no subgroups:
  164. else {
  165. // we want backgrounds with groups to only show in groups.
  166. if (this.parent instanceof BackgroundGroup) {
  167. // if the item is not in a group:
  168. height = Math.max(this.parent.height,
  169. this.parent.itemSet.body.domProps.center.height,
  170. this.parent.itemSet.body.domProps.centerContainer.height);
  171. this.dom.box.style.top = onTop ? '0' : '';
  172. this.dom.box.style.bottom = onTop ? '' : '0';
  173. }
  174. else {
  175. height = this.parent.height;
  176. // same alignment for items when orientation is top or bottom
  177. this.dom.box.style.top = this.parent.top + 'px';
  178. this.dom.box.style.bottom = '';
  179. }
  180. }
  181. this.dom.box.style.height = height + 'px';
  182. };
  183. module.exports = BackgroundItem;