vis-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.gitmodules +3 -0
  4. data/.project +11 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +202 -0
  7. data/README.md +29 -0
  8. data/Rakefile +1 -0
  9. data/lib/vis/rails/engine.rb +6 -0
  10. data/lib/vis/rails/version.rb +5 -0
  11. data/lib/vis/rails.rb +7 -0
  12. data/vendor/assets/javascripts/vis.js +1 -0
  13. data/vendor/assets/stylesheets/vis.css +3 -0
  14. data/vendor/assets/vis/DataSet.js +936 -0
  15. data/vendor/assets/vis/DataView.js +281 -0
  16. data/vendor/assets/vis/EventBus.js +89 -0
  17. data/vendor/assets/vis/events.js +116 -0
  18. data/vendor/assets/vis/graph/ClusterMixin.js +1019 -0
  19. data/vendor/assets/vis/graph/Edge.js +620 -0
  20. data/vendor/assets/vis/graph/Graph.js +2111 -0
  21. data/vendor/assets/vis/graph/Groups.js +80 -0
  22. data/vendor/assets/vis/graph/Images.js +41 -0
  23. data/vendor/assets/vis/graph/NavigationMixin.js +245 -0
  24. data/vendor/assets/vis/graph/Node.js +978 -0
  25. data/vendor/assets/vis/graph/Popup.js +105 -0
  26. data/vendor/assets/vis/graph/SectorsMixin.js +547 -0
  27. data/vendor/assets/vis/graph/SelectionMixin.js +515 -0
  28. data/vendor/assets/vis/graph/dotparser.js +829 -0
  29. data/vendor/assets/vis/graph/img/downarrow.png +0 -0
  30. data/vendor/assets/vis/graph/img/leftarrow.png +0 -0
  31. data/vendor/assets/vis/graph/img/minus.png +0 -0
  32. data/vendor/assets/vis/graph/img/plus.png +0 -0
  33. data/vendor/assets/vis/graph/img/rightarrow.png +0 -0
  34. data/vendor/assets/vis/graph/img/uparrow.png +0 -0
  35. data/vendor/assets/vis/graph/img/zoomExtends.png +0 -0
  36. data/vendor/assets/vis/graph/shapes.js +225 -0
  37. data/vendor/assets/vis/module/exports.js +68 -0
  38. data/vendor/assets/vis/module/header.js +24 -0
  39. data/vendor/assets/vis/module/imports.js +32 -0
  40. data/vendor/assets/vis/shim.js +252 -0
  41. data/vendor/assets/vis/timeline/Controller.js +172 -0
  42. data/vendor/assets/vis/timeline/Range.js +553 -0
  43. data/vendor/assets/vis/timeline/Stack.js +192 -0
  44. data/vendor/assets/vis/timeline/TimeStep.js +449 -0
  45. data/vendor/assets/vis/timeline/Timeline.js +476 -0
  46. data/vendor/assets/vis/timeline/component/Component.js +148 -0
  47. data/vendor/assets/vis/timeline/component/ContentPanel.js +113 -0
  48. data/vendor/assets/vis/timeline/component/CurrentTime.js +101 -0
  49. data/vendor/assets/vis/timeline/component/CustomTime.js +255 -0
  50. data/vendor/assets/vis/timeline/component/Group.js +129 -0
  51. data/vendor/assets/vis/timeline/component/GroupSet.js +546 -0
  52. data/vendor/assets/vis/timeline/component/ItemSet.js +612 -0
  53. data/vendor/assets/vis/timeline/component/Panel.js +112 -0
  54. data/vendor/assets/vis/timeline/component/RootPanel.js +215 -0
  55. data/vendor/assets/vis/timeline/component/TimeAxis.js +522 -0
  56. data/vendor/assets/vis/timeline/component/css/currenttime.css +5 -0
  57. data/vendor/assets/vis/timeline/component/css/customtime.css +6 -0
  58. data/vendor/assets/vis/timeline/component/css/groupset.css +59 -0
  59. data/vendor/assets/vis/timeline/component/css/item.css +93 -0
  60. data/vendor/assets/vis/timeline/component/css/itemset.css +17 -0
  61. data/vendor/assets/vis/timeline/component/css/panel.css +14 -0
  62. data/vendor/assets/vis/timeline/component/css/timeaxis.css +41 -0
  63. data/vendor/assets/vis/timeline/component/css/timeline.css +2 -0
  64. data/vendor/assets/vis/timeline/component/item/Item.js +81 -0
  65. data/vendor/assets/vis/timeline/component/item/ItemBox.js +302 -0
  66. data/vendor/assets/vis/timeline/component/item/ItemPoint.js +237 -0
  67. data/vendor/assets/vis/timeline/component/item/ItemRange.js +251 -0
  68. data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +91 -0
  69. data/vendor/assets/vis/util.js +673 -0
  70. data/vis-rails.gemspec +47 -0
  71. metadata +142 -0
