vis-rails 0.0.6 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
};
|