vis-rails 0.0.6 → 1.0.0
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 +4 -4
- data/lib/vis/rails/version.rb +1 -1
- data/vendor/assets/javascripts/vis.js +2 -9
- data/vendor/assets/vis/DataSet.js +17 -9
- data/vendor/assets/vis/graph/Edge.js +49 -24
- data/vendor/assets/vis/graph/Graph.js +268 -64
- data/vendor/assets/vis/graph/Groups.js +1 -1
- data/vendor/assets/vis/graph/Node.js +18 -67
- data/vendor/assets/vis/graph/Popup.js +40 -13
- data/vendor/assets/vis/graph/css/graph-navigation.css +18 -14
- data/vendor/assets/vis/graph/graphMixins/ClusterMixin.js +7 -5
- data/vendor/assets/vis/graph/graphMixins/HierarchicalLayoutMixin.js +20 -5
- data/vendor/assets/vis/graph/graphMixins/ManipulationMixin.js +33 -33
- data/vendor/assets/vis/graph/graphMixins/MixinLoader.js +30 -32
- data/vendor/assets/vis/graph/graphMixins/NavigationMixin.js +33 -1
- data/vendor/assets/vis/graph/graphMixins/SectorsMixin.js +2 -2
- data/vendor/assets/vis/graph/graphMixins/SelectionMixin.js +72 -60
- data/vendor/assets/vis/graph/graphMixins/physics/BarnesHut.js +43 -18
- data/vendor/assets/vis/graph/graphMixins/physics/HierarchialRepulsion.js +8 -8
- data/vendor/assets/vis/graph/graphMixins/physics/PhysicsMixin.js +309 -129
- data/vendor/assets/vis/graph/graphMixins/physics/Repulsion.js +10 -10
- data/vendor/assets/vis/module/exports.js +1 -2
- data/vendor/assets/vis/module/header.js +2 -2
- data/vendor/assets/vis/timeline/Range.js +53 -93
- data/vendor/assets/vis/timeline/Timeline.js +328 -224
- data/vendor/assets/vis/timeline/component/Component.js +17 -95
- data/vendor/assets/vis/timeline/component/CurrentTime.js +54 -59
- data/vendor/assets/vis/timeline/component/CustomTime.js +55 -83
- data/vendor/assets/vis/timeline/component/Group.js +398 -75
- data/vendor/assets/vis/timeline/component/ItemSet.js +662 -403
- data/vendor/assets/vis/timeline/component/Panel.js +118 -60
- data/vendor/assets/vis/timeline/component/RootPanel.js +80 -132
- data/vendor/assets/vis/timeline/component/TimeAxis.js +191 -277
- data/vendor/assets/vis/timeline/component/css/item.css +16 -23
- data/vendor/assets/vis/timeline/component/css/itemset.css +25 -4
- data/vendor/assets/vis/timeline/component/css/labelset.css +34 -0
- data/vendor/assets/vis/timeline/component/css/panel.css +15 -1
- data/vendor/assets/vis/timeline/component/css/timeaxis.css +8 -8
- data/vendor/assets/vis/timeline/component/item/Item.js +48 -26
- data/vendor/assets/vis/timeline/component/item/ItemBox.js +156 -230
- data/vendor/assets/vis/timeline/component/item/ItemPoint.js +118 -166
- data/vendor/assets/vis/timeline/component/item/ItemRange.js +135 -187
- data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +29 -92
- data/vendor/assets/vis/timeline/stack.js +112 -0
- data/vendor/assets/vis/util.js +136 -38
- metadata +4 -18
- data/vendor/assets/vis/.gitignore +0 -1
- data/vendor/assets/vis/EventBus.js +0 -89
- data/vendor/assets/vis/events.js +0 -116
- data/vendor/assets/vis/graph/ClusterMixin.js +0 -1019
- data/vendor/assets/vis/graph/NavigationMixin.js +0 -245
- data/vendor/assets/vis/graph/SectorsMixin.js +0 -547
- data/vendor/assets/vis/graph/SelectionMixin.js +0 -515
- 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/rightarrow.png +0 -0
- data/vendor/assets/vis/graph/img/uparrow.png +0 -0
- data/vendor/assets/vis/timeline/Controller.js +0 -183
- data/vendor/assets/vis/timeline/Stack.js +0 -190
- data/vendor/assets/vis/timeline/component/ContentPanel.js +0 -113
- data/vendor/assets/vis/timeline/component/GroupSet.js +0 -580
- data/vendor/assets/vis/timeline/component/css/groupset.css +0 -59
@@ -10,8 +10,8 @@ var repulsionMixin = {
|
|
10
10
|
* This field is linearly approximated.
|
11
11
|
*
|
12
12
|
* @private
|
13
|
-
|
14
|
-
_calculateNodeForces
|
13
|
+
*/
|
14
|
+
_calculateNodeForces: function () {
|
15
15
|
var dx, dy, angle, distance, fx, fy, combinedClusterSize,
|
16
16
|
repulsingForce, node1, node2, i, j;
|
17
17
|
|
@@ -19,8 +19,8 @@ var repulsionMixin = {
|
|
19
19
|
var nodeIndices = this.calculationNodeIndices;
|
20
20
|
|
21
21
|
// approximation constants
|
22
|
-
var a_base = -2/3;
|
23
|
-
var b = 4/3;
|
22
|
+
var a_base = -2 / 3;
|
23
|
+
var b = 4 / 3;
|
24
24
|
|
25
25
|
// repulsing forces between nodes
|
26
26
|
var nodeDistance = this.constants.physics.repulsion.nodeDistance;
|
@@ -28,9 +28,9 @@ var repulsionMixin = {
|
|
28
28
|
|
29
29
|
// we loop from i over all but the last entree in the array
|
30
30
|
// j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
|
31
|
-
for (i = 0; i < nodeIndices.length-1; i++) {
|
31
|
+
for (i = 0; i < nodeIndices.length - 1; i++) {
|
32
32
|
node1 = nodes[nodeIndices[i]];
|
33
|
-
for (j = i+1; j < nodeIndices.length; j++) {
|
33
|
+
for (j = i + 1; j < nodeIndices.length; j++) {
|
34
34
|
node2 = nodes[nodeIndices[j]];
|
35
35
|
combinedClusterSize = node1.clusterSize + node2.clusterSize - 2;
|
36
36
|
|
@@ -40,8 +40,8 @@ var repulsionMixin = {
|
|
40
40
|
|
41
41
|
minimumDistance = (combinedClusterSize == 0) ? nodeDistance : (nodeDistance * (1 + combinedClusterSize * this.constants.clustering.distanceAmplification));
|
42
42
|
var a = a_base / minimumDistance;
|
43
|
-
if (distance < 2*minimumDistance) {
|
44
|
-
if (distance < 0.5*minimumDistance) {
|
43
|
+
if (distance < 2 * minimumDistance) {
|
44
|
+
if (distance < 0.5 * minimumDistance) {
|
45
45
|
repulsingForce = 1.0;
|
46
46
|
}
|
47
47
|
else {
|
@@ -50,7 +50,7 @@ var repulsionMixin = {
|
|
50
50
|
|
51
51
|
// amplify the repulsion for clusters.
|
52
52
|
repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification;
|
53
|
-
repulsingForce = repulsingForce/distance;
|
53
|
+
repulsingForce = repulsingForce / distance;
|
54
54
|
|
55
55
|
fx = dx * repulsingForce;
|
56
56
|
fy = dy * repulsingForce;
|
@@ -63,4 +63,4 @@ var repulsionMixin = {
|
|
63
63
|
}
|
64
64
|
}
|
65
65
|
}
|
66
|
-
}
|
66
|
+
};
|
@@ -3,20 +3,39 @@
|
|
3
3
|
* A Range controls a numeric range with a start and end value.
|
4
4
|
* The Range adjusts the range based on mouse events or programmatic changes,
|
5
5
|
* and triggers events when the range is changing or has been changed.
|
6
|
-
* @param {
|
7
|
-
* @
|
6
|
+
* @param {RootPanel} root Root panel, used to subscribe to events
|
7
|
+
* @param {Panel} parent Parent panel, used to attach to the DOM
|
8
|
+
* @param {Object} [options] See description at Range.setOptions
|
8
9
|
*/
|
9
|
-
function Range(options) {
|
10
|
+
function Range(root, parent, options) {
|
10
11
|
this.id = util.randomUUID();
|
11
12
|
this.start = null; // Number
|
12
13
|
this.end = null; // Number
|
13
14
|
|
15
|
+
this.root = root;
|
16
|
+
this.parent = parent;
|
14
17
|
this.options = options || {};
|
15
18
|
|
19
|
+
// drag listeners for dragging
|
20
|
+
this.root.on('dragstart', this._onDragStart.bind(this));
|
21
|
+
this.root.on('drag', this._onDrag.bind(this));
|
22
|
+
this.root.on('dragend', this._onDragEnd.bind(this));
|
23
|
+
|
24
|
+
// ignore dragging when holding
|
25
|
+
this.root.on('hold', this._onHold.bind(this));
|
26
|
+
|
27
|
+
// mouse wheel for zooming
|
28
|
+
this.root.on('mousewheel', this._onMouseWheel.bind(this));
|
29
|
+
this.root.on('DOMMouseScroll', this._onMouseWheel.bind(this)); // For FF
|
30
|
+
|
31
|
+
// pinch to zoom
|
32
|
+
this.root.on('touch', this._onTouch.bind(this));
|
33
|
+
this.root.on('pinch', this._onPinch.bind(this));
|
34
|
+
|
16
35
|
this.setOptions(options);
|
17
36
|
}
|
18
37
|
|
19
|
-
//
|
38
|
+
// turn Range into an event emitter
|
20
39
|
Emitter(Range.prototype);
|
21
40
|
|
22
41
|
/**
|
@@ -49,59 +68,6 @@ function validateDirection (direction) {
|
|
49
68
|
}
|
50
69
|
}
|
51
70
|
|
52
|
-
/**
|
53
|
-
* Add listeners for mouse and touch events to the component
|
54
|
-
* @param {Controller} controller
|
55
|
-
* @param {Component} component Should be a rootpanel
|
56
|
-
* @param {String} event Available events: 'move', 'zoom'
|
57
|
-
* @param {String} direction Available directions: 'horizontal', 'vertical'
|
58
|
-
*/
|
59
|
-
Range.prototype.subscribe = function (controller, component, event, direction) {
|
60
|
-
var me = this;
|
61
|
-
|
62
|
-
if (event == 'move') {
|
63
|
-
// drag start listener
|
64
|
-
controller.on('dragstart', function (event) {
|
65
|
-
me._onDragStart(event, component);
|
66
|
-
});
|
67
|
-
|
68
|
-
// drag listener
|
69
|
-
controller.on('drag', function (event) {
|
70
|
-
me._onDrag(event, component, direction);
|
71
|
-
});
|
72
|
-
|
73
|
-
// drag end listener
|
74
|
-
controller.on('dragend', function (event) {
|
75
|
-
me._onDragEnd(event, component);
|
76
|
-
});
|
77
|
-
|
78
|
-
// ignore dragging when holding
|
79
|
-
controller.on('hold', function (event) {
|
80
|
-
me._onHold();
|
81
|
-
});
|
82
|
-
}
|
83
|
-
else if (event == 'zoom') {
|
84
|
-
// mouse wheel
|
85
|
-
function mousewheel (event) {
|
86
|
-
me._onMouseWheel(event, component, direction);
|
87
|
-
}
|
88
|
-
controller.on('mousewheel', mousewheel);
|
89
|
-
controller.on('DOMMouseScroll', mousewheel); // For FF
|
90
|
-
|
91
|
-
// pinch
|
92
|
-
controller.on('touch', function (event) {
|
93
|
-
me._onTouch(event);
|
94
|
-
});
|
95
|
-
controller.on('pinch', function (event) {
|
96
|
-
me._onPinch(event, component, direction);
|
97
|
-
});
|
98
|
-
}
|
99
|
-
else {
|
100
|
-
throw new TypeError('Unknown event "' + event + '". ' +
|
101
|
-
'Choose "move" or "zoom".');
|
102
|
-
}
|
103
|
-
};
|
104
|
-
|
105
71
|
/**
|
106
72
|
* Set a new start and end range
|
107
73
|
* @param {Number} [start]
|
@@ -111,8 +77,8 @@ Range.prototype.setRange = function(start, end) {
|
|
111
77
|
var changed = this._applyRange(start, end);
|
112
78
|
if (changed) {
|
113
79
|
var params = {
|
114
|
-
|
115
|
-
|
80
|
+
start: new Date(this.start),
|
81
|
+
end: new Date(this.end)
|
116
82
|
};
|
117
83
|
this.emit('rangechange', params);
|
118
84
|
this.emit('rangechanged', params);
|
@@ -280,10 +246,9 @@ var touchParams = {};
|
|
280
246
|
/**
|
281
247
|
* Start dragging horizontally or vertically
|
282
248
|
* @param {Event} event
|
283
|
-
* @param {Object} component
|
284
249
|
* @private
|
285
250
|
*/
|
286
|
-
Range.prototype._onDragStart = function(event
|
251
|
+
Range.prototype._onDragStart = function(event) {
|
287
252
|
// refuse to drag when we where pinching to prevent the timeline make a jump
|
288
253
|
// when releasing the fingers in opposite order from the touch screen
|
289
254
|
if (touchParams.ignore) return;
|
@@ -293,7 +258,7 @@ Range.prototype._onDragStart = function(event, component) {
|
|
293
258
|
touchParams.start = this.start;
|
294
259
|
touchParams.end = this.end;
|
295
260
|
|
296
|
-
var frame =
|
261
|
+
var frame = this.parent.frame;
|
297
262
|
if (frame) {
|
298
263
|
frame.style.cursor = 'move';
|
299
264
|
}
|
@@ -302,11 +267,10 @@ Range.prototype._onDragStart = function(event, component) {
|
|
302
267
|
/**
|
303
268
|
* Perform dragging operating.
|
304
269
|
* @param {Event} event
|
305
|
-
* @param {Component} component
|
306
|
-
* @param {String} direction 'horizontal' or 'vertical'
|
307
270
|
* @private
|
308
271
|
*/
|
309
|
-
Range.prototype._onDrag = function (event
|
272
|
+
Range.prototype._onDrag = function (event) {
|
273
|
+
var direction = this.options.direction;
|
310
274
|
validateDirection(direction);
|
311
275
|
|
312
276
|
// TODO: reckon with option movable
|
@@ -318,38 +282,37 @@ Range.prototype._onDrag = function (event, component, direction) {
|
|
318
282
|
|
319
283
|
var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY,
|
320
284
|
interval = (touchParams.end - touchParams.start),
|
321
|
-
width = (direction == 'horizontal') ?
|
285
|
+
width = (direction == 'horizontal') ? this.parent.width : this.parent.height,
|
322
286
|
diffRange = -delta / width * interval;
|
323
287
|
|
324
288
|
this._applyRange(touchParams.start + diffRange, touchParams.end + diffRange);
|
325
289
|
|
326
290
|
this.emit('rangechange', {
|
327
|
-
start: this.start,
|
328
|
-
end: this.end
|
291
|
+
start: new Date(this.start),
|
292
|
+
end: new Date(this.end)
|
329
293
|
});
|
330
294
|
};
|
331
295
|
|
332
296
|
/**
|
333
297
|
* Stop dragging operating.
|
334
298
|
* @param {event} event
|
335
|
-
* @param {Component} component
|
336
299
|
* @private
|
337
300
|
*/
|
338
|
-
Range.prototype._onDragEnd = function (event
|
301
|
+
Range.prototype._onDragEnd = function (event) {
|
339
302
|
// refuse to drag when we where pinching to prevent the timeline make a jump
|
340
303
|
// when releasing the fingers in opposite order from the touch screen
|
341
304
|
if (touchParams.ignore) return;
|
342
305
|
|
343
306
|
// TODO: reckon with option movable
|
344
307
|
|
345
|
-
if (
|
346
|
-
|
308
|
+
if (this.parent.frame) {
|
309
|
+
this.parent.frame.style.cursor = 'auto';
|
347
310
|
}
|
348
311
|
|
349
312
|
// fire a rangechanged event
|
350
313
|
this.emit('rangechanged', {
|
351
|
-
start: this.start,
|
352
|
-
end: this.end
|
314
|
+
start: new Date(this.start),
|
315
|
+
end: new Date(this.end)
|
353
316
|
});
|
354
317
|
};
|
355
318
|
|
@@ -357,13 +320,9 @@ Range.prototype._onDragEnd = function (event, component) {
|
|
357
320
|
* Event handler for mouse wheel event, used to zoom
|
358
321
|
* Code from http://adomas.org/javascript-mouse-wheel/
|
359
322
|
* @param {Event} event
|
360
|
-
* @param {Component} component
|
361
|
-
* @param {String} direction 'horizontal' or 'vertical'
|
362
323
|
* @private
|
363
324
|
*/
|
364
|
-
Range.prototype._onMouseWheel = function(event
|
365
|
-
validateDirection(direction);
|
366
|
-
|
325
|
+
Range.prototype._onMouseWheel = function(event) {
|
367
326
|
// TODO: reckon with option zoomable
|
368
327
|
|
369
328
|
// retrieve delta
|
@@ -394,8 +353,8 @@ Range.prototype._onMouseWheel = function(event, component, direction) {
|
|
394
353
|
|
395
354
|
// calculate center, the date to zoom around
|
396
355
|
var gesture = util.fakeGesture(this, event),
|
397
|
-
pointer = getPointer(gesture.center,
|
398
|
-
pointerDate = this._pointerToDate(
|
356
|
+
pointer = getPointer(gesture.center, this.parent.frame),
|
357
|
+
pointerDate = this._pointerToDate(pointer);
|
399
358
|
|
400
359
|
this.zoom(scale, pointerDate);
|
401
360
|
}
|
@@ -434,24 +393,23 @@ Range.prototype._onHold = function () {
|
|
434
393
|
/**
|
435
394
|
* Handle pinch event
|
436
395
|
* @param {Event} event
|
437
|
-
* @param {Component} component
|
438
|
-
* @param {String} direction 'horizontal' or 'vertical'
|
439
396
|
* @private
|
440
397
|
*/
|
441
|
-
Range.prototype._onPinch = function (event
|
398
|
+
Range.prototype._onPinch = function (event) {
|
399
|
+
var direction = this.options.direction;
|
442
400
|
touchParams.ignore = true;
|
443
401
|
|
444
402
|
// TODO: reckon with option zoomable
|
445
403
|
|
446
404
|
if (event.gesture.touches.length > 1) {
|
447
405
|
if (!touchParams.center) {
|
448
|
-
touchParams.center = getPointer(event.gesture.center,
|
406
|
+
touchParams.center = getPointer(event.gesture.center, this.parent.frame);
|
449
407
|
}
|
450
408
|
|
451
409
|
var scale = 1 / event.gesture.scale,
|
452
|
-
initDate = this._pointerToDate(
|
453
|
-
center = getPointer(event.gesture.center,
|
454
|
-
date = this._pointerToDate(
|
410
|
+
initDate = this._pointerToDate(touchParams.center),
|
411
|
+
center = getPointer(event.gesture.center, this.parent.frame),
|
412
|
+
date = this._pointerToDate(this.parent, center),
|
455
413
|
delta = date - initDate; // TODO: utilize delta
|
456
414
|
|
457
415
|
// calculate new start and end
|
@@ -465,21 +423,23 @@ Range.prototype._onPinch = function (event, component, direction) {
|
|
465
423
|
|
466
424
|
/**
|
467
425
|
* Helper function to calculate the center date for zooming
|
468
|
-
* @param {Component} component
|
469
426
|
* @param {{x: Number, y: Number}} pointer
|
470
|
-
* @param {String} direction 'horizontal' or 'vertical'
|
471
427
|
* @return {number} date
|
472
428
|
* @private
|
473
429
|
*/
|
474
|
-
Range.prototype._pointerToDate = function (
|
430
|
+
Range.prototype._pointerToDate = function (pointer) {
|
475
431
|
var conversion;
|
432
|
+
var direction = this.options.direction;
|
433
|
+
|
434
|
+
validateDirection(direction);
|
435
|
+
|
476
436
|
if (direction == 'horizontal') {
|
477
|
-
var width =
|
437
|
+
var width = this.parent.width;
|
478
438
|
conversion = this.conversion(width);
|
479
439
|
return pointer.x / conversion.scale + conversion.offset;
|
480
440
|
}
|
481
441
|
else {
|
482
|
-
var height =
|
442
|
+
var height = this.parent.height;
|
483
443
|
conversion = this.conversion(height);
|
484
444
|
return pointer.y / conversion.scale + conversion.offset;
|
485
445
|
}
|
@@ -6,12 +6,24 @@
|
|
6
6
|
* @constructor
|
7
7
|
*/
|
8
8
|
function Timeline (container, items, options) {
|
9
|
+
// validate arguments
|
10
|
+
if (!container) throw new Error('No container element provided');
|
11
|
+
|
9
12
|
var me = this;
|
10
13
|
var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
|
11
14
|
this.options = {
|
12
15
|
orientation: 'bottom',
|
16
|
+
direction: 'horizontal', // 'horizontal' or 'vertical'
|
13
17
|
autoResize: true,
|
14
|
-
|
18
|
+
stack: true,
|
19
|
+
|
20
|
+
editable: {
|
21
|
+
updateTime: false,
|
22
|
+
updateGroup: false,
|
23
|
+
add: false,
|
24
|
+
remove: false
|
25
|
+
},
|
26
|
+
|
15
27
|
selectable: true,
|
16
28
|
snap: null, // will be specified after timeaxis is created
|
17
29
|
|
@@ -27,6 +39,14 @@ function Timeline (container, items, options) {
|
|
27
39
|
showCurrentTime: false,
|
28
40
|
showCustomTime: false,
|
29
41
|
|
42
|
+
type: 'box',
|
43
|
+
align: 'center',
|
44
|
+
margin: {
|
45
|
+
axis: 20,
|
46
|
+
item: 10
|
47
|
+
},
|
48
|
+
padding: 5,
|
49
|
+
|
30
50
|
onAdd: function (item, callback) {
|
31
51
|
callback(item);
|
32
52
|
},
|
@@ -38,112 +58,205 @@ function Timeline (container, items, options) {
|
|
38
58
|
},
|
39
59
|
onRemove: function (item, callback) {
|
40
60
|
callback(item);
|
41
|
-
}
|
42
|
-
};
|
61
|
+
},
|
43
62
|
|
44
|
-
|
45
|
-
|
63
|
+
toScreen: me._toScreen.bind(me),
|
64
|
+
toTime: me._toTime.bind(me)
|
65
|
+
};
|
46
66
|
|
47
67
|
// root panel
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
// auto height
|
60
|
-
return (me.timeaxis.height + me.content.height) + 'px';
|
68
|
+
var rootOptions = util.extend(Object.create(this.options), {
|
69
|
+
height: function () {
|
70
|
+
if (me.options.height) {
|
71
|
+
// fixed height
|
72
|
+
return me.options.height;
|
73
|
+
}
|
74
|
+
else {
|
75
|
+
// auto height
|
76
|
+
// TODO: implement a css based solution to automatically have the right hight
|
77
|
+
return (me.timeAxis.height + me.contentPanel.height) + 'px';
|
78
|
+
}
|
61
79
|
}
|
62
|
-
};
|
80
|
+
});
|
63
81
|
this.rootPanel = new RootPanel(container, rootOptions);
|
64
|
-
this.controller.add(this.rootPanel);
|
65
82
|
|
66
83
|
// single select (or unselect) when tapping an item
|
67
|
-
this.
|
84
|
+
this.rootPanel.on('tap', this._onSelectItem.bind(this));
|
68
85
|
|
69
86
|
// multi select when holding mouse/touch, or on ctrl+click
|
70
|
-
this.
|
87
|
+
this.rootPanel.on('hold', this._onMultiSelectItem.bind(this));
|
71
88
|
|
72
89
|
// add item on doubletap
|
73
|
-
this.
|
90
|
+
this.rootPanel.on('doubletap', this._onAddItem.bind(this));
|
74
91
|
|
75
|
-
//
|
76
|
-
var
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
return me.
|
96
|
-
}
|
97
|
-
else {
|
98
|
-
return 0;
|
92
|
+
// side panel
|
93
|
+
var sideOptions = util.extend(Object.create(this.options), {
|
94
|
+
top: function () {
|
95
|
+
return (sideOptions.orientation == 'top') ? '0' : '';
|
96
|
+
},
|
97
|
+
bottom: function () {
|
98
|
+
return (sideOptions.orientation == 'top') ? '' : '0';
|
99
|
+
},
|
100
|
+
left: '0',
|
101
|
+
right: null,
|
102
|
+
height: '100%',
|
103
|
+
width: function () {
|
104
|
+
if (me.itemSet) {
|
105
|
+
return me.itemSet.getLabelsWidth();
|
106
|
+
}
|
107
|
+
else {
|
108
|
+
return 0;
|
109
|
+
}
|
110
|
+
},
|
111
|
+
className: function () {
|
112
|
+
return 'side' + (me.groupsData ? '' : ' hidden');
|
99
113
|
}
|
100
|
-
};
|
101
|
-
this.
|
102
|
-
this.
|
114
|
+
});
|
115
|
+
this.sidePanel = new Panel(sideOptions);
|
116
|
+
this.rootPanel.appendChild(this.sidePanel);
|
117
|
+
|
118
|
+
// main panel (contains time axis and itemsets)
|
119
|
+
var mainOptions = util.extend(Object.create(this.options), {
|
120
|
+
left: function () {
|
121
|
+
// we align left to enable a smooth resizing of the window
|
122
|
+
return me.sidePanel.width;
|
123
|
+
},
|
124
|
+
right: null,
|
125
|
+
height: '100%',
|
126
|
+
width: function () {
|
127
|
+
return me.rootPanel.width - me.sidePanel.width;
|
128
|
+
},
|
129
|
+
className: 'main'
|
130
|
+
});
|
131
|
+
this.mainPanel = new Panel(mainOptions);
|
132
|
+
this.rootPanel.appendChild(this.mainPanel);
|
103
133
|
|
104
134
|
// range
|
135
|
+
// TODO: move range inside rootPanel?
|
105
136
|
var rangeOptions = Object.create(this.options);
|
106
|
-
this.range = new Range(rangeOptions);
|
137
|
+
this.range = new Range(this.rootPanel, this.mainPanel, rangeOptions);
|
107
138
|
this.range.setRange(
|
108
139
|
now.clone().add('days', -3).valueOf(),
|
109
140
|
now.clone().add('days', 4).valueOf()
|
110
141
|
);
|
111
|
-
|
112
|
-
this.range.subscribe(this.controller, this.rootPanel, 'move', 'horizontal');
|
113
|
-
this.range.subscribe(this.controller, this.rootPanel, 'zoom', 'horizontal');
|
114
142
|
this.range.on('rangechange', function (properties) {
|
115
|
-
|
116
|
-
me.
|
117
|
-
me.controller.emit('request-reflow', force);
|
143
|
+
me.rootPanel.repaint();
|
144
|
+
me.emit('rangechange', properties);
|
118
145
|
});
|
119
146
|
this.range.on('rangechanged', function (properties) {
|
120
|
-
|
121
|
-
me.
|
122
|
-
|
147
|
+
me.rootPanel.repaint();
|
148
|
+
me.emit('rangechanged', properties);
|
149
|
+
});
|
150
|
+
|
151
|
+
// panel with time axis
|
152
|
+
var timeAxisOptions = util.extend(Object.create(rootOptions), {
|
153
|
+
range: this.range,
|
154
|
+
left: null,
|
155
|
+
top: null,
|
156
|
+
width: null,
|
157
|
+
height: null
|
158
|
+
});
|
159
|
+
this.timeAxis = new TimeAxis(timeAxisOptions);
|
160
|
+
this.timeAxis.setRange(this.range);
|
161
|
+
this.options.snap = this.timeAxis.snap.bind(this.timeAxis);
|
162
|
+
this.mainPanel.appendChild(this.timeAxis);
|
163
|
+
|
164
|
+
// content panel (contains itemset(s))
|
165
|
+
var contentOptions = util.extend(Object.create(this.options), {
|
166
|
+
top: function () {
|
167
|
+
return (me.options.orientation == 'top') ? (me.timeAxis.height + 'px') : '';
|
168
|
+
},
|
169
|
+
bottom: function () {
|
170
|
+
return (me.options.orientation == 'top') ? '' : (me.timeAxis.height + 'px');
|
171
|
+
},
|
172
|
+
left: null,
|
173
|
+
right: null,
|
174
|
+
height: null,
|
175
|
+
width: null,
|
176
|
+
className: 'content'
|
177
|
+
});
|
178
|
+
this.contentPanel = new Panel(contentOptions);
|
179
|
+
this.mainPanel.appendChild(this.contentPanel);
|
180
|
+
|
181
|
+
// content panel (contains the vertical lines of box items)
|
182
|
+
var backgroundOptions = util.extend(Object.create(this.options), {
|
183
|
+
top: function () {
|
184
|
+
return (me.options.orientation == 'top') ? (me.timeAxis.height + 'px') : '';
|
185
|
+
},
|
186
|
+
bottom: function () {
|
187
|
+
return (me.options.orientation == 'top') ? '' : (me.timeAxis.height + 'px');
|
188
|
+
},
|
189
|
+
left: null,
|
190
|
+
right: null,
|
191
|
+
height: function () {
|
192
|
+
return me.contentPanel.height;
|
193
|
+
},
|
194
|
+
width: null,
|
195
|
+
className: 'background'
|
196
|
+
});
|
197
|
+
this.backgroundPanel = new Panel(backgroundOptions);
|
198
|
+
this.mainPanel.insertBefore(this.backgroundPanel, this.contentPanel);
|
199
|
+
|
200
|
+
// panel with axis holding the dots of item boxes
|
201
|
+
var axisPanelOptions = util.extend(Object.create(rootOptions), {
|
202
|
+
left: 0,
|
203
|
+
top: function () {
|
204
|
+
return (me.options.orientation == 'top') ? (me.timeAxis.height + 'px') : '';
|
205
|
+
},
|
206
|
+
bottom: function () {
|
207
|
+
return (me.options.orientation == 'top') ? '' : (me.timeAxis.height + 'px');
|
208
|
+
},
|
209
|
+
width: '100%',
|
210
|
+
height: 0,
|
211
|
+
className: 'axis'
|
123
212
|
});
|
213
|
+
this.axisPanel = new Panel(axisPanelOptions);
|
214
|
+
this.mainPanel.appendChild(this.axisPanel);
|
124
215
|
|
125
|
-
//
|
126
|
-
var
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
216
|
+
// content panel (contains itemset(s))
|
217
|
+
var sideContentOptions = util.extend(Object.create(this.options), {
|
218
|
+
top: function () {
|
219
|
+
return (me.options.orientation == 'top') ? (me.timeAxis.height + 'px') : '';
|
220
|
+
},
|
221
|
+
bottom: function () {
|
222
|
+
return (me.options.orientation == 'top') ? '' : (me.timeAxis.height + 'px');
|
223
|
+
},
|
224
|
+
left: null,
|
225
|
+
right: null,
|
226
|
+
height: null,
|
227
|
+
width: null,
|
228
|
+
className: 'side-content'
|
229
|
+
});
|
230
|
+
this.sideContentPanel = new Panel(sideContentOptions);
|
231
|
+
this.sidePanel.appendChild(this.sideContentPanel);
|
136
232
|
|
137
233
|
// current time bar
|
138
|
-
|
139
|
-
this.
|
234
|
+
// Note: time bar will be attached in this.setOptions when selected
|
235
|
+
this.currentTime = new CurrentTime(this.range, rootOptions);
|
140
236
|
|
141
237
|
// custom time bar
|
142
|
-
|
143
|
-
this.
|
238
|
+
// Note: time bar will be attached in this.setOptions when selected
|
239
|
+
this.customTime = new CustomTime(rootOptions);
|
240
|
+
this.customTime.on('timechange', function (time) {
|
241
|
+
me.emit('timechange', time);
|
242
|
+
});
|
243
|
+
this.customTime.on('timechanged', function (time) {
|
244
|
+
me.emit('timechanged', time);
|
245
|
+
});
|
144
246
|
|
145
|
-
//
|
146
|
-
this.
|
247
|
+
// itemset containing items and groups
|
248
|
+
var itemOptions = util.extend(Object.create(this.options), {
|
249
|
+
left: null,
|
250
|
+
right: null,
|
251
|
+
top: null,
|
252
|
+
bottom: null,
|
253
|
+
width: null,
|
254
|
+
height: null
|
255
|
+
});
|
256
|
+
this.itemSet = new ItemSet(this.backgroundPanel, this.axisPanel, this.sideContentPanel, itemOptions);
|
257
|
+
this.itemSet.setRange(this.range);
|
258
|
+
this.itemSet.on('change', me.rootPanel.repaint.bind(me.rootPanel));
|
259
|
+
this.contentPanel.appendChild(this.itemSet);
|
147
260
|
|
148
261
|
this.itemsData = null; // DataSet
|
149
262
|
this.groupsData = null; // DataSet
|
@@ -153,30 +266,14 @@ function Timeline (container, items, options) {
|
|
153
266
|
this.setOptions(options);
|
154
267
|
}
|
155
268
|
|
156
|
-
// create itemset
|
269
|
+
// create itemset
|
157
270
|
if (items) {
|
158
271
|
this.setItems(items);
|
159
272
|
}
|
160
273
|
}
|
161
274
|
|
162
|
-
|
163
|
-
|
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
|
-
};
|
275
|
+
// turn Timeline into an event emitter
|
276
|
+
Emitter(Timeline.prototype);
|
180
277
|
|
181
278
|
/**
|
182
279
|
* Set options
|
@@ -185,6 +282,17 @@ Timeline.prototype.off = function off (event, callback) {
|
|
185
282
|
Timeline.prototype.setOptions = function (options) {
|
186
283
|
util.extend(this.options, options);
|
187
284
|
|
285
|
+
if ('editable' in options) {
|
286
|
+
var isBoolean = typeof options.editable === 'boolean';
|
287
|
+
|
288
|
+
this.options.editable = {
|
289
|
+
updateTime: isBoolean ? options.editable : (options.editable.updateTime || false),
|
290
|
+
updateGroup: isBoolean ? options.editable : (options.editable.updateGroup || false),
|
291
|
+
add: isBoolean ? options.editable : (options.editable.add || false),
|
292
|
+
remove: isBoolean ? options.editable : (options.editable.remove || false)
|
293
|
+
};
|
294
|
+
}
|
295
|
+
|
188
296
|
// force update of range (apply new min/max etc.)
|
189
297
|
// both start and end are optional
|
190
298
|
this.range.setRange(options.start, options.end);
|
@@ -200,6 +308,9 @@ Timeline.prototype.setOptions = function (options) {
|
|
200
308
|
}
|
201
309
|
}
|
202
310
|
|
311
|
+
// force the itemSet to refresh: options like orientation and margins may be changed
|
312
|
+
this.itemSet.markDirty();
|
313
|
+
|
203
314
|
// validate the callback functions
|
204
315
|
var validateCallback = (function (fn) {
|
205
316
|
if (!(this.options[fn] instanceof Function) || this.options[fn].length != 2) {
|
@@ -208,8 +319,39 @@ Timeline.prototype.setOptions = function (options) {
|
|
208
319
|
}).bind(this);
|
209
320
|
['onAdd', 'onUpdate', 'onRemove', 'onMove'].forEach(validateCallback);
|
210
321
|
|
211
|
-
|
212
|
-
this.
|
322
|
+
// add/remove the current time bar
|
323
|
+
if (this.options.showCurrentTime) {
|
324
|
+
if (!this.mainPanel.hasChild(this.currentTime)) {
|
325
|
+
this.mainPanel.appendChild(this.currentTime);
|
326
|
+
this.currentTime.start();
|
327
|
+
}
|
328
|
+
}
|
329
|
+
else {
|
330
|
+
if (this.mainPanel.hasChild(this.currentTime)) {
|
331
|
+
this.currentTime.stop();
|
332
|
+
this.mainPanel.removeChild(this.currentTime);
|
333
|
+
}
|
334
|
+
}
|
335
|
+
|
336
|
+
// add/remove the custom time bar
|
337
|
+
if (this.options.showCustomTime) {
|
338
|
+
if (!this.mainPanel.hasChild(this.customTime)) {
|
339
|
+
this.mainPanel.appendChild(this.customTime);
|
340
|
+
}
|
341
|
+
}
|
342
|
+
else {
|
343
|
+
if (this.mainPanel.hasChild(this.customTime)) {
|
344
|
+
this.mainPanel.removeChild(this.customTime);
|
345
|
+
}
|
346
|
+
}
|
347
|
+
|
348
|
+
// TODO: remove deprecation error one day (deprecated since version 0.8.0)
|
349
|
+
if (options && options.order) {
|
350
|
+
throw new Error('Option order is deprecated. There is no replacement for this feature.');
|
351
|
+
}
|
352
|
+
|
353
|
+
// repaint everything
|
354
|
+
this.rootPanel.repaint();
|
213
355
|
};
|
214
356
|
|
215
357
|
/**
|
@@ -217,11 +359,11 @@ Timeline.prototype.setOptions = function (options) {
|
|
217
359
|
* @param {Date} time
|
218
360
|
*/
|
219
361
|
Timeline.prototype.setCustomTime = function (time) {
|
220
|
-
if (!this.
|
362
|
+
if (!this.customTime) {
|
221
363
|
throw new Error('Cannot get custom time: Custom time bar is not enabled');
|
222
364
|
}
|
223
365
|
|
224
|
-
this.
|
366
|
+
this.customTime.setCustomTime(time);
|
225
367
|
};
|
226
368
|
|
227
369
|
/**
|
@@ -229,11 +371,11 @@ Timeline.prototype.setCustomTime = function (time) {
|
|
229
371
|
* @return {Date} customTime
|
230
372
|
*/
|
231
373
|
Timeline.prototype.getCustomTime = function() {
|
232
|
-
if (!this.
|
374
|
+
if (!this.customTime) {
|
233
375
|
throw new Error('Cannot get custom time: Custom time bar is not enabled');
|
234
376
|
}
|
235
377
|
|
236
|
-
return this.
|
378
|
+
return this.customTime.getCustomTime();
|
237
379
|
};
|
238
380
|
|
239
381
|
/**
|
@@ -248,52 +390,30 @@ Timeline.prototype.setItems = function(items) {
|
|
248
390
|
if (!items) {
|
249
391
|
newDataSet = null;
|
250
392
|
}
|
251
|
-
else if (items instanceof DataSet) {
|
393
|
+
else if (items instanceof DataSet || items instanceof DataView) {
|
252
394
|
newDataSet = items;
|
253
395
|
}
|
254
|
-
|
255
|
-
|
396
|
+
else {
|
397
|
+
// turn an array into a dataset
|
398
|
+
newDataSet = new DataSet(items, {
|
256
399
|
convert: {
|
257
400
|
start: 'Date',
|
258
401
|
end: 'Date'
|
259
402
|
}
|
260
403
|
});
|
261
|
-
newDataSet.add(items);
|
262
404
|
}
|
263
405
|
|
264
406
|
// set items
|
265
407
|
this.itemsData = newDataSet;
|
266
|
-
this.
|
408
|
+
this.itemSet.setItems(newDataSet);
|
267
409
|
|
268
410
|
if (initialLoad && (this.options.start == undefined || this.options.end == undefined)) {
|
269
|
-
|
270
|
-
var dataRange = this.getItemRange();
|
271
|
-
|
272
|
-
// add 5% space on both sides
|
273
|
-
var start = dataRange.min;
|
274
|
-
var end = dataRange.max;
|
275
|
-
if (start != null && end != null) {
|
276
|
-
var interval = (end.valueOf() - start.valueOf());
|
277
|
-
if (interval <= 0) {
|
278
|
-
// prevent an empty interval
|
279
|
-
interval = 24 * 60 * 60 * 1000; // 1 day
|
280
|
-
}
|
281
|
-
start = new Date(start.valueOf() - interval * 0.05);
|
282
|
-
end = new Date(end.valueOf() + interval * 0.05);
|
283
|
-
}
|
411
|
+
this.fit();
|
284
412
|
|
285
|
-
|
286
|
-
|
287
|
-
start = util.convert(this.options.start, 'Date');
|
288
|
-
}
|
289
|
-
if (this.options.end != undefined) {
|
290
|
-
end = util.convert(this.options.end, 'Date');
|
291
|
-
}
|
413
|
+
var start = (this.options.start != undefined) ? util.convert(this.options.start, 'Date') : null;
|
414
|
+
var end = (this.options.end != undefined) ? util.convert(this.options.end, 'Date') : null;
|
292
415
|
|
293
|
-
|
294
|
-
if (start != null || end != null) {
|
295
|
-
this.range.setRange(start, end);
|
296
|
-
}
|
416
|
+
this.setWindow(start, end);
|
297
417
|
}
|
298
418
|
};
|
299
419
|
|
@@ -301,77 +421,50 @@ Timeline.prototype.setItems = function(items) {
|
|
301
421
|
* Set groups
|
302
422
|
* @param {vis.DataSet | Array | google.visualization.DataTable} groups
|
303
423
|
*/
|
304
|
-
Timeline.prototype.setGroups = function(groups) {
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
if (
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
if (this.content.setGroups) {
|
318
|
-
this.content.setGroups(); // disconnect from groups
|
319
|
-
}
|
320
|
-
this.controller.remove(this.content);
|
321
|
-
}
|
424
|
+
Timeline.prototype.setGroups = function setGroups(groups) {
|
425
|
+
// convert to type DataSet when needed
|
426
|
+
var newDataSet;
|
427
|
+
if (!groups) {
|
428
|
+
newDataSet = null;
|
429
|
+
}
|
430
|
+
else if (groups instanceof DataSet || groups instanceof DataView) {
|
431
|
+
newDataSet = groups;
|
432
|
+
}
|
433
|
+
else {
|
434
|
+
// turn an array into a dataset
|
435
|
+
newDataSet = new DataSet(groups);
|
436
|
+
}
|
322
437
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
top: function () {
|
327
|
-
if (me.options.orientation == 'top') {
|
328
|
-
return me.timeaxis.height;
|
329
|
-
}
|
330
|
-
else {
|
331
|
-
return me.itemPanel.height - me.timeaxis.height - me.content.height;
|
332
|
-
}
|
333
|
-
},
|
334
|
-
left: null,
|
335
|
-
width: '100%',
|
336
|
-
height: function () {
|
337
|
-
if (me.options.height) {
|
338
|
-
// fixed height
|
339
|
-
return me.itemPanel.height - me.timeaxis.height;
|
340
|
-
}
|
341
|
-
else {
|
342
|
-
// auto height
|
343
|
-
return null;
|
344
|
-
}
|
345
|
-
},
|
346
|
-
maxHeight: function () {
|
347
|
-
// TODO: change maxHeight to be a css string like '100%' or '300px'
|
348
|
-
if (me.options.maxHeight) {
|
349
|
-
if (!util.isNumber(me.options.maxHeight)) {
|
350
|
-
throw new TypeError('Number expected for property maxHeight');
|
351
|
-
}
|
352
|
-
return me.options.maxHeight - me.timeaxis.height;
|
353
|
-
}
|
354
|
-
else {
|
355
|
-
return null;
|
356
|
-
}
|
357
|
-
},
|
358
|
-
labelContainer: function () {
|
359
|
-
return me.labelPanel.getContainer();
|
360
|
-
}
|
361
|
-
});
|
438
|
+
this.groupsData = newDataSet;
|
439
|
+
this.itemSet.setGroups(newDataSet);
|
440
|
+
};
|
362
441
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
442
|
+
/**
|
443
|
+
* Set Timeline window such that it fits all items
|
444
|
+
*/
|
445
|
+
Timeline.prototype.fit = function fit() {
|
446
|
+
// apply the data range as range
|
447
|
+
var dataRange = this.getItemRange();
|
448
|
+
|
449
|
+
// add 5% space on both sides
|
450
|
+
var start = dataRange.min;
|
451
|
+
var end = dataRange.max;
|
452
|
+
if (start != null && end != null) {
|
453
|
+
var interval = (end.valueOf() - start.valueOf());
|
454
|
+
if (interval <= 0) {
|
455
|
+
// prevent an empty interval
|
456
|
+
interval = 24 * 60 * 60 * 1000; // 1 day
|
372
457
|
}
|
373
|
-
|
458
|
+
start = new Date(start.valueOf() - interval * 0.05);
|
459
|
+
end = new Date(end.valueOf() + interval * 0.05);
|
460
|
+
}
|
461
|
+
|
462
|
+
// skip range set if there is no start and end date
|
463
|
+
if (start === null && end === null) {
|
464
|
+
return;
|
374
465
|
}
|
466
|
+
|
467
|
+
this.range.setRange(start, end);
|
375
468
|
};
|
376
469
|
|
377
470
|
/**
|
@@ -421,7 +514,7 @@ Timeline.prototype.getItemRange = function getItemRange() {
|
|
421
514
|
* unselected.
|
422
515
|
*/
|
423
516
|
Timeline.prototype.setSelection = function setSelection (ids) {
|
424
|
-
|
517
|
+
this.itemSet.setSelection(ids);
|
425
518
|
};
|
426
519
|
|
427
520
|
/**
|
@@ -429,17 +522,30 @@ Timeline.prototype.setSelection = function setSelection (ids) {
|
|
429
522
|
* @return {Array} ids The ids of the selected items
|
430
523
|
*/
|
431
524
|
Timeline.prototype.getSelection = function getSelection() {
|
432
|
-
return this.
|
525
|
+
return this.itemSet.getSelection();
|
433
526
|
};
|
434
527
|
|
435
528
|
/**
|
436
529
|
* Set the visible window. Both parameters are optional, you can change only
|
437
|
-
* start or only end.
|
530
|
+
* start or only end. Syntax:
|
531
|
+
*
|
532
|
+
* TimeLine.setWindow(start, end)
|
533
|
+
* TimeLine.setWindow(range)
|
534
|
+
*
|
535
|
+
* Where start and end can be a Date, number, or string, and range is an
|
536
|
+
* object with properties start and end.
|
537
|
+
*
|
438
538
|
* @param {Date | Number | String} [start] Start date of visible window
|
439
539
|
* @param {Date | Number | String} [end] End date of visible window
|
440
540
|
*/
|
441
541
|
Timeline.prototype.setWindow = function setWindow(start, end) {
|
442
|
-
|
542
|
+
if (arguments.length == 1) {
|
543
|
+
var range = arguments[0];
|
544
|
+
this.range.setRange(range.start, range.end);
|
545
|
+
}
|
546
|
+
else {
|
547
|
+
this.range.setRange(start, end);
|
548
|
+
}
|
443
549
|
};
|
444
550
|
|
445
551
|
/**
|
@@ -470,14 +576,20 @@ Timeline.prototype._onSelectItem = function (event) {
|
|
470
576
|
return;
|
471
577
|
}
|
472
578
|
|
473
|
-
var
|
579
|
+
var oldSelection = this.getSelection();
|
474
580
|
|
581
|
+
var item = ItemSet.itemFromTarget(event);
|
475
582
|
var selection = item ? [item.id] : [];
|
476
583
|
this.setSelection(selection);
|
477
584
|
|
478
|
-
this.
|
479
|
-
|
480
|
-
|
585
|
+
var newSelection = this.getSelection();
|
586
|
+
|
587
|
+
// if selection is changed, emit a select event
|
588
|
+
if (!util.equalArray(oldSelection, newSelection)) {
|
589
|
+
this.emit('select', {
|
590
|
+
items: this.getSelection()
|
591
|
+
});
|
592
|
+
}
|
481
593
|
|
482
594
|
event.stopPropagation();
|
483
595
|
};
|
@@ -489,7 +601,7 @@ Timeline.prototype._onSelectItem = function (event) {
|
|
489
601
|
*/
|
490
602
|
Timeline.prototype._onAddItem = function (event) {
|
491
603
|
if (!this.options.selectable) return;
|
492
|
-
if (!this.options.editable) return;
|
604
|
+
if (!this.options.editable.add) return;
|
493
605
|
|
494
606
|
var me = this,
|
495
607
|
item = ItemSet.itemFromTarget(event);
|
@@ -507,17 +619,17 @@ Timeline.prototype._onAddItem = function (event) {
|
|
507
619
|
}
|
508
620
|
else {
|
509
621
|
// add item
|
510
|
-
var xAbs = vis.util.getAbsoluteLeft(this.
|
622
|
+
var xAbs = vis.util.getAbsoluteLeft(this.contentPanel.frame);
|
511
623
|
var x = event.gesture.center.pageX - xAbs;
|
512
624
|
var newItem = {
|
513
|
-
start: this.
|
625
|
+
start: this.timeAxis.snap(this._toTime(x)),
|
514
626
|
content: 'new item'
|
515
627
|
};
|
516
628
|
|
517
629
|
var id = util.randomUUID();
|
518
630
|
newItem[this.itemsData.fieldId] = id;
|
519
631
|
|
520
|
-
var group =
|
632
|
+
var group = ItemSet.groupFromTarget(event);
|
521
633
|
if (group) {
|
522
634
|
newItem.group = group.groupId;
|
523
635
|
}
|
@@ -526,15 +638,7 @@ Timeline.prototype._onAddItem = function (event) {
|
|
526
638
|
this.options.onAdd(newItem, function (item) {
|
527
639
|
if (item) {
|
528
640
|
me.itemsData.add(newItem);
|
529
|
-
|
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));
|
641
|
+
// TODO: need to trigger a repaint?
|
538
642
|
}
|
539
643
|
});
|
540
644
|
}
|
@@ -566,7 +670,7 @@ Timeline.prototype._onMultiSelectItem = function (event) {
|
|
566
670
|
}
|
567
671
|
this.setSelection(selection);
|
568
672
|
|
569
|
-
this.
|
673
|
+
this.emit('select', {
|
570
674
|
items: this.getSelection()
|
571
675
|
});
|
572
676
|
|
@@ -581,7 +685,7 @@ Timeline.prototype._onMultiSelectItem = function (event) {
|
|
581
685
|
* @private
|
582
686
|
*/
|
583
687
|
Timeline.prototype._toTime = function _toTime(x) {
|
584
|
-
var conversion = this.range.conversion(this.
|
688
|
+
var conversion = this.range.conversion(this.mainPanel.width);
|
585
689
|
return new Date(x / conversion.scale + conversion.offset);
|
586
690
|
};
|
587
691
|
|
@@ -593,6 +697,6 @@ Timeline.prototype._toTime = function _toTime(x) {
|
|
593
697
|
* @private
|
594
698
|
*/
|
595
699
|
Timeline.prototype._toScreen = function _toScreen(time) {
|
596
|
-
var conversion = this.range.conversion(this.
|
700
|
+
var conversion = this.range.conversion(this.mainPanel.width);
|
597
701
|
return (time.valueOf() - conversion.offset) * conversion.scale;
|
598
702
|
};
|