vis-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ };