vis-rails 0.0.4 → 0.0.5
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 +5 -13
- data/lib/vis/rails/version.rb +1 -1
- data/vendor/assets/component/emitter.js +162 -0
- data/vendor/assets/javascripts/vis.js +1 -0
- data/vendor/assets/vis/DataSet.js +8 -2
- data/vendor/assets/vis/DataView.js +8 -4
- data/vendor/assets/vis/graph/Edge.js +210 -78
- data/vendor/assets/vis/graph/Graph.js +474 -652
- data/vendor/assets/vis/graph/Node.js +119 -82
- data/vendor/assets/vis/graph/css/graph-manipulation.css +128 -0
- data/vendor/assets/vis/graph/css/graph-navigation.css +62 -0
- data/vendor/assets/vis/graph/graphMixins/ClusterMixin.js +1141 -0
- data/vendor/assets/vis/graph/graphMixins/HierarchicalLayoutMixin.js +296 -0
- data/vendor/assets/vis/graph/graphMixins/ManipulationMixin.js +433 -0
- data/vendor/assets/vis/graph/graphMixins/MixinLoader.js +201 -0
- data/vendor/assets/vis/graph/graphMixins/NavigationMixin.js +173 -0
- data/vendor/assets/vis/graph/graphMixins/SectorsMixin.js +552 -0
- data/vendor/assets/vis/graph/graphMixins/SelectionMixin.js +558 -0
- data/vendor/assets/vis/graph/graphMixins/physics/BarnesHut.js +373 -0
- data/vendor/assets/vis/graph/graphMixins/physics/HierarchialRepulsion.js +64 -0
- data/vendor/assets/vis/graph/graphMixins/physics/PhysicsMixin.js +513 -0
- data/vendor/assets/vis/graph/graphMixins/physics/Repulsion.js +66 -0
- data/vendor/assets/vis/graph/img/acceptDeleteIcon.png +0 -0
- data/vendor/assets/vis/graph/img/addNodeIcon.png +0 -0
- data/vendor/assets/vis/graph/img/backIcon.png +0 -0
- data/vendor/assets/vis/graph/img/connectIcon.png +0 -0
- data/vendor/assets/vis/graph/img/cross.png +0 -0
- data/vendor/assets/vis/graph/img/cross2.png +0 -0
- data/vendor/assets/vis/graph/img/deleteIcon.png +0 -0
- data/vendor/assets/vis/graph/img/downArrow.png +0 -0
- data/vendor/assets/vis/graph/img/editIcon.png +0 -0
- data/vendor/assets/vis/graph/img/leftArrow.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/module/exports.js +0 -2
- data/vendor/assets/vis/module/header.js +2 -2
- data/vendor/assets/vis/module/imports.js +1 -2
- data/vendor/assets/vis/timeline/Controller.js +56 -45
- data/vendor/assets/vis/timeline/Range.js +68 -62
- data/vendor/assets/vis/timeline/Stack.js +11 -13
- data/vendor/assets/vis/timeline/TimeStep.js +43 -38
- data/vendor/assets/vis/timeline/Timeline.js +215 -93
- data/vendor/assets/vis/timeline/component/Component.js +19 -3
- data/vendor/assets/vis/timeline/component/CurrentTime.js +1 -1
- data/vendor/assets/vis/timeline/component/CustomTime.js +39 -120
- data/vendor/assets/vis/timeline/component/GroupSet.js +35 -1
- data/vendor/assets/vis/timeline/component/ItemSet.js +272 -9
- data/vendor/assets/vis/timeline/component/RootPanel.js +59 -47
- data/vendor/assets/vis/timeline/component/TimeAxis.js +10 -0
- data/vendor/assets/vis/timeline/component/css/item.css +53 -22
- data/vendor/assets/vis/timeline/component/item/Item.js +40 -5
- data/vendor/assets/vis/timeline/component/item/ItemBox.js +3 -1
- data/vendor/assets/vis/timeline/component/item/ItemPoint.js +3 -1
- data/vendor/assets/vis/timeline/component/item/ItemRange.js +67 -3
- data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +37 -9
- data/vendor/assets/vis/timeline/img/delete.png +0 -0
- data/vendor/assets/vis/util.js +169 -30
- metadata +39 -12
@@ -1,11 +1,11 @@
|
|
1
1
|
/**
|
2
2
|
* @constructor Stack
|
3
3
|
* Stacks items on top of each other.
|
4
|
-
* @param {ItemSet}
|
4
|
+
* @param {ItemSet} itemset
|
5
5
|
* @param {Object} [options]
|
6
6
|
*/
|
7
|
-
function Stack (
|
8
|
-
this.
|
7
|
+
function Stack (itemset, options) {
|
8
|
+
this.itemset = itemset;
|
9
9
|
|
10
10
|
this.options = options || {};
|
11
11
|
this.defaultOptions = {
|
@@ -43,14 +43,14 @@ function Stack (parent, options) {
|
|
43
43
|
/**
|
44
44
|
* Set options for the stack
|
45
45
|
* @param {Object} options Available options:
|
46
|
-
* {ItemSet}
|
46
|
+
* {ItemSet} itemset
|
47
47
|
* {Number} margin
|
48
48
|
* {function} order Stacking order
|
49
49
|
*/
|
50
50
|
Stack.prototype.setOptions = function setOptions (options) {
|
51
51
|
util.extend(this.options, options);
|
52
52
|
|
53
|
-
// TODO: register on data changes at the connected
|
53
|
+
// TODO: register on data changes at the connected itemset, and update the changed part only and immediately
|
54
54
|
};
|
55
55
|
|
56
56
|
/**
|
@@ -63,16 +63,14 @@ Stack.prototype.update = function update() {
|
|
63
63
|
};
|
64
64
|
|
65
65
|
/**
|
66
|
-
* Order the items.
|
67
|
-
*
|
68
|
-
* If a custom order function has been provided via the options, then this will
|
69
|
-
* be used.
|
66
|
+
* Order the items. If a custom order function has been provided via the options,
|
67
|
+
* then this will be used.
|
70
68
|
* @private
|
71
69
|
*/
|
72
70
|
Stack.prototype._order = function _order () {
|
73
|
-
var items = this.
|
71
|
+
var items = this.itemset.items;
|
74
72
|
if (!items) {
|
75
|
-
throw new Error('Cannot stack items:
|
73
|
+
throw new Error('Cannot stack items: ItemSet does not contain items');
|
76
74
|
}
|
77
75
|
|
78
76
|
// TODO: store the sorted items, to have less work later on
|
@@ -185,8 +183,8 @@ Stack.prototype.checkOverlap = function checkOverlap (items, itemIndex,
|
|
185
183
|
* @return {boolean} true if a and b collide, else false
|
186
184
|
*/
|
187
185
|
Stack.prototype.collision = function collision (a, b, margin) {
|
188
|
-
return ((a.left - margin) < (b.left + b.
|
189
|
-
(a.left + a.
|
186
|
+
return ((a.left - margin) < (b.left + b.width) &&
|
187
|
+
(a.left + a.width + margin) > b.left &&
|
190
188
|
(a.top - margin) < (b.top + b.height) &&
|
191
189
|
(a.top + a.height + margin) > b.top);
|
192
190
|
};
|
@@ -281,35 +281,38 @@ TimeStep.prototype.setMinimumStep = function(minimumStep) {
|
|
281
281
|
};
|
282
282
|
|
283
283
|
/**
|
284
|
-
* Snap a date to a rounded value.
|
285
|
-
* current scale and step.
|
286
|
-
* @param {Date} date the date to be snapped
|
284
|
+
* Snap a date to a rounded value.
|
285
|
+
* The snap intervals are dependent on the current scale and step.
|
286
|
+
* @param {Date} date the date to be snapped.
|
287
|
+
* @return {Date} snappedDate
|
287
288
|
*/
|
288
289
|
TimeStep.prototype.snap = function(date) {
|
290
|
+
var clone = new Date(date.valueOf());
|
291
|
+
|
289
292
|
if (this.scale == TimeStep.SCALE.YEAR) {
|
290
|
-
var year =
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
293
|
+
var year = clone.getFullYear() + Math.round(clone.getMonth() / 12);
|
294
|
+
clone.setFullYear(Math.round(year / this.step) * this.step);
|
295
|
+
clone.setMonth(0);
|
296
|
+
clone.setDate(0);
|
297
|
+
clone.setHours(0);
|
298
|
+
clone.setMinutes(0);
|
299
|
+
clone.setSeconds(0);
|
300
|
+
clone.setMilliseconds(0);
|
298
301
|
}
|
299
302
|
else if (this.scale == TimeStep.SCALE.MONTH) {
|
300
|
-
if (
|
301
|
-
|
302
|
-
|
303
|
+
if (clone.getDate() > 15) {
|
304
|
+
clone.setDate(1);
|
305
|
+
clone.setMonth(clone.getMonth() + 1);
|
303
306
|
// important: first set Date to 1, after that change the month.
|
304
307
|
}
|
305
308
|
else {
|
306
|
-
|
309
|
+
clone.setDate(1);
|
307
310
|
}
|
308
311
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
312
|
+
clone.setHours(0);
|
313
|
+
clone.setMinutes(0);
|
314
|
+
clone.setSeconds(0);
|
315
|
+
clone.setMilliseconds(0);
|
313
316
|
}
|
314
317
|
else if (this.scale == TimeStep.SCALE.DAY ||
|
315
318
|
this.scale == TimeStep.SCALE.WEEKDAY) {
|
@@ -317,56 +320,58 @@ TimeStep.prototype.snap = function(date) {
|
|
317
320
|
switch (this.step) {
|
318
321
|
case 5:
|
319
322
|
case 2:
|
320
|
-
|
323
|
+
clone.setHours(Math.round(clone.getHours() / 24) * 24); break;
|
321
324
|
default:
|
322
|
-
|
325
|
+
clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
|
323
326
|
}
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
+
clone.setMinutes(0);
|
328
|
+
clone.setSeconds(0);
|
329
|
+
clone.setMilliseconds(0);
|
327
330
|
}
|
328
331
|
else if (this.scale == TimeStep.SCALE.HOUR) {
|
329
332
|
switch (this.step) {
|
330
333
|
case 4:
|
331
|
-
|
334
|
+
clone.setMinutes(Math.round(clone.getMinutes() / 60) * 60); break;
|
332
335
|
default:
|
333
|
-
|
336
|
+
clone.setMinutes(Math.round(clone.getMinutes() / 30) * 30); break;
|
334
337
|
}
|
335
|
-
|
336
|
-
|
338
|
+
clone.setSeconds(0);
|
339
|
+
clone.setMilliseconds(0);
|
337
340
|
} else if (this.scale == TimeStep.SCALE.MINUTE) {
|
338
341
|
//noinspection FallthroughInSwitchStatementJS
|
339
342
|
switch (this.step) {
|
340
343
|
case 15:
|
341
344
|
case 10:
|
342
|
-
|
343
|
-
|
345
|
+
clone.setMinutes(Math.round(clone.getMinutes() / 5) * 5);
|
346
|
+
clone.setSeconds(0);
|
344
347
|
break;
|
345
348
|
case 5:
|
346
|
-
|
349
|
+
clone.setSeconds(Math.round(clone.getSeconds() / 60) * 60); break;
|
347
350
|
default:
|
348
|
-
|
351
|
+
clone.setSeconds(Math.round(clone.getSeconds() / 30) * 30); break;
|
349
352
|
}
|
350
|
-
|
353
|
+
clone.setMilliseconds(0);
|
351
354
|
}
|
352
355
|
else if (this.scale == TimeStep.SCALE.SECOND) {
|
353
356
|
//noinspection FallthroughInSwitchStatementJS
|
354
357
|
switch (this.step) {
|
355
358
|
case 15:
|
356
359
|
case 10:
|
357
|
-
|
358
|
-
|
360
|
+
clone.setSeconds(Math.round(clone.getSeconds() / 5) * 5);
|
361
|
+
clone.setMilliseconds(0);
|
359
362
|
break;
|
360
363
|
case 5:
|
361
|
-
|
364
|
+
clone.setMilliseconds(Math.round(clone.getMilliseconds() / 1000) * 1000); break;
|
362
365
|
default:
|
363
|
-
|
366
|
+
clone.setMilliseconds(Math.round(clone.getMilliseconds() / 500) * 500); break;
|
364
367
|
}
|
365
368
|
}
|
366
369
|
else if (this.scale == TimeStep.SCALE.MILLISECOND) {
|
367
370
|
var step = this.step > 5 ? this.step / 2 : 1;
|
368
|
-
|
371
|
+
clone.setMilliseconds(Math.round(clone.getMilliseconds() / step) * step);
|
369
372
|
}
|
373
|
+
|
374
|
+
return clone;
|
370
375
|
};
|
371
376
|
|
372
377
|
/**
|
@@ -1,7 +1,7 @@
|
|
1
1
|
/**
|
2
2
|
* Create a timeline visualization
|
3
3
|
* @param {HTMLElement} container
|
4
|
-
* @param {vis.DataSet | Array | DataTable} [items]
|
4
|
+
* @param {vis.DataSet | Array | google.visualization.DataTable} [items]
|
5
5
|
* @param {Object} [options] See Timeline.setOptions for the available options.
|
6
6
|
* @constructor
|
7
7
|
*/
|
@@ -10,17 +10,35 @@ function Timeline (container, items, options) {
|
|
10
10
|
var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
|
11
11
|
this.options = {
|
12
12
|
orientation: 'bottom',
|
13
|
+
autoResize: true,
|
14
|
+
editable: false,
|
15
|
+
selectable: true,
|
16
|
+
snap: null, // will be specified after timeaxis is created
|
17
|
+
|
13
18
|
min: null,
|
14
19
|
max: null,
|
15
20
|
zoomMin: 10, // milliseconds
|
16
21
|
zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds
|
17
22
|
// moveable: true, // TODO: option moveable
|
18
23
|
// zoomable: true, // TODO: option zoomable
|
24
|
+
|
19
25
|
showMinorLabels: true,
|
20
26
|
showMajorLabels: true,
|
21
27
|
showCurrentTime: false,
|
22
28
|
showCustomTime: false,
|
23
|
-
|
29
|
+
|
30
|
+
onAdd: function (item, callback) {
|
31
|
+
callback(item);
|
32
|
+
},
|
33
|
+
onUpdate: function (item, callback) {
|
34
|
+
callback(item);
|
35
|
+
},
|
36
|
+
onMove: function (item, callback) {
|
37
|
+
callback(item);
|
38
|
+
},
|
39
|
+
onRemove: function (item, callback) {
|
40
|
+
callback(item);
|
41
|
+
}
|
24
42
|
};
|
25
43
|
|
26
44
|
// controller
|
@@ -45,6 +63,15 @@ function Timeline (container, items, options) {
|
|
45
63
|
this.rootPanel = new RootPanel(container, rootOptions);
|
46
64
|
this.controller.add(this.rootPanel);
|
47
65
|
|
66
|
+
// single select (or unselect) when tapping an item
|
67
|
+
this.controller.on('tap', this._onSelectItem.bind(this));
|
68
|
+
|
69
|
+
// multi select when holding mouse/touch, or on ctrl+click
|
70
|
+
this.controller.on('hold', this._onMultiSelectItem.bind(this));
|
71
|
+
|
72
|
+
// add item on doubletap
|
73
|
+
this.controller.on('doubletap', this._onAddItem.bind(this));
|
74
|
+
|
48
75
|
// item panel
|
49
76
|
var itemOptions = Object.create(this.options);
|
50
77
|
itemOptions.left = function () {
|
@@ -82,28 +109,19 @@ function Timeline (container, items, options) {
|
|
82
109
|
now.clone().add('days', 4).valueOf()
|
83
110
|
);
|
84
111
|
|
85
|
-
|
86
|
-
|
87
|
-
this.range.subscribe(this.rootPanel, 'move', 'horizontal');
|
88
|
-
this.range.subscribe(this.rootPanel, 'zoom', 'horizontal');
|
112
|
+
this.range.subscribe(this.controller, this.rootPanel, 'move', 'horizontal');
|
113
|
+
this.range.subscribe(this.controller, this.rootPanel, 'zoom', 'horizontal');
|
89
114
|
this.range.on('rangechange', function (properties) {
|
90
115
|
var force = true;
|
91
|
-
me.controller.
|
92
|
-
me.
|
116
|
+
me.controller.emit('rangechange', properties);
|
117
|
+
me.controller.emit('request-reflow', force);
|
93
118
|
});
|
94
119
|
this.range.on('rangechanged', function (properties) {
|
95
120
|
var force = true;
|
96
|
-
me.controller.
|
97
|
-
me.
|
121
|
+
me.controller.emit('rangechanged', properties);
|
122
|
+
me.controller.emit('request-reflow', force);
|
98
123
|
});
|
99
124
|
|
100
|
-
// single select (or unselect) when tapping an item
|
101
|
-
// TODO: implement ctrl+click
|
102
|
-
this.rootPanel.on('tap', this._onSelectItem.bind(this));
|
103
|
-
|
104
|
-
// multi select when holding mouse/touch, or on ctrl+click
|
105
|
-
this.rootPanel.on('hold', this._onMultiSelectItem.bind(this));
|
106
|
-
|
107
125
|
// time axis
|
108
126
|
var timeaxisOptions = Object.create(rootOptions);
|
109
127
|
timeaxisOptions.range = this.range;
|
@@ -114,6 +132,7 @@ function Timeline (container, items, options) {
|
|
114
132
|
this.timeaxis = new TimeAxis(this.itemPanel, [], timeaxisOptions);
|
115
133
|
this.timeaxis.setRange(this.range);
|
116
134
|
this.controller.add(this.timeaxis);
|
135
|
+
this.options.snap = this.timeaxis.snap.bind(this.timeaxis);
|
117
136
|
|
118
137
|
// current time bar
|
119
138
|
this.currenttime = new CurrentTime(this.timeaxis, [], rootOptions);
|
@@ -140,6 +159,25 @@ function Timeline (container, items, options) {
|
|
140
159
|
}
|
141
160
|
}
|
142
161
|
|
162
|
+
/**
|
163
|
+
* Add an event listener to the timeline
|
164
|
+
* @param {String} event Available events: select, rangechange, rangechanged,
|
165
|
+
* timechange, timechanged
|
166
|
+
* @param {function} callback
|
167
|
+
*/
|
168
|
+
Timeline.prototype.on = function on (event, callback) {
|
169
|
+
this.controller.on(event, callback);
|
170
|
+
};
|
171
|
+
|
172
|
+
/**
|
173
|
+
* Add an event listener from the timeline
|
174
|
+
* @param {String} event
|
175
|
+
* @param {function} callback
|
176
|
+
*/
|
177
|
+
Timeline.prototype.off = function off (event, callback) {
|
178
|
+
this.controller.off(event, callback);
|
179
|
+
};
|
180
|
+
|
143
181
|
/**
|
144
182
|
* Set options
|
145
183
|
* @param {Object} options TODO: describe the available options
|
@@ -151,6 +189,25 @@ Timeline.prototype.setOptions = function (options) {
|
|
151
189
|
// both start and end are optional
|
152
190
|
this.range.setRange(options.start, options.end);
|
153
191
|
|
192
|
+
if ('editable' in options || 'selectable' in options) {
|
193
|
+
if (this.options.selectable) {
|
194
|
+
// force update of selection
|
195
|
+
this.setSelection(this.getSelection());
|
196
|
+
}
|
197
|
+
else {
|
198
|
+
// remove selection
|
199
|
+
this.setSelection([]);
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
203
|
+
// validate the callback functions
|
204
|
+
var validateCallback = (function (fn) {
|
205
|
+
if (!(this.options[fn] instanceof Function) || this.options[fn].length != 2) {
|
206
|
+
throw new Error('option ' + fn + ' must be a function ' + fn + '(item, callback)');
|
207
|
+
}
|
208
|
+
}).bind(this);
|
209
|
+
['onAdd', 'onUpdate', 'onRemove', 'onMove'].forEach(validateCallback);
|
210
|
+
|
154
211
|
this.controller.reflow();
|
155
212
|
this.controller.repaint();
|
156
213
|
};
|
@@ -160,7 +217,11 @@ Timeline.prototype.setOptions = function (options) {
|
|
160
217
|
* @param {Date} time
|
161
218
|
*/
|
162
219
|
Timeline.prototype.setCustomTime = function (time) {
|
163
|
-
this.customtime
|
220
|
+
if (!this.customtime) {
|
221
|
+
throw new Error('Cannot get custom time: Custom time bar is not enabled');
|
222
|
+
}
|
223
|
+
|
224
|
+
this.customtime.setCustomTime(time);
|
164
225
|
};
|
165
226
|
|
166
227
|
/**
|
@@ -168,37 +229,41 @@ Timeline.prototype.setCustomTime = function (time) {
|
|
168
229
|
* @return {Date} customTime
|
169
230
|
*/
|
170
231
|
Timeline.prototype.getCustomTime = function() {
|
171
|
-
|
232
|
+
if (!this.customtime) {
|
233
|
+
throw new Error('Cannot get custom time: Custom time bar is not enabled');
|
234
|
+
}
|
235
|
+
|
236
|
+
return this.customtime.getCustomTime();
|
172
237
|
};
|
173
238
|
|
174
239
|
/**
|
175
240
|
* Set items
|
176
|
-
* @param {vis.DataSet | Array | DataTable | null} items
|
241
|
+
* @param {vis.DataSet | Array | google.visualization.DataTable | null} items
|
177
242
|
*/
|
178
243
|
Timeline.prototype.setItems = function(items) {
|
179
244
|
var initialLoad = (this.itemsData == null);
|
180
245
|
|
181
246
|
// convert to type DataSet when needed
|
182
|
-
var
|
247
|
+
var newDataSet;
|
183
248
|
if (!items) {
|
184
|
-
|
249
|
+
newDataSet = null;
|
185
250
|
}
|
186
251
|
else if (items instanceof DataSet) {
|
187
|
-
|
252
|
+
newDataSet = items;
|
188
253
|
}
|
189
254
|
if (!(items instanceof DataSet)) {
|
190
|
-
|
255
|
+
newDataSet = new DataSet({
|
191
256
|
convert: {
|
192
257
|
start: 'Date',
|
193
258
|
end: 'Date'
|
194
259
|
}
|
195
260
|
});
|
196
|
-
|
261
|
+
newDataSet.add(items);
|
197
262
|
}
|
198
263
|
|
199
264
|
// set items
|
200
|
-
this.itemsData =
|
201
|
-
this.content.setItems(
|
265
|
+
this.itemsData = newDataSet;
|
266
|
+
this.content.setItems(newDataSet);
|
202
267
|
|
203
268
|
if (initialLoad && (this.options.start == undefined || this.options.end == undefined)) {
|
204
269
|
// apply the data range as range
|
@@ -234,7 +299,7 @@ Timeline.prototype.setItems = function(items) {
|
|
234
299
|
|
235
300
|
/**
|
236
301
|
* Set groups
|
237
|
-
* @param {vis.DataSet | Array | DataTable} groups
|
302
|
+
* @param {vis.DataSet | Array | google.visualization.DataTable} groups
|
238
303
|
*/
|
239
304
|
Timeline.prototype.setGroups = function(groups) {
|
240
305
|
var me = this;
|
@@ -368,41 +433,25 @@ Timeline.prototype.getSelection = function getSelection() {
|
|
368
433
|
};
|
369
434
|
|
370
435
|
/**
|
371
|
-
*
|
372
|
-
*
|
373
|
-
*
|
374
|
-
* @param {
|
375
|
-
* where properties is an optional object containing
|
376
|
-
* event specific properties.
|
436
|
+
* Set the visible window. Both parameters are optional, you can change only
|
437
|
+
* start or only end.
|
438
|
+
* @param {Date | Number | String} [start] Start date of visible window
|
439
|
+
* @param {Date | Number | String} [end] End date of visible window
|
377
440
|
*/
|
378
|
-
Timeline.prototype.
|
379
|
-
|
380
|
-
|
381
|
-
if (available.indexOf(event) == -1) {
|
382
|
-
throw new Error('Unknown event "' + event + '". Choose from ' + available.join());
|
383
|
-
}
|
384
|
-
|
385
|
-
events.addListener(this, event, callback);
|
441
|
+
Timeline.prototype.setWindow = function setWindow(start, end) {
|
442
|
+
this.range.setRange(start, end);
|
386
443
|
};
|
387
444
|
|
388
445
|
/**
|
389
|
-
*
|
390
|
-
* @
|
391
|
-
* @param {function} callback Callback function
|
392
|
-
*/
|
393
|
-
Timeline.prototype.off = function off (event, callback) {
|
394
|
-
events.removeListener(this, event, callback);
|
395
|
-
};
|
396
|
-
|
397
|
-
/**
|
398
|
-
* Trigger an event
|
399
|
-
* @param {String} event Event name, available events: 'rangechange',
|
400
|
-
* 'rangechanged', 'select'
|
401
|
-
* @param {Object} [properties] Event specific properties
|
402
|
-
* @private
|
446
|
+
* Get the visible window
|
447
|
+
* @return {{start: Date, end: Date}} Visible range
|
403
448
|
*/
|
404
|
-
Timeline.prototype.
|
405
|
-
|
449
|
+
Timeline.prototype.getWindow = function setWindow() {
|
450
|
+
var range = this.range.getRange();
|
451
|
+
return {
|
452
|
+
start: new Date(range.start),
|
453
|
+
end: new Date(range.end)
|
454
|
+
};
|
406
455
|
};
|
407
456
|
|
408
457
|
/**
|
@@ -410,13 +459,23 @@ Timeline.prototype._trigger = function _trigger(event, properties) {
|
|
410
459
|
* @param {Event} event
|
411
460
|
* @private
|
412
461
|
*/
|
462
|
+
// TODO: move this function to ItemSet
|
413
463
|
Timeline.prototype._onSelectItem = function (event) {
|
414
|
-
|
464
|
+
if (!this.options.selectable) return;
|
465
|
+
|
466
|
+
var ctrlKey = event.gesture.srcEvent && event.gesture.srcEvent.ctrlKey;
|
467
|
+
var shiftKey = event.gesture.srcEvent && event.gesture.srcEvent.shiftKey;
|
468
|
+
if (ctrlKey || shiftKey) {
|
469
|
+
this._onMultiSelectItem(event);
|
470
|
+
return;
|
471
|
+
}
|
472
|
+
|
473
|
+
var item = ItemSet.itemFromTarget(event);
|
415
474
|
|
416
475
|
var selection = item ? [item.id] : [];
|
417
476
|
this.setSelection(selection);
|
418
477
|
|
419
|
-
this.
|
478
|
+
this.controller.emit('select', {
|
420
479
|
items: this.getSelection()
|
421
480
|
});
|
422
481
|
|
@@ -424,53 +483,116 @@ Timeline.prototype._onSelectItem = function (event) {
|
|
424
483
|
};
|
425
484
|
|
426
485
|
/**
|
427
|
-
* Handle
|
428
|
-
* @param
|
486
|
+
* Handle creation and updates of an item on double tap
|
487
|
+
* @param event
|
429
488
|
* @private
|
430
489
|
*/
|
431
|
-
Timeline.prototype.
|
432
|
-
|
433
|
-
|
490
|
+
Timeline.prototype._onAddItem = function (event) {
|
491
|
+
if (!this.options.selectable) return;
|
492
|
+
if (!this.options.editable) return;
|
434
493
|
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
494
|
+
var me = this,
|
495
|
+
item = ItemSet.itemFromTarget(event);
|
496
|
+
|
497
|
+
if (item) {
|
498
|
+
// update item
|
439
499
|
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
500
|
+
// execute async handler to update the item (or cancel it)
|
501
|
+
var itemData = me.itemsData.get(item.id); // get a clone of the data from the dataset
|
502
|
+
this.options.onUpdate(itemData, function (itemData) {
|
503
|
+
if (itemData) {
|
504
|
+
me.itemsData.update(itemData);
|
505
|
+
}
|
506
|
+
});
|
445
507
|
}
|
446
508
|
else {
|
447
|
-
// item
|
448
|
-
|
449
|
-
|
450
|
-
|
509
|
+
// add item
|
510
|
+
var xAbs = vis.util.getAbsoluteLeft(this.rootPanel.frame);
|
511
|
+
var x = event.gesture.center.pageX - xAbs;
|
512
|
+
var newItem = {
|
513
|
+
start: this.timeaxis.snap(this._toTime(x)),
|
514
|
+
content: 'new item'
|
515
|
+
};
|
516
|
+
|
517
|
+
var id = util.randomUUID();
|
518
|
+
newItem[this.itemsData.fieldId] = id;
|
519
|
+
|
520
|
+
var group = GroupSet.groupFromTarget(event);
|
521
|
+
if (group) {
|
522
|
+
newItem.group = group.groupId;
|
523
|
+
}
|
451
524
|
|
452
|
-
|
453
|
-
|
454
|
-
|
525
|
+
// execute async handler to customize (or cancel) adding an item
|
526
|
+
this.options.onAdd(newItem, function (item) {
|
527
|
+
if (item) {
|
528
|
+
me.itemsData.add(newItem);
|
455
529
|
|
456
|
-
|
530
|
+
// select the created item after it is repainted
|
531
|
+
me.controller.once('repaint', function () {
|
532
|
+
me.setSelection([id]);
|
533
|
+
|
534
|
+
me.controller.emit('select', {
|
535
|
+
items: me.getSelection()
|
536
|
+
});
|
537
|
+
}.bind(me));
|
538
|
+
}
|
539
|
+
});
|
540
|
+
}
|
457
541
|
};
|
458
542
|
|
459
543
|
/**
|
460
|
-
*
|
461
|
-
* searches for the attribute 'timeline-item' in the event target's element tree
|
544
|
+
* Handle selecting/deselecting multiple items when holding an item
|
462
545
|
* @param {Event} event
|
463
|
-
* @return {Item | null| item
|
464
546
|
* @private
|
465
547
|
*/
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
548
|
+
// TODO: move this function to ItemSet
|
549
|
+
Timeline.prototype._onMultiSelectItem = function (event) {
|
550
|
+
if (!this.options.selectable) return;
|
551
|
+
|
552
|
+
var selection,
|
553
|
+
item = ItemSet.itemFromTarget(event);
|
554
|
+
|
555
|
+
if (item) {
|
556
|
+
// multi select items
|
557
|
+
selection = this.getSelection(); // current selection
|
558
|
+
var index = selection.indexOf(item.id);
|
559
|
+
if (index == -1) {
|
560
|
+
// item is not yet selected -> select it
|
561
|
+
selection.push(item.id);
|
562
|
+
}
|
563
|
+
else {
|
564
|
+
// item is already selected -> deselect it
|
565
|
+
selection.splice(index, 1);
|
471
566
|
}
|
472
|
-
|
567
|
+
this.setSelection(selection);
|
568
|
+
|
569
|
+
this.controller.emit('select', {
|
570
|
+
items: this.getSelection()
|
571
|
+
});
|
572
|
+
|
573
|
+
event.stopPropagation();
|
473
574
|
}
|
575
|
+
};
|
474
576
|
|
475
|
-
|
476
|
-
|
577
|
+
/**
|
578
|
+
* Convert a position on screen (pixels) to a datetime
|
579
|
+
* @param {int} x Position on the screen in pixels
|
580
|
+
* @return {Date} time The datetime the corresponds with given position x
|
581
|
+
* @private
|
582
|
+
*/
|
583
|
+
Timeline.prototype._toTime = function _toTime(x) {
|
584
|
+
var conversion = this.range.conversion(this.content.width);
|
585
|
+
return new Date(x / conversion.scale + conversion.offset);
|
586
|
+
};
|
587
|
+
|
588
|
+
/**
|
589
|
+
* Convert a datetime (Date object) into a position on the screen
|
590
|
+
* @param {Date} time A date
|
591
|
+
* @return {int} x The position on the screen in pixels which corresponds
|
592
|
+
* with the given date.
|
593
|
+
* @private
|
594
|
+
*/
|
595
|
+
Timeline.prototype._toScreen = function _toScreen(time) {
|
596
|
+
var conversion = this.range.conversion(this.content.width);
|
597
|
+
return (time.valueOf() - conversion.offset) * conversion.scale;
|
598
|
+
};
|