vis-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.gitmodules +3 -0
  4. data/.project +11 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +202 -0
  7. data/README.md +29 -0
  8. data/Rakefile +1 -0
  9. data/lib/vis/rails/engine.rb +6 -0
  10. data/lib/vis/rails/version.rb +5 -0
  11. data/lib/vis/rails.rb +7 -0
  12. data/vendor/assets/javascripts/vis.js +1 -0
  13. data/vendor/assets/stylesheets/vis.css +3 -0
  14. data/vendor/assets/vis/DataSet.js +936 -0
  15. data/vendor/assets/vis/DataView.js +281 -0
  16. data/vendor/assets/vis/EventBus.js +89 -0
  17. data/vendor/assets/vis/events.js +116 -0
  18. data/vendor/assets/vis/graph/ClusterMixin.js +1019 -0
  19. data/vendor/assets/vis/graph/Edge.js +620 -0
  20. data/vendor/assets/vis/graph/Graph.js +2111 -0
  21. data/vendor/assets/vis/graph/Groups.js +80 -0
  22. data/vendor/assets/vis/graph/Images.js +41 -0
  23. data/vendor/assets/vis/graph/NavigationMixin.js +245 -0
  24. data/vendor/assets/vis/graph/Node.js +978 -0
  25. data/vendor/assets/vis/graph/Popup.js +105 -0
  26. data/vendor/assets/vis/graph/SectorsMixin.js +547 -0
  27. data/vendor/assets/vis/graph/SelectionMixin.js +515 -0
  28. data/vendor/assets/vis/graph/dotparser.js +829 -0
  29. data/vendor/assets/vis/graph/img/downarrow.png +0 -0
  30. data/vendor/assets/vis/graph/img/leftarrow.png +0 -0
  31. data/vendor/assets/vis/graph/img/minus.png +0 -0
  32. data/vendor/assets/vis/graph/img/plus.png +0 -0
  33. data/vendor/assets/vis/graph/img/rightarrow.png +0 -0
  34. data/vendor/assets/vis/graph/img/uparrow.png +0 -0
  35. data/vendor/assets/vis/graph/img/zoomExtends.png +0 -0
  36. data/vendor/assets/vis/graph/shapes.js +225 -0
  37. data/vendor/assets/vis/module/exports.js +68 -0
  38. data/vendor/assets/vis/module/header.js +24 -0
  39. data/vendor/assets/vis/module/imports.js +32 -0
  40. data/vendor/assets/vis/shim.js +252 -0
  41. data/vendor/assets/vis/timeline/Controller.js +172 -0
  42. data/vendor/assets/vis/timeline/Range.js +553 -0
  43. data/vendor/assets/vis/timeline/Stack.js +192 -0
  44. data/vendor/assets/vis/timeline/TimeStep.js +449 -0
  45. data/vendor/assets/vis/timeline/Timeline.js +476 -0
  46. data/vendor/assets/vis/timeline/component/Component.js +148 -0
  47. data/vendor/assets/vis/timeline/component/ContentPanel.js +113 -0
  48. data/vendor/assets/vis/timeline/component/CurrentTime.js +101 -0
  49. data/vendor/assets/vis/timeline/component/CustomTime.js +255 -0
  50. data/vendor/assets/vis/timeline/component/Group.js +129 -0
  51. data/vendor/assets/vis/timeline/component/GroupSet.js +546 -0
  52. data/vendor/assets/vis/timeline/component/ItemSet.js +612 -0
  53. data/vendor/assets/vis/timeline/component/Panel.js +112 -0
  54. data/vendor/assets/vis/timeline/component/RootPanel.js +215 -0
  55. data/vendor/assets/vis/timeline/component/TimeAxis.js +522 -0
  56. data/vendor/assets/vis/timeline/component/css/currenttime.css +5 -0
  57. data/vendor/assets/vis/timeline/component/css/customtime.css +6 -0
  58. data/vendor/assets/vis/timeline/component/css/groupset.css +59 -0
  59. data/vendor/assets/vis/timeline/component/css/item.css +93 -0
  60. data/vendor/assets/vis/timeline/component/css/itemset.css +17 -0
  61. data/vendor/assets/vis/timeline/component/css/panel.css +14 -0
  62. data/vendor/assets/vis/timeline/component/css/timeaxis.css +41 -0
  63. data/vendor/assets/vis/timeline/component/css/timeline.css +2 -0
  64. data/vendor/assets/vis/timeline/component/item/Item.js +81 -0
  65. data/vendor/assets/vis/timeline/component/item/ItemBox.js +302 -0
  66. data/vendor/assets/vis/timeline/component/item/ItemPoint.js +237 -0
  67. data/vendor/assets/vis/timeline/component/item/ItemRange.js +251 -0
  68. data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +91 -0
  69. data/vendor/assets/vis/util.js +673 -0
  70. data/vis-rails.gemspec +47 -0
  71. metadata +142 -0
