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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.gitmodules +3 -0
- data/.project +11 -0
- data/Gemfile +4 -0
- data/LICENSE +202 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/vis/rails/engine.rb +6 -0
- data/lib/vis/rails/version.rb +5 -0
- data/lib/vis/rails.rb +7 -0
- data/vendor/assets/javascripts/vis.js +1 -0
- data/vendor/assets/stylesheets/vis.css +3 -0
- data/vendor/assets/vis/DataSet.js +936 -0
- data/vendor/assets/vis/DataView.js +281 -0
- data/vendor/assets/vis/EventBus.js +89 -0
- data/vendor/assets/vis/events.js +116 -0
- data/vendor/assets/vis/graph/ClusterMixin.js +1019 -0
- data/vendor/assets/vis/graph/Edge.js +620 -0
- data/vendor/assets/vis/graph/Graph.js +2111 -0
- data/vendor/assets/vis/graph/Groups.js +80 -0
- data/vendor/assets/vis/graph/Images.js +41 -0
- data/vendor/assets/vis/graph/NavigationMixin.js +245 -0
- data/vendor/assets/vis/graph/Node.js +978 -0
- data/vendor/assets/vis/graph/Popup.js +105 -0
- data/vendor/assets/vis/graph/SectorsMixin.js +547 -0
- data/vendor/assets/vis/graph/SelectionMixin.js +515 -0
- data/vendor/assets/vis/graph/dotparser.js +829 -0
- data/vendor/assets/vis/graph/img/downarrow.png +0 -0
- data/vendor/assets/vis/graph/img/leftarrow.png +0 -0
- data/vendor/assets/vis/graph/img/minus.png +0 -0
- data/vendor/assets/vis/graph/img/plus.png +0 -0
- data/vendor/assets/vis/graph/img/rightarrow.png +0 -0
- data/vendor/assets/vis/graph/img/uparrow.png +0 -0
- data/vendor/assets/vis/graph/img/zoomExtends.png +0 -0
- data/vendor/assets/vis/graph/shapes.js +225 -0
- data/vendor/assets/vis/module/exports.js +68 -0
- data/vendor/assets/vis/module/header.js +24 -0
- data/vendor/assets/vis/module/imports.js +32 -0
- data/vendor/assets/vis/shim.js +252 -0
- data/vendor/assets/vis/timeline/Controller.js +172 -0
- data/vendor/assets/vis/timeline/Range.js +553 -0
- data/vendor/assets/vis/timeline/Stack.js +192 -0
- data/vendor/assets/vis/timeline/TimeStep.js +449 -0
- data/vendor/assets/vis/timeline/Timeline.js +476 -0
- data/vendor/assets/vis/timeline/component/Component.js +148 -0
- data/vendor/assets/vis/timeline/component/ContentPanel.js +113 -0
- data/vendor/assets/vis/timeline/component/CurrentTime.js +101 -0
- data/vendor/assets/vis/timeline/component/CustomTime.js +255 -0
- data/vendor/assets/vis/timeline/component/Group.js +129 -0
- data/vendor/assets/vis/timeline/component/GroupSet.js +546 -0
- data/vendor/assets/vis/timeline/component/ItemSet.js +612 -0
- data/vendor/assets/vis/timeline/component/Panel.js +112 -0
- data/vendor/assets/vis/timeline/component/RootPanel.js +215 -0
- data/vendor/assets/vis/timeline/component/TimeAxis.js +522 -0
- data/vendor/assets/vis/timeline/component/css/currenttime.css +5 -0
- data/vendor/assets/vis/timeline/component/css/customtime.css +6 -0
- data/vendor/assets/vis/timeline/component/css/groupset.css +59 -0
- data/vendor/assets/vis/timeline/component/css/item.css +93 -0
- data/vendor/assets/vis/timeline/component/css/itemset.css +17 -0
- data/vendor/assets/vis/timeline/component/css/panel.css +14 -0
- data/vendor/assets/vis/timeline/component/css/timeaxis.css +41 -0
- data/vendor/assets/vis/timeline/component/css/timeline.css +2 -0
- data/vendor/assets/vis/timeline/component/item/Item.js +81 -0
- data/vendor/assets/vis/timeline/component/item/ItemBox.js +302 -0
- data/vendor/assets/vis/timeline/component/item/ItemPoint.js +237 -0
- data/vendor/assets/vis/timeline/component/item/ItemRange.js +251 -0
- data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +91 -0
- data/vendor/assets/vis/util.js +673 -0
- data/vis-rails.gemspec +47 -0
- 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
|
+
};
|