]> git.proxmox.com Git - sencha-touch.git/blob - src/src/chart/interactions/CrossZoom.js
import Sencha Touch 2.4.2 source
[sencha-touch.git] / src / src / chart / interactions / CrossZoom.js
1 /**
2 * @class Ext.chart.interactions.CrossZoom
3 * @extends Ext.chart.interactions.Abstract
4 *
5 * The CrossZoom interaction allows the user to zoom in on a selected area of the chart.
6 *
7 * @example preview
8 * var lineChart = new Ext.chart.CartesianChart({
9 * interactions: [{
10 * type: 'crosszoom'
11 * }],
12 * animate: true,
13 * store: {
14 * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
15 * data: [
16 * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
17 * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
18 * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
19 * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
20 * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
21 * ]
22 * },
23 * axes: [{
24 * type: 'numeric',
25 * position: 'left',
26 * fields: ['data1'],
27 * title: {
28 * text: 'Sample Values',
29 * fontSize: 15
30 * },
31 * grid: true,
32 * minimum: 0
33 * }, {
34 * type: 'category',
35 * position: 'bottom',
36 * fields: ['name'],
37 * title: {
38 * text: 'Sample Values',
39 * fontSize: 15
40 * }
41 * }],
42 * series: [{
43 * type: 'line',
44 * highlight: {
45 * size: 7,
46 * radius: 7
47 * },
48 * style: {
49 * stroke: 'rgb(143,203,203)'
50 * },
51 * xField: 'name',
52 * yField: 'data1',
53 * marker: {
54 * type: 'path',
55 * path: ['M', -2, 0, 0, 2, 2, 0, 0, -2, 'Z'],
56 * stroke: 'blue',
57 * lineWidth: 0
58 * }
59 * }, {
60 * type: 'line',
61 * highlight: {
62 * size: 7,
63 * radius: 7
64 * },
65 * fill: true,
66 * xField: 'name',
67 * yField: 'data3',
68 * marker: {
69 * type: 'circle',
70 * radius: 4,
71 * lineWidth: 0
72 * }
73 * }]
74 * });
75 * Ext.Viewport.setLayout('fit');
76 * Ext.Viewport.add(lineChart);
77 */
78 Ext.define('Ext.chart.interactions.CrossZoom', {
79
80 extend: 'Ext.chart.interactions.Abstract',
81
82 type: 'crosszoom',
83 alias: 'interaction.crosszoom',
84
85 config: {
86 /**
87 * @cfg {Object/Array} axes
88 * Specifies which axes should be made navigable. The config value can take the following formats:
89 *
90 * - An Object whose keys correspond to the {@link Ext.chart.axis.Axis#position position} of each
91 * axis that should be made navigable. Each key's value can either be an Object with further
92 * configuration options for each axis or simply `true` for a default set of options.
93 * {
94 * type: 'crosszoom',
95 * axes: {
96 * left: {
97 * maxZoom: 5,
98 * allowPan: false
99 * },
100 * bottom: true
101 * }
102 * }
103 *
104 * If using the full Object form, the following options can be specified for each axis:
105 *
106 * - minZoom (Number) A minimum zoom level for the axis. Defaults to `1` which is its natural size.
107 * - maxZoom (Number) A maximum zoom level for the axis. Defaults to `10`.
108 * - startZoom (Number) A starting zoom level for the axis. Defaults to `1`.
109 * - allowZoom (Boolean) Whether zooming is allowed for the axis. Defaults to `true`.
110 * - allowPan (Boolean) Whether panning is allowed for the axis. Defaults to `true`.
111 * - startPan (Boolean) A starting panning offset for the axis. Defaults to `0`.
112 *
113 * - An Array of strings, each one corresponding to the {@link Ext.chart.axis.Axis#position position}
114 * of an axis that should be made navigable. The default options will be used for each named axis.
115 *
116 * {
117 * type: 'crosszoom',
118 * axes: ['left', 'bottom']
119 * }
120 *
121 * If the `axes` config is not specified, it will default to making all axes navigable with the
122 * default axis options.
123 */
124 axes: true,
125
126 gesture: 'drag',
127
128 undoButton: {}
129 },
130
131 stopAnimationBeforeSync: false,
132
133 zoomAnimationInProgress: false,
134
135 constructor: function () {
136 this.callSuper(arguments);
137 this.zoomHistory = [];
138 },
139
140 applyAxes: function (axesConfig) {
141 var result = {};
142 if (axesConfig === true) {
143 return {
144 top: {},
145 right: {},
146 bottom: {},
147 left: {}
148 };
149 } else if (Ext.isArray(axesConfig)) {
150 // array of axis names - translate to full object form
151 result = {};
152 Ext.each(axesConfig, function (axis) {
153 result[axis] = {};
154 });
155 } else if (Ext.isObject(axesConfig)) {
156 Ext.iterate(axesConfig, function (key, val) {
157 // axis name with `true` value -> translate to object
158 if (val === true) {
159 result[key] = {};
160 } else if (val !== false) {
161 result[key] = val;
162 }
163 });
164 }
165 return result;
166 },
167
168 applyUndoButton: function (button, oldButton) {
169 var me = this;
170 if (button) {
171 if (oldButton) {
172 oldButton.destroy();
173 }
174 return Ext.create('Ext.Button', Ext.apply({
175 cls: [],
176 iconCls: 'refresh',
177 text: 'Undo Zoom',
178 disabled: true,
179 handler: function () {
180 me.undoZoom();
181 }
182 }, button));
183 } else if (oldButton) {
184 oldButton.destroy();
185 }
186 },
187
188 getGestures: function () {
189 var me = this,
190 gestures = {};
191 gestures[me.getGesture()] = 'onGesture';
192 gestures[me.getGesture() + 'start'] = 'onGestureStart';
193 gestures[me.getGesture() + 'end'] = 'onGestureEnd';
194 gestures.doubletap = 'onDoubleTap';
195 return gestures;
196 },
197
198 getSurface: function () {
199 return this.getChart() && this.getChart().getSurface('main');
200 },
201
202 setSeriesOpacity: function (opacity) {
203 var surface = this.getChart() && this.getChart().getSurface('series-surface', 'series');
204 if (surface) {
205 surface.element.setStyle('opacity', opacity);
206 }
207 },
208
209 onGestureStart: function (e) {
210 var me = this,
211 chart = me.getChart(),
212 surface = me.getSurface(),
213 region = chart.getInnerRegion(),
214 chartWidth = region[2],
215 chartHeight = region[3],
216 xy = chart.element.getXY(),
217 x = e.pageX - xy[0] - region[0],
218 y = e.pageY - xy[1] - region[1];
219
220 if (me.zoomAnimationInProgress) {
221 return;
222 }
223 if (x > 0 && x < chartWidth && y > 0 && y < chartHeight) {
224 me.lockEvents(me.getGesture());
225 me.startX = x;
226 me.startY = y;
227 me.selectionRect = surface.add({
228 type: 'rect',
229 globalAlpha: 0.5,
230 fillStyle: 'rgba(80,80,140,0.5)',
231 strokeStyle: 'rgba(80,80,140,1)',
232 lineWidth: 2,
233 x: x,
234 y: y,
235 width: 0,
236 height: 0,
237 zIndex: 10000
238 });
239 me.setSeriesOpacity(0.8);
240 return false;
241 }
242 },
243
244 onGesture: function (e) {
245 var me = this;
246 if (me.zoomAnimationInProgress) {
247 return;
248 }
249 if (me.getLocks()[me.getGesture()] === me) {
250 var chart = me.getChart(),
251 surface = me.getSurface(),
252 region = chart.getInnerRegion(),
253 chartWidth = region[2],
254 chartHeight = region[3],
255 xy = chart.element.getXY(),
256 x = e.pageX - xy[0] - region[0],
257 y = e.pageY - xy[1] - region[1];
258
259 if (x < 0) {
260 x = 0;
261 } else if (x > chartWidth) {
262 x = chartWidth;
263 }
264 if (y < 0) {
265 y = 0;
266 } else if (y > chartHeight) {
267 y = chartHeight;
268 }
269 me.selectionRect.setAttributes({
270 width: x - me.startX,
271 height: y - me.startY
272 });
273 if (Math.abs(me.startX - x) < 11 || Math.abs(me.startY - y) < 11) {
274 me.selectionRect.setAttributes({globalAlpha: 0.5});
275 } else {
276 me.selectionRect.setAttributes({globalAlpha: 1});
277 }
278 surface.renderFrame();
279 return false;
280 }
281 },
282
283 onGestureEnd: function (e) {
284 var me = this;
285 if (me.zoomAnimationInProgress) {
286 return;
287 }
288 if (me.getLocks()[me.getGesture()] === me) {
289 var chart = me.getChart(),
290 surface = me.getSurface(),
291 region = chart.getInnerRegion(),
292 chartWidth = region[2],
293 chartHeight = region[3],
294 xy = chart.element.getXY(),
295 x = e.pageX - xy[0] - region[0],
296 y = e.pageY - xy[1] - region[1];
297
298 if (x < 0) {
299 x = 0;
300 } else if (x > chartWidth) {
301 x = chartWidth;
302 }
303 if (y < 0) {
304 y = 0;
305 } else if (y > chartHeight) {
306 y = chartHeight;
307 }
308 if (Math.abs(me.startX - x) < 11 || Math.abs(me.startY - y) < 11) {
309 surface.remove(me.selectionRect);
310 } else {
311 me.zoomBy([
312 Math.min(me.startX, x) / chartWidth,
313 1 - Math.max(me.startY, y) / chartHeight,
314 Math.max(me.startX, x) / chartWidth,
315 1 - Math.min(me.startY, y) / chartHeight
316 ]);
317
318 me.selectionRect.setAttributes({
319 x: Math.min(me.startX, x),
320 y: Math.min(me.startY, y),
321 width: Math.abs(me.startX - x),
322 height: Math.abs(me.startY - y)
323 });
324
325 me.selectionRect.fx.setConfig(chart.getAnimate() || {duration: 0});
326 me.selectionRect.setAttributes({
327 globalAlpha: 0,
328 x: 0,
329 y: 0,
330 width: chartWidth,
331 height: chartHeight
332 });
333
334 me.zoomAnimationInProgress = true;
335
336 chart.suspendThicknessChanged();
337 me.selectionRect.fx.on('animationend', function () {
338 chart.resumeThicknessChanged();
339
340 surface.remove(me.selectionRect);
341 me.selectionRect = null;
342
343 me.zoomAnimationInProgress = false;
344 });
345 }
346
347 surface.renderFrame();
348 me.sync();
349 me.unlockEvents(me.getGesture());
350 me.setSeriesOpacity(1.0);
351
352 if (!me.zoomAnimationInProgress) {
353 surface.remove(me.selectionRect);
354 me.selectionRect = null;
355 }
356 }
357 },
358
359 zoomBy: function (region) {
360 var me = this,
361 axisConfigs = me.getAxes(),
362 axes = me.getChart().getAxes(),
363 config,
364 zoomMap = {};
365
366 for (var i = 0; i < axes.length; i++) {
367 var axis = axes[i];
368 config = axisConfigs[axis.getPosition()];
369 if (config && config.allowZoom !== false) {
370 var isSide = axis.isSide(),
371 oldRange = axis.getVisibleRange();
372 zoomMap[axis.getId()] = oldRange.slice(0);
373 if (!isSide) {
374 axis.setVisibleRange([
375 (oldRange[1] - oldRange[0]) * region[0] + oldRange[0],
376 (oldRange[1] - oldRange[0]) * region[2] + oldRange[0]
377 ]);
378 } else {
379 axis.setVisibleRange([
380 (oldRange[1] - oldRange[0]) * region[1] + oldRange[0],
381 (oldRange[1] - oldRange[0]) * region[3] + oldRange[0]
382 ]);
383 }
384 }
385 }
386
387 me.zoomHistory.push(zoomMap);
388 me.getUndoButton().setDisabled(false);
389 },
390
391 undoZoom: function () {
392 var zoomMap = this.zoomHistory.pop(),
393 axes = this.getChart().getAxes();
394 if (zoomMap) {
395 for (var i = 0; i < axes.length; i++) {
396 var axis = axes[i];
397 if (zoomMap[axis.getId()]) {
398 axis.setVisibleRange(zoomMap[axis.getId()]);
399 }
400 }
401 }
402 this.getUndoButton().setDisabled(this.zoomHistory.length === 0);
403 this.sync();
404 },
405
406 onDoubleTap: function (e) {
407 this.undoZoom();
408 }
409 });