@@ -0,0 +1,172 @@
1
+ /**
2
+ * @constructor Controller
3
+ *
4
+ * A Controller controls the reflows and repaints of all visual components
5
+ */
6
+ function Controller () {
7
+ this.id = util.randomUUID();
8
+ this.components = {};
9
+
10
+ this.repaintTimer = undefined;
11
+ this.reflowTimer = undefined;
12
+ }
13
+
14
+ /**
15
+ * Add a component to the controller
16
+ * @param {Component} component
17
+ */
18
+ Controller.prototype.add = function add(component) {
19
+ // validate the component
20
+ if (component.id == undefined) {
21
+ throw new Error('Component has no field id');
22
+ }
23
+ if (!(component instanceof Component) && !(component instanceof Controller)) {
24
+ throw new TypeError('Component must be an instance of ' +
25
+ 'prototype Component or Controller');
26
+ }
27
+
28
+ // add the component
29
+ component.controller = this;
30
+ this.components[component.id] = component;
31
+ };
32
+
33
+ /**
34
+ * Remove a component from the controller
35
+ * @param {Component | String} component
36
+ */
37
+ Controller.prototype.remove = function remove(component) {
38
+ var id;
39
+ for (id in this.components) {
40
+ if (this.components.hasOwnProperty(id)) {
41
+ if (id == component || this.components[id] == component) {
42
+ break;
43
+ }
44
+ }
45
+ }
46
+
47
+ if (id) {
48
+ delete this.components[id];
49
+ }
50
+ };
51
+
52
+ /**
53
+ * Request a reflow. The controller will schedule a reflow
54
+ * @param {Boolean} [force] If true, an immediate reflow is forced. Default
55
+ * is false.
56
+ */
57
+ Controller.prototype.requestReflow = function requestReflow(force) {
58
+ if (force) {
59
+ this.reflow();
60
+ }
61
+ else {
62
+ if (!this.reflowTimer) {
63
+ var me = this;
64
+ this.reflowTimer = setTimeout(function () {
65
+ me.reflowTimer = undefined;
66
+ me.reflow();
67
+ }, 0);
68
+ }
69
+ }
70
+ };
71
+
72
+ /**
73
+ * Request a repaint. The controller will schedule a repaint
74
+ * @param {Boolean} [force] If true, an immediate repaint is forced. Default
75
+ * is false.
76
+ */
77
+ Controller.prototype.requestRepaint = function requestRepaint(force) {
78
+ if (force) {
79
+ this.repaint();
80
+ }
81
+ else {
82
+ if (!this.repaintTimer) {
83
+ var me = this;
84
+ this.repaintTimer = setTimeout(function () {
85
+ me.repaintTimer = undefined;
86
+ me.repaint();
87
+ }, 0);
88
+ }
89
+ }
90
+ };
91
+
92
+ /**
93
+ * Repaint all components
94
+ */
95
+ Controller.prototype.repaint = function repaint() {
96
+ var changed = false;
97
+
98
+ // cancel any running repaint request
99
+ if (this.repaintTimer) {
100
+ clearTimeout(this.repaintTimer);
101
+ this.repaintTimer = undefined;
102
+ }
103
+
104
+ var done = {};
105
+
106
+ function repaint(component, id) {
107
+ if (!(id in done)) {
108
+ // first repaint the components on which this component is dependent
109
+ if (component.depends) {
110
+ component.depends.forEach(function (dep) {
111
+ repaint(dep, dep.id);
112
+ });
113
+ }
114
+ if (component.parent) {
115
+ repaint(component.parent, component.parent.id);
116
+ }
117
+
118
+ // repaint the component itself and mark as done
119
+ changed = component.repaint() || changed;
120
+ done[id] = true;
121
+ }
122
+ }
123
+
124
+ util.forEach(this.components, repaint);
125
+
126
+ // immediately reflow when needed
127
+ if (changed) {
128
+ this.reflow();
129
+ }
130
+ // TODO: limit the number of nested reflows/repaints, prevent loop
131
+ };
132
+
133
+ /**
134
+ * Reflow all components
135
+ */
136
+ Controller.prototype.reflow = function reflow() {
137
+ var resized = false;
138
+
139
+ // cancel any running repaint request
140
+ if (this.reflowTimer) {
141
+ clearTimeout(this.reflowTimer);
142
+ this.reflowTimer = undefined;
143
+ }
144
+
145
+ var done = {};
146
+
147
+ function reflow(component, id) {
148
+ if (!(id in done)) {
149
+ // first reflow the components on which this component is dependent
150
+ if (component.depends) {
151
+ component.depends.forEach(function (dep) {
152
+ reflow(dep, dep.id);
153
+ });
154
+ }
155
+ if (component.parent) {
156
+ reflow(component.parent, component.parent.id);
157
+ }
158
+
159
+ // reflow the component itself and mark as done
160
+ resized = component.reflow() || resized;
161
+ done[id] = true;
162
+ }
163
+ }
164
+
165
+ util.forEach(this.components, reflow);
166
+
167
+ // immediately repaint when needed
168
+ if (resized) {
169
+ this.repaint();
170
+ }
171
+ // TODO: limit the number of nested reflows/repaints, prevent loop
172
+ };
@@ -0,0 +1,553 @@
1
+ /**
2
+ * @constructor Range
3
+ * A Range controls a numeric range with a start and end value.
4
+ * The Range adjusts the range based on mouse events or programmatic changes,
5
+ * and triggers events when the range is changing or has been changed.
6
+ * @param {Object} [options] See description at Range.setOptions
7
+ * @extends Controller
8
+ */
9
+ function Range(options) {
10
+ this.id = util.randomUUID();
11
+ this.start = null; // Number
12
+ this.end = null; // Number
13
+
14
+ this.options = options || {};
15
+
16
+ this.setOptions(options);
17
+ }
18
+
19
+ /**
20
+ * Set options for the range controller
21
+ * @param {Object} options Available options:
22
+ * {Number} min Minimum value for start
23
+ * {Number} max Maximum value for end
24
+ * {Number} zoomMin Set a minimum value for
25
+ * (end - start).
26
+ * {Number} zoomMax Set a maximum value for
27
+ * (end - start).
28
+ */
29
+ Range.prototype.setOptions = function (options) {
30
+ util.extend(this.options, options);
31
+
32
+ // re-apply range with new limitations
33
+ if (this.start !== null && this.end !== null) {
34
+ this.setRange(this.start, this.end);
35
+ }
36
+ };
37
+
38
+ /**
39
+ * Test whether direction has a valid value
40
+ * @param {String} direction 'horizontal' or 'vertical'
41
+ */
42
+ function validateDirection (direction) {
43
+ if (direction != 'horizontal' && direction != 'vertical') {
44
+ throw new TypeError('Unknown direction "' + direction + '". ' +
45
+ 'Choose "horizontal" or "vertical".');
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Add listeners for mouse and touch events to the component
51
+ * @param {Component} component
52
+ * @param {String} event Available events: 'move', 'zoom'
53
+ * @param {String} direction Available directions: 'horizontal', 'vertical'
54
+ */
55
+ Range.prototype.subscribe = function (component, event, direction) {
56
+ var me = this;
57
+
58
+ if (event == 'move') {
59
+ // drag start listener
60
+ component.on('dragstart', function (event) {
61
+ me._onDragStart(event, component);
62
+ });
63
+
64
+ // drag listener
65
+ component.on('drag', function (event) {
66
+ me._onDrag(event, component, direction);
67
+ });
68
+
69
+ // drag end listener
70
+ component.on('dragend', function (event) {
71
+ me._onDragEnd(event, component);
72
+ });
73
+ }
74
+ else if (event == 'zoom') {
75
+ // mouse wheel
76
+ function mousewheel (event) {
77
+ me._onMouseWheel(event, component, direction);
78
+ }
79
+ component.on('mousewheel', mousewheel);
80
+ component.on('DOMMouseScroll', mousewheel); // For FF
81
+
82
+ // pinch
83
+ component.on('touch', function (event) {
84
+ me._onTouch();
85
+ });
86
+ component.on('pinch', function (event) {
87
+ me._onPinch(event, component, direction);
88
+ });
89
+ }
90
+ else {
91
+ throw new TypeError('Unknown event "' + event + '". ' +
92
+ 'Choose "move" or "zoom".');
93
+ }
94
+ };
95
+
96
+ /**
97
+ * Add event listener
98
+ * @param {String} event Name of the event.
99
+ * Available events: 'rangechange', 'rangechanged'
100
+ * @param {function} callback Callback function, invoked as callback({start: Date, end: Date})
101
+ */
102
+ Range.prototype.on = function on (event, callback) {
103
+ var available = ['rangechange', 'rangechanged'];
104
+
105
+ if (available.indexOf(event) == -1) {
106
+ throw new Error('Unknown event "' + event + '". Choose from ' + available.join());
107
+ }
108
+
109
+ events.addListener(this, event, callback);
110
+ };
111
+
112
+ /**
113
+ * Remove an event listener
114
+ * @param {String} event name of the event
115
+ * @param {function} callback callback handler
116
+ */
117
+ Range.prototype.off = function off (event, callback) {
118
+ events.removeListener(this, event, callback);
119
+ };
120
+
121
+ /**
122
+ * Trigger an event
123
+ * @param {String} event name of the event, available events: 'rangechange',
124
+ * 'rangechanged'
125
+ * @private
126
+ */
127
+ Range.prototype._trigger = function (event) {
128
+ events.trigger(this, event, {
129
+ start: this.start,
130
+ end: this.end
131
+ });
132
+ };
133
+
134
+ /**
135
+ * Set a new start and end range
136
+ * @param {Number} [start]
137
+ * @param {Number} [end]
138
+ */
139
+ Range.prototype.setRange = function(start, end) {
140
+ var changed = this._applyRange(start, end);
141
+ if (changed) {
142
+ this._trigger('rangechange');
143
+ this._trigger('rangechanged');
144
+ }
145
+ };
146
+
147
+ /**
148
+ * Set a new start and end range. This method is the same as setRange, but
149
+ * does not trigger a range change and range changed event, and it returns
150
+ * true when the range is changed
151
+ * @param {Number} [start]
152
+ * @param {Number} [end]
153
+ * @return {Boolean} changed
154
+ * @private
155
+ */
156
+ Range.prototype._applyRange = function(start, end) {
157
+ var newStart = (start != null) ? util.convert(start, 'Date').valueOf() : this.start,
158
+ newEnd = (end != null) ? util.convert(end, 'Date').valueOf() : this.end,
159
+ max = (this.options.max != null) ? util.convert(this.options.max, 'Date').valueOf() : null,
160
+ min = (this.options.min != null) ? util.convert(this.options.min, 'Date').valueOf() : null,
161
+ diff;
162
+
163
+ // check for valid number
164
+ if (isNaN(newStart) || newStart === null) {
165
+ throw new Error('Invalid start "' + start + '"');
166
+ }
167
+ if (isNaN(newEnd) || newEnd === null) {
168
+ throw new Error('Invalid end "' + end + '"');
169
+ }
170
+
171
+ // prevent start < end
172
+ if (newEnd < newStart) {
173
+ newEnd = newStart;
174
+ }
175
+
176
+ // prevent start < min
177
+ if (min !== null) {
178
+ if (newStart < min) {
179
+ diff = (min - newStart);
180
+ newStart += diff;
181
+ newEnd += diff;
182
+
183
+ // prevent end > max
184
+ if (max != null) {
185
+ if (newEnd > max) {
186
+ newEnd = max;
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ // prevent end > max
193
+ if (max !== null) {
194
+ if (newEnd > max) {
195
+ diff = (newEnd - max);
196
+ newStart -= diff;
197
+ newEnd -= diff;
198
+
199
+ // prevent start < min
200
+ if (min != null) {
201
+ if (newStart < min) {
202
+ newStart = min;
203
+ }
204
+ }
205
+ }
206
+ }
207
+
208
+ // prevent (end-start) < zoomMin
209
+ if (this.options.zoomMin !== null) {
210
+ var zoomMin = parseFloat(this.options.zoomMin);
211
+ if (zoomMin < 0) {
212
+ zoomMin = 0;
213
+ }
214
+ if ((newEnd - newStart) < zoomMin) {
215
+ if ((this.end - this.start) === zoomMin) {
216
+ // ignore this action, we are already zoomed to the minimum
217
+ newStart = this.start;
218
+ newEnd = this.end;
219
+ }
220
+ else {
221
+ // zoom to the minimum
222
+ diff = (zoomMin - (newEnd - newStart));
223
+ newStart -= diff / 2;
224
+ newEnd += diff / 2;
225
+ }
226
+ }
227
+ }
228
+
229
+ // prevent (end-start) > zoomMax
230
+ if (this.options.zoomMax !== null) {
231
+ var zoomMax = parseFloat(this.options.zoomMax);
232
+ if (zoomMax < 0) {
233
+ zoomMax = 0;
234
+ }
235
+ if ((newEnd - newStart) > zoomMax) {
236
+ if ((this.end - this.start) === zoomMax) {
237
+ // ignore this action, we are already zoomed to the maximum
238
+ newStart = this.start;
239
+ newEnd = this.end;
240
+ }
241
+ else {
242
+ // zoom to the maximum
243
+ diff = ((newEnd - newStart) - zoomMax);
244
+ newStart += diff / 2;
245
+ newEnd -= diff / 2;
246
+ }
247
+ }
248
+ }
249
+
250
+ var changed = (this.start != newStart || this.end != newEnd);
251
+
252
+ this.start = newStart;
253
+ this.end = newEnd;
254
+
255
+ return changed;
256
+ };
257
+
258
+ /**
259
+ * Retrieve the current range.
260
+ * @return {Object} An object with start and end properties
261
+ */
262
+ Range.prototype.getRange = function() {
263
+ return {
264
+ start: this.start,
265
+ end: this.end
266
+ };
267
+ };
268
+
269
+ /**
270
+ * Calculate the conversion offset and scale for current range, based on
271
+ * the provided width
272
+ * @param {Number} width
273
+ * @returns {{offset: number, scale: number}} conversion
274
+ */
275
+ Range.prototype.conversion = function (width) {
276
+ return Range.conversion(this.start, this.end, width);
277
+ };
278
+
279
+ /**
280
+ * Static method to calculate the conversion offset and scale for a range,
281
+ * based on the provided start, end, and width
282
+ * @param {Number} start
283
+ * @param {Number} end
284
+ * @param {Number} width
285
+ * @returns {{offset: number, scale: number}} conversion
286
+ */
287
+ Range.conversion = function (start, end, width) {
288
+ if (width != 0 && (end - start != 0)) {
289
+ return {
290
+ offset: start,
291
+ scale: width / (end - start)
292
+ }
293
+ }
294
+ else {
295
+ return {
296
+ offset: 0,
297
+ scale: 1
298
+ };
299
+ }
300
+ };
301
+
302
+ // global (private) object to store drag params
303
+ var touchParams = {};
304
+
305
+ /**
306
+ * Start dragging horizontally or vertically
307
+ * @param {Event} event
308
+ * @param {Object} component
309
+ * @private
310
+ */
311
+ Range.prototype._onDragStart = function(event, component) {
312
+ // refuse to drag when we where pinching to prevent the timeline make a jump
313
+ // when releasing the fingers in opposite order from the touch screen
314
+ if (touchParams.pinching) return;
315
+
316
+ touchParams.start = this.start;
317
+ touchParams.end = this.end;
318
+
319
+ var frame = component.frame;
320
+ if (frame) {
321
+ frame.style.cursor = 'move';
322
+ }
323
+ };
324
+
325
+ /**
326
+ * Perform dragging operating.
327
+ * @param {Event} event
328
+ * @param {Component} component
329
+ * @param {String} direction 'horizontal' or 'vertical'
330
+ * @private
331
+ */
332
+ Range.prototype._onDrag = function (event, component, direction) {
333
+ validateDirection(direction);
334
+
335
+ // refuse to drag when we where pinching to prevent the timeline make a jump
336
+ // when releasing the fingers in opposite order from the touch screen
337
+ if (touchParams.pinching) return;
338
+
339
+ var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY,
340
+ interval = (touchParams.end - touchParams.start),
341
+ width = (direction == 'horizontal') ? component.width : component.height,
342
+ diffRange = -delta / width * interval;
343
+
344
+ this._applyRange(touchParams.start + diffRange, touchParams.end + diffRange);
345
+
346
+ // fire a rangechange event
347
+ this._trigger('rangechange');
348
+ };
349
+
350
+ /**
351
+ * Stop dragging operating.
352
+ * @param {event} event
353
+ * @param {Component} component
354
+ * @private
355
+ */
356
+ Range.prototype._onDragEnd = function (event, component) {
357
+ // refuse to drag when we where pinching to prevent the timeline make a jump
358
+ // when releasing the fingers in opposite order from the touch screen
359
+ if (touchParams.pinching) return;
360
+
361
+ if (component.frame) {
362
+ component.frame.style.cursor = 'auto';
363
+ }
364
+
365
+ // fire a rangechanged event
366
+ this._trigger('rangechanged');
367
+ };
368
+
369
+ /**
370
+ * Event handler for mouse wheel event, used to zoom
371
+ * Code from http://adomas.org/javascript-mouse-wheel/
372
+ * @param {Event} event
373
+ * @param {Component} component
374
+ * @param {String} direction 'horizontal' or 'vertical'
375
+ * @private
376
+ */
377
+ Range.prototype._onMouseWheel = function(event, component, direction) {
378
+ validateDirection(direction);
379
+
380
+ // retrieve delta
381
+ var delta = 0;
382
+ if (event.wheelDelta) { /* IE/Opera. */
383
+ delta = event.wheelDelta / 120;
384
+ } else if (event.detail) { /* Mozilla case. */
385
+ // In Mozilla, sign of delta is different than in IE.
386
+ // Also, delta is multiple of 3.
387
+ delta = -event.detail / 3;
388
+ }
389
+
390
+ // If delta is nonzero, handle it.
391
+ // Basically, delta is now positive if wheel was scrolled up,
392
+ // and negative, if wheel was scrolled down.
393
+ if (delta) {
394
+ // perform the zoom action. Delta is normally 1 or -1
395
+
396
+ // adjust a negative delta such that zooming in with delta 0.1
397
+ // equals zooming out with a delta -0.1
398
+ var scale;
399
+ if (delta < 0) {
400
+ scale = 1 - (delta / 5);
401
+ }
402
+ else {
403
+ scale = 1 / (1 + (delta / 5)) ;
404
+ }
405
+
406
+ // calculate center, the date to zoom around
407
+ var gesture = util.fakeGesture(this, event),
408
+ pointer = getPointer(gesture.touches[0], component.frame),
409
+ pointerDate = this._pointerToDate(component, direction, pointer);
410
+
411
+ this.zoom(scale, pointerDate);
412
+ }
413
+
414
+ // Prevent default actions caused by mouse wheel
415
+ // (else the page and timeline both zoom and scroll)
416
+ util.preventDefault(event);
417
+ };
418
+
419
+ /**
420
+ * On start of a touch gesture, initialize scale to 1
421
+ * @private
422
+ */
423
+ Range.prototype._onTouch = function () {
424
+ touchParams.start = this.start;
425
+ touchParams.end = this.end;
426
+ touchParams.pinching = false;
427
+ touchParams.center = null;
428
+ };
429
+
430
+ /**
431
+ * Handle pinch event
432
+ * @param {Event} event
433
+ * @param {Component} component
434
+ * @param {String} direction 'horizontal' or 'vertical'
435
+ * @private
436
+ */
437
+ Range.prototype._onPinch = function (event, component, direction) {
438
+ touchParams.pinching = true;
439
+
440
+ if (event.gesture.touches.length > 1) {
441
+ if (!touchParams.center) {
442
+ touchParams.center = getPointer(event.gesture.center, component.frame);
443
+ }
444
+
445
+ var scale = 1 / event.gesture.scale,
446
+ initDate = this._pointerToDate(component, direction, touchParams.center),
447
+ center = getPointer(event.gesture.center, component.frame),
448
+ date = this._pointerToDate(component, direction, center),
449
+ delta = date - initDate; // TODO: utilize delta
450
+
451
+ // calculate new start and end
452
+ var newStart = parseInt(initDate + (touchParams.start - initDate) * scale);
453
+ var newEnd = parseInt(initDate + (touchParams.end - initDate) * scale);
454
+
455
+ // apply new range
456
+ this.setRange(newStart, newEnd);
457
+ }
458
+ };
459
+
460
+ /**
461
+ * Helper function to calculate the center date for zooming
462
+ * @param {Component} component
463
+ * @param {{x: Number, y: Number}} pointer
464
+ * @param {String} direction 'horizontal' or 'vertical'
465
+ * @return {number} date
466
+ * @private
467
+ */
468
+ Range.prototype._pointerToDate = function (component, direction, pointer) {
469
+ var conversion;
470
+ if (direction == 'horizontal') {
471
+ var width = component.width;
472
+ conversion = this.conversion(width);
473
+ return pointer.x / conversion.scale + conversion.offset;
474
+ }
475
+ else {
476
+ var height = component.height;
477
+ conversion = this.conversion(height);
478
+ return pointer.y / conversion.scale + conversion.offset;
479
+ }
480
+ };
481
+
482
+ /**
483
+ * Get the pointer location relative to the location of the dom element
484
+ * @param {{pageX: Number, pageY: Number}} touch
485
+ * @param {Element} element HTML DOM element
486
+ * @return {{x: Number, y: Number}} pointer
487
+ * @private
488
+ */
489
+ function getPointer (touch, element) {
490
+ return {
491
+ x: touch.pageX - vis.util.getAbsoluteLeft(element),
492
+ y: touch.pageY - vis.util.getAbsoluteTop(element)
493
+ };
494
+ }
495
+
496
+ /**
497
+ * Zoom the range the given scale in or out. Start and end date will
498
+ * be adjusted, and the timeline will be redrawn. You can optionally give a
499
+ * date around which to zoom.
500
+ * For example, try scale = 0.9 or 1.1
501
+ * @param {Number} scale Scaling factor. Values above 1 will zoom out,
502
+ * values below 1 will zoom in.
503
+ * @param {Number} [center] Value representing a date around which will
504
+ * be zoomed.
505
+ */
506
+ Range.prototype.zoom = function(scale, center) {
507
+ // if centerDate is not provided, take it half between start Date and end Date
508
+ if (center == null) {
509
+ center = (this.start + this.end) / 2;
510
+ }
511
+
512
+ // calculate new start and end
513
+ var newStart = center + (this.start - center) * scale;
514
+ var newEnd = center + (this.end - center) * scale;
515
+
516
+ this.setRange(newStart, newEnd);
517
+ };
518
+
519
+ /**
520
+ * Move the range with a given delta to the left or right. Start and end
521
+ * value will be adjusted. For example, try delta = 0.1 or -0.1
522
+ * @param {Number} delta Moving amount. Positive value will move right,
523
+ * negative value will move left
524
+ */
525
+ Range.prototype.move = function(delta) {
526
+ // zoom start Date and end Date relative to the centerDate
527
+ var diff = (this.end - this.start);
528
+
529
+ // apply new values
530
+ var newStart = this.start + diff * delta;
531
+ var newEnd = this.end + diff * delta;
532
+
533
+ // TODO: reckon with min and max range
534
+
535
+ this.start = newStart;
536
+ this.end = newEnd;
537
+ };
538
+
539
+ /**
540
+ * Move the range to a new center point
541
+ * @param {Number} moveTo New center point of the range
542
+ */
543
+ Range.prototype.moveTo = function(moveTo) {
544
+ var center = (this.start + this.end) / 2;
545
+
546
+ var diff = center - moveTo;
547
+
548
+ // calculate new start and end
549
+ var newStart = this.start - diff;
550
+ var newEnd = this.end - diff;
551
+
552
+ this.setRange(newStart, newEnd);
553
+ };