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.

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