UNPKG

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