summaryrefslogtreecommitdiff
path: root/js/dojo/dojox/charting/action2d/TouchZoomAndPan.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/dojo/dojox/charting/action2d/TouchZoomAndPan.js')
-rw-r--r--js/dojo/dojox/charting/action2d/TouchZoomAndPan.js250
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);
+ }
+ });
+});