diff options
Diffstat (limited to 'js/dojo/dojox/charting/action2d/TouchZoomAndPan.js')
| -rw-r--r-- | js/dojo/dojox/charting/action2d/TouchZoomAndPan.js | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/js/dojo/dojox/charting/action2d/TouchZoomAndPan.js b/js/dojo/dojox/charting/action2d/TouchZoomAndPan.js new file mode 100644 index 0000000..9ad36b9 --- /dev/null +++ b/js/dojo/dojox/charting/action2d/TouchZoomAndPan.js @@ -0,0 +1,250 @@ +//>>built +define("dojox/charting/action2d/TouchZoomAndPan", ["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/event", "dojo/_base/sniff", + "./ChartAction", "../Element", "dojox/gesture/tap", "../plot2d/common"], + function(lang, declare, eventUtil, has, ChartAction, Element, tap, common){ + var GlassView = declare("dojox.charting.action2d._GlassView", [Element], { + // summary: Private internal class used by TouchZoomAndPan actions. + // tags: + // private + constructor: function(chart){ + }, + render: function(){ + if(!this.isDirty()){ + return; + } + this.cleanGroup(); + this.group.createRect({width: this.chart.dim.width, height: this.chart.dim.height}).setFill("rgba(0,0,0,0)"); + }, + cleanGroup: function(creator){ + // summary: + // Clean any elements (HTML or GFX-based) out of our group, and create a new one. + // creator: dojox.gfx.Surface? + // An optional surface to work with. + // returns: dojox.charting.Element + // A reference to this object for functional chaining. + this.inherited(arguments); + return this; // dojox.charting.Element + }, + clear: function(){ + // summary: + // Clear out any parameters set on this plot. + // returns: dojox.charting.action2d._IndicatorElement + // The reference to this plot for functional chaining. + this.dirty = true; + // glass view needs to be above + if(this.chart.stack[0] != this){ + this.chart.movePlotToFront(this.name); + } + return this; // dojox.charting.plot2d._IndicatorElement + }, + getSeriesStats: function(){ + // summary: + // Returns default stats (irrelevant for this type of plot). + // returns: Object + // {hmin, hmax, vmin, vmax} min/max in both directions. + return lang.delegate(common.defaultStats); + }, + initializeScalers: function(){ + // summary: + // Does nothing (irrelevant for this type of plot). + return this; + }, + isDirty: function(){ + // summary: + // Return whether or not this plot needs to be redrawn. + // returns: Boolean + // If this plot needs to be rendered, this will return true. + return this.dirty; + } + }); + + /*===== + + declare("dojox.charting.action2d.__TouchZoomAndPanCtorArgs", null, { + // summary: + // Additional arguments for mouse zoom and pan actions. + + // axis: String? + // Target axis name for this action. Default is "x". + axis: "x", + // scaleFactor: Number? + // The scale factor applied on double tap. Default is 1.2. + scaleFactor: 1.2, + // maxScale: Number? + // The max scale factor accepted by this action. Default is 100. + maxScale: 100, + // enableScroll: Boolean? + // Whether touch drag gesture should scroll the chart. Default is true. + enableScroll: true, + // enableZoom: Boolean? + // Whether touch pinch and spread gesture should zoom out or in the chart. Default is true. + enableZoom: true, + }); + var ChartAction = dojox.charting.action2d.ChartAction; + =====*/ + + return declare("dojox.charting.action2d.TouchZoomAndPan", ChartAction, { + // summary: + // Create a touch zoom and pan action. + // You can zoom out or in the data window with pinch and spread gestures. You can scroll using drag gesture. + // Finally this is possible to navigate between a fit window and a zoom one using double tap gesture. + + // the data description block for the widget parser + defaultParams: { + axis: "x", + scaleFactor: 1.2, + maxScale: 100, + enableScroll: true, + enableZoom: true + }, + optionalParams: {}, // no optional parameters + + constructor: function(chart, plot, kwArgs){ + // summary: + // Create a new touch zoom and pan action and connect it. + // chart: dojox.charting.Chart + // The chart this action applies to. + // kwArgs: dojox.charting.action2d.__TouchZoomAndPanCtorArgs? + // Optional arguments for the action. + this._listeners = [{eventName: "ontouchstart", methodName: "onTouchStart"}, + {eventName: "ontouchmove", methodName: "onTouchMove"}, + {eventName: "ontouchend", methodName: "onTouchEnd"}, + {eventName: tap.doubletap, methodName: "onDoubleTap"}]; + if(!kwArgs){ kwArgs = {}; } + this.axis = kwArgs.axis ? kwArgs.axis : "x"; + this.scaleFactor = kwArgs.scaleFactor ? kwArgs.scaleFactor : 1.2; + this.maxScale = kwArgs.maxScale ? kwArgs.maxScale : 100; + this.enableScroll = kwArgs.enableScroll != undefined ? kwArgs.enableScroll : true; + this.enableZoom = kwArgs.enableScroll != undefined ? kwArgs.enableZoom : true; + this._uName = "touchZoomPan"+this.axis; + this.connect(); + }, + + connect: function(){ + // summary: + // Connect this action to the chart. On Safari this adds a new glass view plot + // to the chart that's why Chart.render() must be called after connect. + this.inherited(arguments); + // this is needed to workaround issue on Safari + SVG, because a touch start action + // started above a item that is removed during the touch action will stop + // dispatching touch events! + if(has("safari") && this.chart.surface.declaredClass.indexOf("svg")!=-1){ + this.chart.addPlot(this._uName, {type: GlassView}); + } + }, + + disconnect: function(){ + // summary: + // Disconnect this action from the chart. + if(has("safari") && this.chart.surface.declaredClass.indexOf("svg")!=-1){ + this.chart.removePlot(this._uName); + } + this.inherited(arguments); + }, + + onTouchStart: function(event){ + // summary: + // Called when touch is started on the chart. + // we always want to be above regular plots and not clipped + var chart = this.chart, axis = chart.getAxis(this.axis); + var length = event.touches.length; + this._startPageCoord = {x: event.touches[0].pageX, y: event.touches[0].pageY}; + if((this.enableZoom || this.enableScroll) && chart._delayedRenderHandle){ + // we have pending rendering from a scroll, let's sync + clearTimeout(chart._delayedRenderHandle); + chart._delayedRenderHandle = null; + chart.render(); + } + if(this.enableZoom && length >= 2){ + this._endPageCoord = {x: event.touches[1].pageX, y: event.touches[1].pageY}; + var middlePageCoord = {x: (this._startPageCoord.x + this._endPageCoord.x) / 2, + y: (this._startPageCoord.y + this._endPageCoord.y) / 2}; + var scaler = axis.getScaler(); + this._initScale = axis.getWindowScale(); + var t = this._initData = this.plot.toData(); + this._middleCoord = t(middlePageCoord)[this.axis]; + this._startCoord = scaler.bounds.from; + this._endCoord = scaler.bounds.to; + }else if(this.enableScroll){ + this._startScroll(axis); + // needed for Android, otherwise will get a touch cancel while swiping + eventUtil.stop(event); + } + }, + + onTouchMove: function(event){ + // summary: + // Called when touch is moved on the chart. + var chart = this.chart, axis = chart.getAxis(this.axis); + var length = event.touches.length; + var pAttr = axis.vertical?"pageY":"pageX", + attr = axis.vertical?"y":"x"; + if(this.enableZoom && length >= 2){ + var newMiddlePageCoord = {x: (event.touches[1].pageX + event.touches[0].pageX) / 2, + y: (event.touches[1].pageY + event.touches[0].pageY) / 2}; + var scale = (this._endPageCoord[attr] - this._startPageCoord[attr]) / + (event.touches[1][pAttr] - event.touches[0][pAttr]); + + if(this._initScale / scale > this.maxScale){ + return; + } + + var newMiddleCoord = this._initData(newMiddlePageCoord)[this.axis]; + + var newStart = scale * (this._startCoord - newMiddleCoord) + this._middleCoord, + newEnd = scale * (this._endCoord - newMiddleCoord) + this._middleCoord; + chart.zoomIn(this.axis, [newStart, newEnd]); + // avoid browser pan + eventUtil.stop(event); + }else if(this.enableScroll){ + var delta = axis.vertical?(this._startPageCoord[attr] - event.touches[0][pAttr]): + (event.touches[0][pAttr] - this._startPageCoord[attr]); + chart.setAxisWindow(this.axis, this._lastScale, this._initOffset - delta / this._lastFactor / this._lastScale); + chart.delayedRender(); + // avoid browser pan + eventUtil.stop(event); + } + }, + + onTouchEnd: function(event){ + // summary: + // Called when touch is ended on the chart. + var chart = this.chart, axis = chart.getAxis(this.axis); + if(event.touches.length == 1 && this.enableScroll){ + // still one touch available, let's start back from here for + // potential pan + this._startPageCoord = {x: event.touches[0].pageX, y: event.touches[0].pageY}; + this._startScroll(axis); + } + }, + + _startScroll: function(axis){ + var bounds = axis.getScaler().bounds; + this._initOffset = axis.getWindowOffset(); + // we keep it because of delay rendering we might now always have access to the + // information to compute it + this._lastScale = axis.getWindowScale(); + this._lastFactor = bounds.span / (bounds.upper - bounds.lower); + }, + + onDoubleTap: function(event){ + // summary: + // Called when double tap is performed on the chart. + var chart = this.chart, axis = chart.getAxis(this.axis); + var scale = 1 / this.scaleFactor; + // are we fit? + if(axis.getWindowScale()==1){ + // fit => zoom + var scaler = axis.getScaler(), start = scaler.bounds.from, end = scaler.bounds.to, + oldMiddle = (start + end) / 2, newMiddle = this.plot.toData(this._startPageCoord)[this.axis], + newStart = scale * (start - oldMiddle) + newMiddle, newEnd = scale * (end - oldMiddle) + newMiddle; + chart.zoomIn(this.axis, [newStart, newEnd]); + }else{ + // non fit => fit + chart.setAxisWindow(this.axis, 1, 0); + chart.render(); + } + eventUtil.stop(event); + } + }); +}); |