@@ -0,0 +1,612 @@
1
+ /**
2
+ * An ItemSet holds a set of items and ranges which can be displayed in a
3
+ * range. The width is determined by the parent of the ItemSet, and the height
4
+ * is determined by the size of the items.
5
+ * @param {Component} parent
6
+ * @param {Component[]} [depends] Components on which this components depends
7
+ * (except for the parent)
8
+ * @param {Object} [options] See ItemSet.setOptions for the available
9
+ * options.
10
+ * @constructor ItemSet
11
+ * @extends Panel
12
+ */
13
+ // TODO: improve performance by replacing all Array.forEach with a for loop
14
+ function ItemSet(parent, depends, options) {
15
+ this.id = util.randomUUID();
16
+ this.parent = parent;
17
+ this.depends = depends;
18
+
19
+ // one options object is shared by this itemset and all its items
20
+ this.options = options || {};
21
+ this.defaultOptions = {
22
+ type: 'box',
23
+ align: 'center',
24
+ orientation: 'bottom',
25
+ margin: {
26
+ axis: 20,
27
+ item: 10
28
+ },
29
+ padding: 5
30
+ };
31
+
32
+ this.dom = {};
33
+
34
+ var me = this;
35
+ this.itemsData = null; // DataSet
36
+ this.range = null; // Range or Object {start: number, end: number}
37
+
38
+ this.listeners = {
39
+ 'add': function (event, params, senderId) {
40
+ if (senderId != me.id) {
41
+ me._onAdd(params.items);
42
+ }
43
+ },
44
+ 'update': function (event, params, senderId) {
45
+ if (senderId != me.id) {
46
+ me._onUpdate(params.items);
47
+ }
48
+ },
49
+ 'remove': function (event, params, senderId) {
50
+ if (senderId != me.id) {
51
+ me._onRemove(params.items);
52
+ }
53
+ }
54
+ };
55
+
56
+ this.items = {}; // object with an Item for every data item
57
+ this.selection = []; // list with the ids of all selected nodes
58
+ this.queue = {}; // queue with id/actions: 'add', 'update', 'delete'
59
+ this.stack = new Stack(this, Object.create(this.options));
60
+ this.conversion = null;
61
+
62
+ // TODO: ItemSet should also attach event listeners for rangechange and rangechanged, like timeaxis
63
+ }
64
+
65
+ ItemSet.prototype = new Panel();
66
+
67
+ // available item types will be registered here
68
+ ItemSet.types = {
69
+ box: ItemBox,
70
+ range: ItemRange,
71
+ rangeoverflow: ItemRangeOverflow,
72
+ point: ItemPoint
73
+ };
74
+
75
+ /**
76
+ * Set options for the ItemSet. Existing options will be extended/overwritten.
77
+ * @param {Object} [options] The following options are available:
78
+ * {String | function} [className]
79
+ * class name for the itemset
80
+ * {String} [type]
81
+ * Default type for the items. Choose from 'box'
82
+ * (default), 'point', or 'range'. The default
83
+ * Style can be overwritten by individual items.
84
+ * {String} align
85
+ * Alignment for the items, only applicable for
86
+ * ItemBox. Choose 'center' (default), 'left', or
87
+ * 'right'.
88
+ * {String} orientation
89
+ * Orientation of the item set. Choose 'top' or
90
+ * 'bottom' (default).
91
+ * {Number} margin.axis
92
+ * Margin between the axis and the items in pixels.
93
+ * Default is 20.
94
+ * {Number} margin.item
95
+ * Margin between items in pixels. Default is 10.
96
+ * {Number} padding
97
+ * Padding of the contents of an item in pixels.
98
+ * Must correspond with the items css. Default is 5.
99
+ */
100
+ ItemSet.prototype.setOptions = Component.prototype.setOptions;
101
+
102
+ /**
103
+ * Set range (start and end).
104
+ * @param {Range | Object} range A Range or an object containing start and end.
105
+ */
106
+ ItemSet.prototype.setRange = function setRange(range) {
107
+ if (!(range instanceof Range) && (!range || !range.start || !range.end)) {
108
+ throw new TypeError('Range must be an instance of Range, ' +
109
+ 'or an object containing start and end.');
110
+ }
111
+ this.range = range;
112
+ };
113
+
114
+ /**
115
+ * Set selected items by their id. Replaces the current selection
116
+ * Unknown id's are silently ignored.
117
+ * @param {Array} [ids] An array with zero or more id's of the items to be
118
+ * selected. If ids is an empty array, all items will be
119
+ * unselected.
120
+ */
121
+ ItemSet.prototype.setSelection = function setSelection(ids) {
122
+ var i, ii, id, item, selection;
123
+
124
+ if (ids) {
125
+ if (!Array.isArray(ids)) {
126
+ throw new TypeError('Array expected');
127
+ }
128
+
129
+ // unselect currently selected items
130
+ for (i = 0, ii = this.selection.length; i < ii; i++) {
131
+ id = this.selection[i];
132
+ item = this.items[id];
133
+ if (item) item.unselect();
134
+ }
135
+
136
+ // select items
137
+ this.selection = [];
138
+ for (i = 0, ii = ids.length; i < ii; i++) {
139
+ id = ids[i];
140
+ item = this.items[id];
141
+ if (item) {
142
+ this.selection.push(id);
143
+ item.select();
144
+ }
145
+ }
146
+
147
+ // trigger a select event
148
+ selection = this.selection.concat([]);
149
+ events.trigger(this, 'select', {
150
+ ids: selection
151
+ });
152
+
153
+ if (this.controller) {
154
+ this.requestRepaint();
155
+ }
156
+ }
157
+ };
158
+
159
+ /**
160
+ * Get the selected items by their id
161
+ * @return {Array} ids The ids of the selected items
162
+ */
163
+ ItemSet.prototype.getSelection = function getSelection() {
164
+ return this.selection.concat([]);
165
+ };
166
+
167
+ /**
168
+ * Deselect a selected item
169
+ * @param {String | Number} id
170
+ * @private
171
+ */
172
+ ItemSet.prototype._deselect = function _deselect(id) {
173
+ var selection = this.selection;
174
+ for (var i = 0, ii = selection.length; i < ii; i++) {
175
+ if (selection[i] == id) { // non-strict comparison!
176
+ selection.splice(i, 1);
177
+ break;
178
+ }
179
+ }
180
+ };
181
+
182
+ /**
183
+ * Repaint the component
184
+ * @return {Boolean} changed
185
+ */
186
+ ItemSet.prototype.repaint = function repaint() {
187
+ var changed = 0,
188
+ update = util.updateProperty,
189
+ asSize = util.option.asSize,
190
+ options = this.options,
191
+ orientation = this.getOption('orientation'),
192
+ defaultOptions = this.defaultOptions,
193
+ frame = this.frame;
194
+
195
+ if (!frame) {
196
+ frame = document.createElement('div');
197
+ frame.className = 'itemset';
198
+
199
+ var className = options.className;
200
+ if (className) {
201
+ util.addClassName(frame, util.option.asString(className));
202
+ }
203
+
204
+ // create background panel
205
+ var background = document.createElement('div');
206
+ background.className = 'background';
207
+ frame.appendChild(background);
208
+ this.dom.background = background;
209
+
210
+ // create foreground panel
211
+ var foreground = document.createElement('div');
212
+ foreground.className = 'foreground';
213
+ frame.appendChild(foreground);
214
+ this.dom.foreground = foreground;
215
+
216
+ // create axis panel
217
+ var axis = document.createElement('div');
218
+ axis.className = 'itemset-axis';
219
+ //frame.appendChild(axis);
220
+ this.dom.axis = axis;
221
+
222
+ this.frame = frame;
223
+ changed += 1;
224
+ }
225
+
226
+ if (!this.parent) {
227
+ throw new Error('Cannot repaint itemset: no parent attached');
228
+ }
229
+ var parentContainer = this.parent.getContainer();
230
+ if (!parentContainer) {
231
+ throw new Error('Cannot repaint itemset: parent has no container element');
232
+ }
233
+ if (!frame.parentNode) {
234
+ parentContainer.appendChild(frame);
235
+ changed += 1;
236
+ }
237
+ if (!this.dom.axis.parentNode) {
238
+ parentContainer.appendChild(this.dom.axis);
239
+ changed += 1;
240
+ }
241
+
242
+ // reposition frame
243
+ changed += update(frame.style, 'left', asSize(options.left, '0px'));
244
+ changed += update(frame.style, 'top', asSize(options.top, '0px'));
245
+ changed += update(frame.style, 'width', asSize(options.width, '100%'));
246
+ changed += update(frame.style, 'height', asSize(options.height, this.height + 'px'));
247
+
248
+ // reposition axis
249
+ changed += update(this.dom.axis.style, 'left', asSize(options.left, '0px'));
250
+ changed += update(this.dom.axis.style, 'width', asSize(options.width, '100%'));
251
+ if (orientation == 'bottom') {
252
+ changed += update(this.dom.axis.style, 'top', (this.height + this.top) + 'px');
253
+ }
254
+ else { // orientation == 'top'
255
+ changed += update(this.dom.axis.style, 'top', this.top + 'px');
256
+ }
257
+
258
+ this._updateConversion();
259
+
260
+ var me = this,
261
+ queue = this.queue,
262
+ itemsData = this.itemsData,
263
+ items = this.items,
264
+ dataOptions = {
265
+ // TODO: cleanup
266
+ // fields: [(itemsData && itemsData.fieldId || 'id'), 'start', 'end', 'content', 'type', 'className']
267
+ };
268
+
269
+ // show/hide added/changed/removed items
270
+ for (var id in queue) {
271
+ if (queue.hasOwnProperty(id)) {
272
+ var entry = queue[id],
273
+ item = items[id],
274
+ action = entry.action;
275
+
276
+ //noinspection FallthroughInSwitchStatementJS
277
+ switch (action) {
278
+ case 'add':
279
+ case 'update':
280
+ var itemData = itemsData && itemsData.get(id, dataOptions);
281
+
282
+ if (itemData) {
283
+ var type = itemData.type ||
284
+ (itemData.start && itemData.end && 'range') ||
285
+ options.type ||
286
+ 'box';
287
+ var constructor = ItemSet.types[type];
288
+
289
+ // TODO: how to handle items with invalid data? hide them and give a warning? or throw an error?
290
+ if (item) {
291
+ // update item
292
+ if (!constructor || !(item instanceof constructor)) {
293
+ // item type has changed, hide and delete the item
294
+ changed += item.hide();
295
+ item = null;
296
+ }
297
+ else {
298
+ item.data = itemData; // TODO: create a method item.setData ?
299
+ changed++;
300
+ }
301
+ }
302
+
303
+ if (!item) {
304
+ // create item
305
+ if (constructor) {
306
+ item = new constructor(me, itemData, options, defaultOptions);
307
+ item.id = entry.id; // we take entry.id, as id itself is stringified
308
+ changed++;
309
+ }
310
+ else {
311
+ throw new TypeError('Unknown item type "' + type + '"');
312
+ }
313
+ }
314
+
315
+ // force a repaint (not only a reposition)
316
+ item.repaint();
317
+
318
+ items[id] = item;
319
+ }
320
+
321
+ // update queue
322
+ delete queue[id];
323
+ break;
324
+
325
+ case 'remove':
326
+ if (item) {
327
+ // remove the item from the set selected items
328
+ if (item.selected) {
329
+ me._deselect(id);
330
+ }
331
+
332
+ // remove DOM of the item
333
+ changed += item.hide();
334
+ }
335
+
336
+ // update lists
337
+ delete items[id];
338
+ delete queue[id];
339
+ break;
340
+
341
+ default:
342
+ console.log('Error: unknown action "' + action + '"');
343
+ }
344
+ }
345
+ }
346
+
347
+ // reposition all items. Show items only when in the visible area
348
+ util.forEach(this.items, function (item) {
349
+ if (item.visible) {
350
+ changed += item.show();
351
+ item.reposition();
352
+ }
353
+ else {
354
+ changed += item.hide();
355
+ }
356
+ });
357
+
358
+ return (changed > 0);
359
+ };
360
+
361
+ /**
362
+ * Get the foreground container element
363
+ * @return {HTMLElement} foreground
364
+ */
365
+ ItemSet.prototype.getForeground = function getForeground() {
366
+ return this.dom.foreground;
367
+ };
368
+
369
+ /**
370
+ * Get the background container element
371
+ * @return {HTMLElement} background
372
+ */
373
+ ItemSet.prototype.getBackground = function getBackground() {
374
+ return this.dom.background;
375
+ };
376
+
377
+ /**
378
+ * Get the axis container element
379
+ * @return {HTMLElement} axis
380
+ */
381
+ ItemSet.prototype.getAxis = function getAxis() {
382
+ return this.dom.axis;
383
+ };
384
+
385
+ /**
386
+ * Reflow the component
387
+ * @return {Boolean} resized
388
+ */
389
+ ItemSet.prototype.reflow = function reflow () {
390
+ var changed = 0,
391
+ options = this.options,
392
+ marginAxis = options.margin && options.margin.axis || this.defaultOptions.margin.axis,
393
+ marginItem = options.margin && options.margin.item || this.defaultOptions.margin.item,
394
+ update = util.updateProperty,
395
+ asNumber = util.option.asNumber,
396
+ asSize = util.option.asSize,
397
+ frame = this.frame;
398
+
399
+ if (frame) {
400
+ this._updateConversion();
401
+
402
+ util.forEach(this.items, function (item) {
403
+ changed += item.reflow();
404
+ });
405
+
406
+ // TODO: stack.update should be triggered via an event, in stack itself
407
+ // TODO: only update the stack when there are changed items
408
+ this.stack.update();
409
+
410
+ var maxHeight = asNumber(options.maxHeight);
411
+ var fixedHeight = (asSize(options.height) != null);
412
+ var height;
413
+ if (fixedHeight) {
414
+ height = frame.offsetHeight;
415
+ }
416
+ else {
417
+ // height is not specified, determine the height from the height and positioned items
418
+ var visibleItems = this.stack.ordered; // TODO: not so nice way to get the filtered items
419
+ if (visibleItems.length) {
420
+ var min = visibleItems[0].top;
421
+ var max = visibleItems[0].top + visibleItems[0].height;
422
+ util.forEach(visibleItems, function (item) {
423
+ min = Math.min(min, item.top);
424
+ max = Math.max(max, (item.top + item.height));
425
+ });
426
+ height = (max - min) + marginAxis + marginItem;
427
+ }
428
+ else {
429
+ height = marginAxis + marginItem;
430
+ }
431
+ }
432
+ if (maxHeight != null) {
433
+ height = Math.min(height, maxHeight);
434
+ }
435
+ changed += update(this, 'height', height);
436
+
437
+ // calculate height from items
438
+ changed += update(this, 'top', frame.offsetTop);
439
+ changed += update(this, 'left', frame.offsetLeft);
440
+ changed += update(this, 'width', frame.offsetWidth);
441
+ }
442
+ else {
443
+ changed += 1;
444
+ }
445
+
446
+ return (changed > 0);
447
+ };
448
+
449
+ /**
450
+ * Hide this component from the DOM
451
+ * @return {Boolean} changed
452
+ */
453
+ ItemSet.prototype.hide = function hide() {
454
+ var changed = false;
455
+
456
+ // remove the DOM
457
+ if (this.frame && this.frame.parentNode) {
458
+ this.frame.parentNode.removeChild(this.frame);
459
+ changed = true;
460
+ }
461
+ if (this.dom.axis && this.dom.axis.parentNode) {
462
+ this.dom.axis.parentNode.removeChild(this.dom.axis);
463
+ changed = true;
464
+ }
465
+
466
+ return changed;
467
+ };
468
+
469
+ /**
470
+ * Set items
471
+ * @param {vis.DataSet | null} items
472
+ */
473
+ ItemSet.prototype.setItems = function setItems(items) {
474
+ var me = this,
475
+ ids,
476
+ oldItemsData = this.itemsData;
477
+
478
+ // replace the dataset
479
+ if (!items) {
480
+ this.itemsData = null;
481
+ }
482
+ else if (items instanceof DataSet || items instanceof DataView) {
483
+ this.itemsData = items;
484
+ }
485
+ else {
486
+ throw new TypeError('Data must be an instance of DataSet');
487
+ }
488
+
489
+ if (oldItemsData) {
490
+ // unsubscribe from old dataset
491
+ util.forEach(this.listeners, function (callback, event) {
492
+ oldItemsData.unsubscribe(event, callback);
493
+ });
494
+
495
+ // remove all drawn items
496
+ ids = oldItemsData.getIds();
497
+ this._onRemove(ids);
498
+ }
499
+
500
+ if (this.itemsData) {
501
+ // subscribe to new dataset
502
+ var id = this.id;
503
+ util.forEach(this.listeners, function (callback, event) {
504
+ me.itemsData.subscribe(event, callback, id);
505
+ });
506
+
507
+ // draw all new items
508
+ ids = this.itemsData.getIds();
509
+ this._onAdd(ids);
510
+ }
511
+ };
512
+
513
+ /**
514
+ * Get the current items items
515
+ * @returns {vis.DataSet | null}
516
+ */
517
+ ItemSet.prototype.getItems = function getItems() {
518
+ return this.itemsData;
519
+ };
520
+
521
+ /**
522
+ * Handle updated items
523
+ * @param {Number[]} ids
524
+ * @private
525
+ */
526
+ ItemSet.prototype._onUpdate = function _onUpdate(ids) {
527
+ this._toQueue('update', ids);
528
+ };
529
+
530
+ /**
531
+ * Handle changed items
532
+ * @param {Number[]} ids
533
+ * @private
534
+ */
535
+ ItemSet.prototype._onAdd = function _onAdd(ids) {
536
+ this._toQueue('add', ids);
537
+ };
538
+
539
+ /**
540
+ * Handle removed items
541
+ * @param {Number[]} ids
542
+ * @private
543
+ */
544
+ ItemSet.prototype._onRemove = function _onRemove(ids) {
545
+ this._toQueue('remove', ids);
546
+ };
547
+
548
+ /**
549
+ * Put items in the queue to be added/updated/remove
550
+ * @param {String} action can be 'add', 'update', 'remove'
551
+ * @param {Number[]} ids
552
+ */
553
+ ItemSet.prototype._toQueue = function _toQueue(action, ids) {
554
+ var queue = this.queue;
555
+ ids.forEach(function (id) {
556
+ queue[id] = {
557
+ id: id,
558
+ action: action
559
+ };
560
+ });
561
+
562
+ if (this.controller) {
563
+ //this.requestReflow();
564
+ this.requestRepaint();
565
+ }
566
+ };
567
+
568
+ /**
569
+ * Calculate the scale and offset to convert a position on screen to the
570
+ * corresponding date and vice versa.
571
+ * After the method _updateConversion is executed once, the methods toTime
572
+ * and toScreen can be used.
573
+ * @private
574
+ */
575
+ ItemSet.prototype._updateConversion = function _updateConversion() {
576
+ var range = this.range;
577
+ if (!range) {
578
+ throw new Error('No range configured');
579
+ }
580
+
581
+ if (range.conversion) {
582
+ this.conversion = range.conversion(this.width);
583
+ }
584
+ else {
585
+ this.conversion = Range.conversion(range.start, range.end, this.width);
586
+ }
587
+ };
588
+
589
+ /**
590
+ * Convert a position on screen (pixels) to a datetime
591
+ * Before this method can be used, the method _updateConversion must be
592
+ * executed once.
593
+ * @param {int} x Position on the screen in pixels
594
+ * @return {Date} time The datetime the corresponds with given position x
595
+ */
596
+ ItemSet.prototype.toTime = function toTime(x) {
597
+ var conversion = this.conversion;
598
+ return new Date(x / conversion.scale + conversion.offset);
599
+ };
600
+
601
+ /**
602
+ * Convert a datetime (Date object) into a position on the screen
603
+ * Before this method can be used, the method _updateConversion must be
604
+ * executed once.
605
+ * @param {Date} time A date
606
+ * @return {int} x The position on the screen in pixels which corresponds
607
+ * with the given date.
608
+ */
609
+ ItemSet.prototype.toScreen = function toScreen(time) {
610
+ var conversion = this.conversion;
611
+ return (time.valueOf() - conversion.offset) * conversion.scale;
612
+ };
@@ -0,0 +1,112 @@
1
+ /**
2
+ * A panel can contain components
3
+ * @param {Component} [parent]
4
+ * @param {Component[]} [depends] Components on which this components depends
5
+ * (except for the parent)
6
+ * @param {Object} [options] Available parameters:
7
+ * {String | Number | function} [left]
8
+ * {String | Number | function} [top]
9
+ * {String | Number | function} [width]
10
+ * {String | Number | function} [height]
11
+ * {String | function} [className]
12
+ * @constructor Panel
13
+ * @extends Component
14
+ */
15
+ function Panel(parent, depends, options) {
16
+ this.id = util.randomUUID();
17
+ this.parent = parent;
18
+ this.depends = depends;
19
+
20
+ this.options = options || {};
21
+ }
22
+
23
+ Panel.prototype = new Component();
24
+
25
+ /**
26
+ * Set options. Will extend the current options.
27
+ * @param {Object} [options] Available parameters:
28
+ * {String | function} [className]
29
+ * {String | Number | function} [left]
30
+ * {String | Number | function} [top]
31
+ * {String | Number | function} [width]
32
+ * {String | Number | function} [height]
33
+ */
34
+ Panel.prototype.setOptions = Component.prototype.setOptions;
35
+
36
+ /**
37
+ * Get the container element of the panel, which can be used by a child to
38
+ * add its own widgets.
39
+ * @returns {HTMLElement} container
40
+ */
41
+ Panel.prototype.getContainer = function () {
42
+ return this.frame;
43
+ };
44
+
45
+ /**
46
+ * Repaint the component
47
+ * @return {Boolean} changed
48
+ */
49
+ Panel.prototype.repaint = function () {
50
+ var changed = 0,
51
+ update = util.updateProperty,
52
+ asSize = util.option.asSize,
53
+ options = this.options,
54
+ frame = this.frame;
55
+ if (!frame) {
56
+ frame = document.createElement('div');
57
+ frame.className = 'panel';
58
+
59
+ var className = options.className;
60
+ if (className) {
61
+ if (typeof className == 'function') {
62
+ util.addClassName(frame, String(className()));
63
+ }
64
+ else {
65
+ util.addClassName(frame, String(className));
66
+ }
67
+ }
68
+
69
+ this.frame = frame;
70
+ changed += 1;
71
+ }
72
+ if (!frame.parentNode) {
73
+ if (!this.parent) {
74
+ throw new Error('Cannot repaint panel: no parent attached');
75
+ }
76
+ var parentContainer = this.parent.getContainer();
77
+ if (!parentContainer) {
78
+ throw new Error('Cannot repaint panel: parent has no container element');
79
+ }
80
+ parentContainer.appendChild(frame);
81
+ changed += 1;
82
+ }
83
+
84
+ changed += update(frame.style, 'top', asSize(options.top, '0px'));
85
+ changed += update(frame.style, 'left', asSize(options.left, '0px'));
86
+ changed += update(frame.style, 'width', asSize(options.width, '100%'));
87
+ changed += update(frame.style, 'height', asSize(options.height, '100%'));
88
+
89
+ return (changed > 0);
90
+ };
91
+
92
+ /**
93
+ * Reflow the component
94
+ * @return {Boolean} resized
95
+ */
96
+ Panel.prototype.reflow = function () {
97
+ var changed = 0,
98
+ update = util.updateProperty,
99
+ frame = this.frame;
100
+
101
+ if (frame) {
102
+ changed += update(this, 'top', frame.offsetTop);
103
+ changed += update(this, 'left', frame.offsetLeft);
104
+ changed += update(this, 'width', frame.offsetWidth);
105
+ changed += update(this, 'height', frame.offsetHeight);
106
+ }
107
+ else {
108
+ changed += 1;
109
+ }
110
+
111
+ return (changed > 0);
112
+ };