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.

370 lines
11 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. var moment = require('../module/moment');
  2. var util = require('../util');
  3. var DataSet = require('../DataSet');
  4. var DataView = require('../DataView');
  5. var Range = require('./Range');
  6. var Core = require('./Core');
  7. var TimeAxis = require('./component/TimeAxis');
  8. var CurrentTime = require('./component/CurrentTime');
  9. var CustomTime = require('./component/CustomTime');
  10. var LineGraph = require('./component/LineGraph');
  11. var printStyle = require('../shared/Validator').printStyle;
  12. var allOptions = require('./optionsGraph2d').allOptions;
  13. var configureOptions = require('./optionsGraph2d').configureOptions;
  14. var Configurator = require('../shared/Configurator').default;
  15. var Validator = require('../shared/Validator').default;
  16. /**
  17. * Create a timeline visualization
  18. * @param {HTMLElement} container
  19. * @param {vis.DataSet | Array} [items]
  20. * @param {vis.DataSet | Array | vis.DataView | Object} [groups]
  21. * @param {Object} [options] See Graph2d.setOptions for the available options.
  22. * @constructor Graph2d
  23. * @extends Core
  24. */
  25. function Graph2d (container, items, groups, options) {
  26. // if the third element is options, the forth is groups (optionally);
  27. if (!(Array.isArray(groups) || groups instanceof DataSet || groups instanceof DataView) && groups instanceof Object) {
  28. var forthArgument = options;
  29. options = groups;
  30. groups = forthArgument;
  31. }
  32. // TODO: REMOVE THIS in the next MAJOR release
  33. // see https://github.com/almende/vis/issues/2511
  34. if (options && options.throttleRedraw) {
  35. console.warn("Graph2d option \"throttleRedraw\" is DEPRICATED and no longer supported. It will be removed in the next MAJOR release.");
  36. }
  37. var me = this;
  38. this.defaultOptions = {
  39. start: null,
  40. end: null,
  41. autoResize: true,
  42. orientation: {
  43. axis: 'bottom', // axis orientation: 'bottom', 'top', or 'both'
  44. item: 'bottom' // not relevant for Graph2d
  45. },
  46. moment: moment,
  47. width: null,
  48. height: null,
  49. maxHeight: null,
  50. minHeight: null
  51. };
  52. this.options = util.deepExtend({}, this.defaultOptions);
  53. // Create the DOM, props, and emitter
  54. this._create(container);
  55. // all components listed here will be repainted automatically
  56. this.components = [];
  57. this.body = {
  58. dom: this.dom,
  59. domProps: this.props,
  60. emitter: {
  61. on: this.on.bind(this),
  62. off: this.off.bind(this),
  63. emit: this.emit.bind(this)
  64. },
  65. hiddenDates: [],
  66. util: {
  67. toScreen: me._toScreen.bind(me),
  68. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  69. toTime: me._toTime.bind(me),
  70. toGlobalTime : me._toGlobalTime.bind(me)
  71. }
  72. };
  73. // range
  74. this.range = new Range(this.body);
  75. this.components.push(this.range);
  76. this.body.range = this.range;
  77. // time axis
  78. this.timeAxis = new TimeAxis(this.body);
  79. this.components.push(this.timeAxis);
  80. //this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
  81. // current time bar
  82. this.currentTime = new CurrentTime(this.body);
  83. this.components.push(this.currentTime);
  84. // item set
  85. this.linegraph = new LineGraph(this.body);
  86. this.components.push(this.linegraph);
  87. this.itemsData = null; // DataSet
  88. this.groupsData = null; // DataSet
  89. this.on('tap', function (event) {
  90. me.emit('click', me.getEventProperties(event))
  91. });
  92. this.on('doubletap', function (event) {
  93. me.emit('doubleClick', me.getEventProperties(event))
  94. });
  95. this.dom.root.oncontextmenu = function (event) {
  96. me.emit('contextmenu', me.getEventProperties(event))
  97. };
  98. //Single time autoscale/fit
  99. this.initialFitDone = false;
  100. this.on('changed', function (){
  101. if (me.itemsData == null) return;
  102. if (!me.initialFitDone && !me.options.rollingMode) {
  103. me.initialFitDone = true;
  104. if (me.options.start != undefined || me.options.end != undefined) {
  105. if (me.options.start == undefined || me.options.end == undefined) {
  106. var range = me.getItemRange();
  107. }
  108. var start = me.options.start != undefined ? me.options.start : range.min;
  109. var end = me.options.end != undefined ? me.options.end : range.max;
  110. me.setWindow(start, end, {animation: false});
  111. } else {
  112. me.fit({animation: false});
  113. }
  114. }
  115. if (!me.initialDrawDone && (me.initialRangeChangeDone || (!me.options.start && !me.options.end)
  116. || me.options.rollingMode)) {
  117. me.initialDrawDone = true;
  118. me.itemSet.initialDrawDone = true;
  119. me.dom.root.style.visibility = 'visible';
  120. me.dom.loadingScreen.parentNode.removeChild(me.dom.loadingScreen);
  121. if (me.options.onInitialDrawComplete) {
  122. setTimeout(() => {
  123. return me.options.onInitialDrawComplete();
  124. }, 0)
  125. }
  126. }
  127. });
  128. // apply options
  129. if (options) {
  130. this.setOptions(options);
  131. }
  132. // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
  133. if (groups) {
  134. this.setGroups(groups);
  135. }
  136. // create itemset
  137. if (items) {
  138. this.setItems(items);
  139. }
  140. // draw for the first time
  141. this._redraw();
  142. }
  143. // Extend the functionality from Core
  144. Graph2d.prototype = new Core();
  145. Graph2d.prototype.setOptions = function (options) {
  146. // validate options
  147. let errorFound = Validator.validate(options, allOptions);
  148. if (errorFound === true) {
  149. console.log('%cErrors have been found in the supplied options object.', printStyle);
  150. }
  151. Core.prototype.setOptions.call(this, options);
  152. };
  153. /**
  154. * Set items
  155. * @param {vis.DataSet | Array | null} items
  156. */
  157. Graph2d.prototype.setItems = function(items) {
  158. var initialLoad = (this.itemsData == null);
  159. // convert to type DataSet when needed
  160. var newDataSet;
  161. if (!items) {
  162. newDataSet = null;
  163. }
  164. else if (items instanceof DataSet || items instanceof DataView) {
  165. newDataSet = items;
  166. }
  167. else {
  168. // turn an array into a dataset
  169. newDataSet = new DataSet(items, {
  170. type: {
  171. start: 'Date',
  172. end: 'Date'
  173. }
  174. });
  175. }
  176. // set items
  177. this.itemsData = newDataSet;
  178. this.linegraph && this.linegraph.setItems(newDataSet);
  179. if (initialLoad) {
  180. if (this.options.start != undefined || this.options.end != undefined) {
  181. var start = this.options.start != undefined ? this.options.start : null;
  182. var end = this.options.end != undefined ? this.options.end : null;
  183. this.setWindow(start, end, {animation: false});
  184. }
  185. else {
  186. this.fit({animation: false});
  187. }
  188. }
  189. };
  190. /**
  191. * Set groups
  192. * @param {vis.DataSet | Array} groups
  193. */
  194. Graph2d.prototype.setGroups = function(groups) {
  195. // convert to type DataSet when needed
  196. var newDataSet;
  197. if (!groups) {
  198. newDataSet = null;
  199. }
  200. else if (groups instanceof DataSet || groups instanceof DataView) {
  201. newDataSet = groups;
  202. }
  203. else {
  204. // turn an array into a dataset
  205. newDataSet = new DataSet(groups);
  206. }
  207. this.groupsData = newDataSet;
  208. this.linegraph.setGroups(newDataSet);
  209. };
  210. /**
  211. * Returns an object containing an SVG element with the icon of the group (size determined by iconWidth and iconHeight), the label of the group (content) and the yAxisOrientation of the group (left or right).
  212. * @param {vis.GraphGroup.id} groupId
  213. * @param {number} width
  214. * @param {number} height
  215. * @returns {{icon: SVGElement, label: string, orientation: string}|string}
  216. */
  217. Graph2d.prototype.getLegend = function(groupId, width, height) {
  218. if (width === undefined) {width = 15;}
  219. if (height === undefined) {height = 15;}
  220. if (this.linegraph.groups[groupId] !== undefined) {
  221. return this.linegraph.groups[groupId].getLegend(width,height);
  222. }
  223. else {
  224. return "cannot find group:'" + groupId + "'";
  225. }
  226. };
  227. /**
  228. * This checks if the visible option of the supplied group (by ID) is true or false.
  229. * @param {vis.GraphGroup.id} groupId
  230. * @returns {boolean}
  231. */
  232. Graph2d.prototype.isGroupVisible = function(groupId) {
  233. if (this.linegraph.groups[groupId] !== undefined) {
  234. return (this.linegraph.groups[groupId].visible && (this.linegraph.options.groups.visibility[groupId] === undefined || this.linegraph.options.groups.visibility[groupId] == true));
  235. }
  236. else {
  237. return false;
  238. }
  239. };
  240. /**
  241. * Get the data range of the item set.
  242. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  243. * When no minimum is found, min==null
  244. * When no maximum is found, max==null
  245. */
  246. Graph2d.prototype.getDataRange = function() {
  247. var min = null;
  248. var max = null;
  249. // calculate min from start filed
  250. for (var groupId in this.linegraph.groups) {
  251. if (this.linegraph.groups.hasOwnProperty(groupId)) {
  252. if (this.linegraph.groups[groupId].visible == true) {
  253. for (var i = 0; i < this.linegraph.groups[groupId].itemsData.length; i++) {
  254. var item = this.linegraph.groups[groupId].itemsData[i];
  255. var value = util.convert(item.x, 'Date').valueOf();
  256. min = min == null ? value : min > value ? value : min;
  257. max = max == null ? value : max < value ? value : max;
  258. }
  259. }
  260. }
  261. }
  262. return {
  263. min: (min != null) ? new Date(min) : null,
  264. max: (max != null) ? new Date(max) : null
  265. };
  266. };
  267. /**
  268. * Generate Timeline related information from an event
  269. * @param {Event} event
  270. * @return {Object} An object with related information, like on which area
  271. * The event happened, whether clicked on an item, etc.
  272. */
  273. Graph2d.prototype.getEventProperties = function (event) {
  274. var clientX = event.center ? event.center.x : event.clientX;
  275. var clientY = event.center ? event.center.y : event.clientY;
  276. var x = clientX - util.getAbsoluteLeft(this.dom.centerContainer);
  277. var y = clientY - util.getAbsoluteTop(this.dom.centerContainer);
  278. var time = this._toTime(x);
  279. var customTime = CustomTime.customTimeFromTarget(event);
  280. var element = util.getTarget(event);
  281. var what = null;
  282. if (util.hasParent(element, this.timeAxis.dom.foreground)) {what = 'axis';}
  283. else if (this.timeAxis2 && util.hasParent(element, this.timeAxis2.dom.foreground)) {what = 'axis';}
  284. else if (util.hasParent(element, this.linegraph.yAxisLeft.dom.frame)) {what = 'data-axis';}
  285. else if (util.hasParent(element, this.linegraph.yAxisRight.dom.frame)) {what = 'data-axis';}
  286. else if (util.hasParent(element, this.linegraph.legendLeft.dom.frame)) {what = 'legend';}
  287. else if (util.hasParent(element, this.linegraph.legendRight.dom.frame)) {what = 'legend';}
  288. else if (customTime != null) {what = 'custom-time';}
  289. else if (util.hasParent(element, this.currentTime.bar)) {what = 'current-time';}
  290. else if (util.hasParent(element, this.dom.center)) {what = 'background';}
  291. var value = [];
  292. var yAxisLeft = this.linegraph.yAxisLeft;
  293. var yAxisRight = this.linegraph.yAxisRight;
  294. if (!yAxisLeft.hidden && this.itemsData.length > 0) {
  295. value.push(yAxisLeft.screenToValue(y));
  296. }
  297. if (!yAxisRight.hidden && this.itemsData.length > 0) {
  298. value.push(yAxisRight.screenToValue(y));
  299. }
  300. return {
  301. event: event,
  302. what: what,
  303. pageX: event.srcEvent ? event.srcEvent.pageX : event.pageX,
  304. pageY: event.srcEvent ? event.srcEvent.pageY : event.pageY,
  305. x: x,
  306. y: y,
  307. time: time,
  308. value: value
  309. }
  310. };
  311. /**
  312. * Load a configurator
  313. * @return {Object}
  314. * @private
  315. */
  316. Graph2d.prototype._createConfigurator = function () {
  317. return new Configurator(this, this.dom.container, configureOptions);
  318. };
  319. module.exports = Graph2d;