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.

308 lines
9.0 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. var Emitter = require('emitter-component');
  2. var Hammer = require('../module/hammer');
  3. var util = require('../util');
  4. var DataSet = require('../DataSet');
  5. var DataView = require('../DataView');
  6. var Range = require('./Range');
  7. var Core = require('./Core');
  8. var TimeAxis = require('./component/TimeAxis');
  9. var CurrentTime = require('./component/CurrentTime');
  10. var CustomTime = require('./component/CustomTime');
  11. var ItemSet = require('./component/ItemSet');
  12. /**
  13. * Create a timeline visualization
  14. * @param {HTMLElement} container
  15. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  16. * @param {Object} [options] See Timeline.setOptions for the available options.
  17. * @constructor
  18. * @extends Core
  19. */
  20. function Timeline (container, items, groups, options) {
  21. if (!(this instanceof Timeline)) {
  22. throw new SyntaxError('Constructor must be called with the new operator');
  23. }
  24. // if the third element is options, the forth is groups (optionally);
  25. if (!(groups instanceof Array || groups instanceof DataSet) && groups instanceof Object) {
  26. var forthArgument = options;
  27. options = groups;
  28. groups = forthArgument;
  29. }
  30. var me = this;
  31. this.defaultOptions = {
  32. start: null,
  33. end: null,
  34. autoResize: true,
  35. orientation: 'bottom',
  36. width: null,
  37. height: null,
  38. maxHeight: null,
  39. minHeight: null
  40. };
  41. this.options = util.deepExtend({}, this.defaultOptions);
  42. // Create the DOM, props, and emitter
  43. this._create(container);
  44. // all components listed here will be repainted automatically
  45. this.components = [];
  46. this.body = {
  47. dom: this.dom,
  48. domProps: this.props,
  49. emitter: {
  50. on: this.on.bind(this),
  51. off: this.off.bind(this),
  52. emit: this.emit.bind(this)
  53. },
  54. util: {
  55. snap: null, // will be specified after TimeAxis is created
  56. toScreen: me._toScreen.bind(me),
  57. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  58. toTime: me._toTime.bind(me),
  59. toGlobalTime : me._toGlobalTime.bind(me)
  60. }
  61. };
  62. // range
  63. this.range = new Range(this.body);
  64. this.components.push(this.range);
  65. this.body.range = this.range;
  66. // time axis
  67. this.timeAxis = new TimeAxis(this.body);
  68. this.components.push(this.timeAxis);
  69. this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
  70. // current time bar
  71. this.currentTime = new CurrentTime(this.body);
  72. this.components.push(this.currentTime);
  73. // custom time bar
  74. // Note: time bar will be attached in this.setOptions when selected
  75. this.customTime = new CustomTime(this.body);
  76. this.components.push(this.customTime);
  77. // item set
  78. this.itemSet = new ItemSet(this.body);
  79. this.components.push(this.itemSet);
  80. this.itemsData = null; // DataSet
  81. this.groupsData = null; // DataSet
  82. // apply options
  83. if (options) {
  84. this.setOptions(options);
  85. }
  86. // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
  87. if (groups) {
  88. this.setGroups(groups);
  89. }
  90. // create itemset
  91. if (items) {
  92. this.setItems(items);
  93. }
  94. else {
  95. this.redraw();
  96. }
  97. }
  98. // Extend the functionality from Core
  99. Timeline.prototype = new Core();
  100. /**
  101. * Set items
  102. * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
  103. */
  104. Timeline.prototype.setItems = function(items) {
  105. var initialLoad = (this.itemsData == null);
  106. // convert to type DataSet when needed
  107. var newDataSet;
  108. if (!items) {
  109. newDataSet = null;
  110. }
  111. else if (items instanceof DataSet || items instanceof DataView) {
  112. newDataSet = items;
  113. }
  114. else {
  115. // turn an array into a dataset
  116. newDataSet = new DataSet(items, {
  117. type: {
  118. start: 'Date',
  119. end: 'Date'
  120. }
  121. });
  122. }
  123. // set items
  124. this.itemsData = newDataSet;
  125. this.itemSet && this.itemSet.setItems(newDataSet);
  126. if (initialLoad) {
  127. if (this.options.start != undefined || this.options.end != undefined) {
  128. var start = this.options.start != undefined ? this.options.start : null;
  129. var end = this.options.end != undefined ? this.options.end : null;
  130. this.setWindow(start, end, {animate: false});
  131. }
  132. else {
  133. this.fit({animate: false});
  134. }
  135. }
  136. };
  137. /**
  138. * Set groups
  139. * @param {vis.DataSet | Array | google.visualization.DataTable} groups
  140. */
  141. Timeline.prototype.setGroups = function(groups) {
  142. // convert to type DataSet when needed
  143. var newDataSet;
  144. if (!groups) {
  145. newDataSet = null;
  146. }
  147. else if (groups instanceof DataSet || groups instanceof DataView) {
  148. newDataSet = groups;
  149. }
  150. else {
  151. // turn an array into a dataset
  152. newDataSet = new DataSet(groups);
  153. }
  154. this.groupsData = newDataSet;
  155. this.itemSet.setGroups(newDataSet);
  156. };
  157. /**
  158. * Set selected items by their id. Replaces the current selection
  159. * Unknown id's are silently ignored.
  160. * @param {string[] | string} [ids] An array with zero or more id's of the items to be
  161. * selected. If ids is an empty array, all items will be
  162. * unselected.
  163. * @param {Object} [options] Available options:
  164. * `focus: boolean`
  165. * If true, focus will be set to the selected item(s)
  166. * `animate: boolean | number`
  167. * If true (default), the range is animated
  168. * smoothly to the new window.
  169. * If a number, the number is taken as duration
  170. * for the animation. Default duration is 500 ms.
  171. * Only applicable when option focus is true.
  172. */
  173. Timeline.prototype.setSelection = function(ids, options) {
  174. this.itemSet && this.itemSet.setSelection(ids);
  175. if (options && options.focus) {
  176. this.focus(ids, options);
  177. }
  178. };
  179. /**
  180. * Get the selected items by their id
  181. * @return {Array} ids The ids of the selected items
  182. */
  183. Timeline.prototype.getSelection = function() {
  184. return this.itemSet && this.itemSet.getSelection() || [];
  185. };
  186. /**
  187. * Adjust the visible window such that the selected item (or multiple items)
  188. * are centered on screen.
  189. * @param {String | String[]} id An item id or array with item ids
  190. * @param {Object} [options] Available options:
  191. * `animate: boolean | number`
  192. * If true (default), the range is animated
  193. * smoothly to the new window.
  194. * If a number, the number is taken as duration
  195. * for the animation. Default duration is 500 ms.
  196. * Only applicable when option focus is true
  197. */
  198. Timeline.prototype.focus = function(id, options) {
  199. if (!this.itemsData || id == undefined) return;
  200. var ids = Array.isArray(id) ? id : [id];
  201. // get the specified item(s)
  202. var itemsData = this.itemsData.getDataSet().get(ids, {
  203. type: {
  204. start: 'Date',
  205. end: 'Date'
  206. }
  207. });
  208. // calculate minimum start and maximum end of specified items
  209. var start = null;
  210. var end = null;
  211. itemsData.forEach(function (itemData) {
  212. var s = itemData.start.valueOf();
  213. var e = 'end' in itemData ? itemData.end.valueOf() : itemData.start.valueOf();
  214. if (start === null || s < start) {
  215. start = s;
  216. }
  217. if (end === null || e > end) {
  218. end = e;
  219. }
  220. });
  221. if (start !== null && end !== null) {
  222. // calculate the new middle and interval for the window
  223. var middle = (start + end) / 2;
  224. var interval = Math.max((this.range.end - this.range.start), (end - start) * 1.1);
  225. var animate = (options && options.animate !== undefined) ? options.animate : true;
  226. this.range.setRange(middle - interval / 2, middle + interval / 2, animate);
  227. }
  228. };
  229. /**
  230. * Get the data range of the item set.
  231. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  232. * When no minimum is found, min==null
  233. * When no maximum is found, max==null
  234. */
  235. Timeline.prototype.getItemRange = function() {
  236. // calculate min from start filed
  237. var dataset = this.itemsData.getDataSet(),
  238. min = null,
  239. max = null;
  240. if (dataset) {
  241. // calculate the minimum value of the field 'start'
  242. var minItem = dataset.min('start');
  243. min = minItem ? util.convert(minItem.start, 'Date').valueOf() : null;
  244. // Note: we convert first to Date and then to number because else
  245. // a conversion from ISODate to Number will fail
  246. // calculate maximum value of fields 'start' and 'end'
  247. var maxStartItem = dataset.max('start');
  248. if (maxStartItem) {
  249. max = util.convert(maxStartItem.start, 'Date').valueOf();
  250. }
  251. var maxEndItem = dataset.max('end');
  252. if (maxEndItem) {
  253. if (max == null) {
  254. max = util.convert(maxEndItem.end, 'Date').valueOf();
  255. }
  256. else {
  257. max = Math.max(max, util.convert(maxEndItem.end, 'Date').valueOf());
  258. }
  259. }
  260. }
  261. return {
  262. min: (min != null) ? new Date(min) : null,
  263. max: (max != null) ? new Date(max) : null
  264. };
  265. };
  266. module.exports = Timeline;