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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.gitmodules +3 -0
- data/.project +11 -0
- data/Gemfile +4 -0
- data/LICENSE +202 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/vis/rails/engine.rb +6 -0
- data/lib/vis/rails/version.rb +5 -0
- data/lib/vis/rails.rb +7 -0
- data/vendor/assets/javascripts/vis.js +1 -0
- data/vendor/assets/stylesheets/vis.css +3 -0
- data/vendor/assets/vis/DataSet.js +936 -0
- data/vendor/assets/vis/DataView.js +281 -0
- data/vendor/assets/vis/EventBus.js +89 -0
- data/vendor/assets/vis/events.js +116 -0
- data/vendor/assets/vis/graph/ClusterMixin.js +1019 -0
- data/vendor/assets/vis/graph/Edge.js +620 -0
- data/vendor/assets/vis/graph/Graph.js +2111 -0
- data/vendor/assets/vis/graph/Groups.js +80 -0
- data/vendor/assets/vis/graph/Images.js +41 -0
- data/vendor/assets/vis/graph/NavigationMixin.js +245 -0
- data/vendor/assets/vis/graph/Node.js +978 -0
- data/vendor/assets/vis/graph/Popup.js +105 -0
- data/vendor/assets/vis/graph/SectorsMixin.js +547 -0
- data/vendor/assets/vis/graph/SelectionMixin.js +515 -0
- data/vendor/assets/vis/graph/dotparser.js +829 -0
- 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/minus.png +0 -0
- data/vendor/assets/vis/graph/img/plus.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/graph/img/zoomExtends.png +0 -0
- data/vendor/assets/vis/graph/shapes.js +225 -0
- data/vendor/assets/vis/module/exports.js +68 -0
- data/vendor/assets/vis/module/header.js +24 -0
- data/vendor/assets/vis/module/imports.js +32 -0
- data/vendor/assets/vis/shim.js +252 -0
- data/vendor/assets/vis/timeline/Controller.js +172 -0
- data/vendor/assets/vis/timeline/Range.js +553 -0
- data/vendor/assets/vis/timeline/Stack.js +192 -0
- data/vendor/assets/vis/timeline/TimeStep.js +449 -0
- data/vendor/assets/vis/timeline/Timeline.js +476 -0
- data/vendor/assets/vis/timeline/component/Component.js +148 -0
- data/vendor/assets/vis/timeline/component/ContentPanel.js +113 -0
- data/vendor/assets/vis/timeline/component/CurrentTime.js +101 -0
- data/vendor/assets/vis/timeline/component/CustomTime.js +255 -0
- data/vendor/assets/vis/timeline/component/Group.js +129 -0
- data/vendor/assets/vis/timeline/component/GroupSet.js +546 -0
- data/vendor/assets/vis/timeline/component/ItemSet.js +612 -0
- data/vendor/assets/vis/timeline/component/Panel.js +112 -0
- data/vendor/assets/vis/timeline/component/RootPanel.js +215 -0
- data/vendor/assets/vis/timeline/component/TimeAxis.js +522 -0
- data/vendor/assets/vis/timeline/component/css/currenttime.css +5 -0
- data/vendor/assets/vis/timeline/component/css/customtime.css +6 -0
- data/vendor/assets/vis/timeline/component/css/groupset.css +59 -0
- data/vendor/assets/vis/timeline/component/css/item.css +93 -0
- data/vendor/assets/vis/timeline/component/css/itemset.css +17 -0
- data/vendor/assets/vis/timeline/component/css/panel.css +14 -0
- data/vendor/assets/vis/timeline/component/css/timeaxis.css +41 -0
- data/vendor/assets/vis/timeline/component/css/timeline.css +2 -0
- data/vendor/assets/vis/timeline/component/item/Item.js +81 -0
- data/vendor/assets/vis/timeline/component/item/ItemBox.js +302 -0
- data/vendor/assets/vis/timeline/component/item/ItemPoint.js +237 -0
- data/vendor/assets/vis/timeline/component/item/ItemRange.js +251 -0
- data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +91 -0
- data/vendor/assets/vis/util.js +673 -0
- data/vis-rails.gemspec +47 -0
- 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
|
+
};
|