TouchZoomAndPan.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. define("dojox/charting/action2d/TouchZoomAndPan", ["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/event", "dojo/_base/sniff",
  2. "./ChartAction", "../Element", "dojox/gesture/tap", "../plot2d/common"],
  3. function(lang, declare, eventUtil, has, ChartAction, Element, tap, common){
  4. var GlassView = declare("dojox.charting.action2d._GlassView", [Element], {
  5. // summary: Private internal class used by TouchZoomAndPan actions.
  6. // tags:
  7. // private
  8. constructor: function(chart){
  9. },
  10. render: function(){
  11. if(!this.isDirty()){
  12. return;
  13. }
  14. this.cleanGroup();
  15. this.group.createRect({width: this.chart.dim.width, height: this.chart.dim.height}).setFill("rgba(0,0,0,0)");
  16. },
  17. cleanGroup: function(creator){
  18. // summary:
  19. // Clean any elements (HTML or GFX-based) out of our group, and create a new one.
  20. // creator: dojox.gfx.Surface?
  21. // An optional surface to work with.
  22. // returns: dojox.charting.Element
  23. // A reference to this object for functional chaining.
  24. this.inherited(arguments);
  25. return this; // dojox.charting.Element
  26. },
  27. clear: function(){
  28. // summary:
  29. // Clear out any parameters set on this plot.
  30. // returns: dojox.charting.action2d._IndicatorElement
  31. // The reference to this plot for functional chaining.
  32. this.dirty = true;
  33. // glass view needs to be above
  34. if(this.chart.stack[0] != this){
  35. this.chart.movePlotToFront(this.name);
  36. }
  37. return this; // dojox.charting.plot2d._IndicatorElement
  38. },
  39. getSeriesStats: function(){
  40. // summary:
  41. // Returns default stats (irrelevant for this type of plot).
  42. // returns: Object
  43. // {hmin, hmax, vmin, vmax} min/max in both directions.
  44. return lang.delegate(common.defaultStats);
  45. },
  46. initializeScalers: function(){
  47. // summary:
  48. // Does nothing (irrelevant for this type of plot).
  49. return this;
  50. },
  51. isDirty: function(){
  52. // summary:
  53. // Return whether or not this plot needs to be redrawn.
  54. // returns: Boolean
  55. // If this plot needs to be rendered, this will return true.
  56. return this.dirty;
  57. }
  58. });
  59. /*=====
  60. declare("dojox.charting.action2d.__TouchZoomAndPanCtorArgs", null, {
  61. // summary:
  62. // Additional arguments for mouse zoom and pan actions.
  63. // axis: String?
  64. // Target axis name for this action. Default is "x".
  65. axis: "x",
  66. // scaleFactor: Number?
  67. // The scale factor applied on double tap. Default is 1.2.
  68. scaleFactor: 1.2,
  69. // maxScale: Number?
  70. // The max scale factor accepted by this action. Default is 100.
  71. maxScale: 100,
  72. // enableScroll: Boolean?
  73. // Whether touch drag gesture should scroll the chart. Default is true.
  74. enableScroll: true,
  75. // enableZoom: Boolean?
  76. // Whether touch pinch and spread gesture should zoom out or in the chart. Default is true.
  77. enableZoom: true,
  78. });
  79. var ChartAction = dojox.charting.action2d.ChartAction;
  80. =====*/
  81. return declare("dojox.charting.action2d.TouchZoomAndPan", ChartAction, {
  82. // summary:
  83. // Create a touch zoom and pan action.
  84. // You can zoom out or in the data window with pinch and spread gestures. You can scroll using drag gesture.
  85. // Finally this is possible to navigate between a fit window and a zoom one using double tap gesture.
  86. // the data description block for the widget parser
  87. defaultParams: {
  88. axis: "x",
  89. scaleFactor: 1.2,
  90. maxScale: 100,
  91. enableScroll: true,
  92. enableZoom: true
  93. },
  94. optionalParams: {}, // no optional parameters
  95. constructor: function(chart, plot, kwArgs){
  96. // summary:
  97. // Create a new touch zoom and pan action and connect it.
  98. // chart: dojox.charting.Chart
  99. // The chart this action applies to.
  100. // kwArgs: dojox.charting.action2d.__TouchZoomAndPanCtorArgs?
  101. // Optional arguments for the action.
  102. this._listeners = [{eventName: "ontouchstart", methodName: "onTouchStart"},
  103. {eventName: "ontouchmove", methodName: "onTouchMove"},
  104. {eventName: "ontouchend", methodName: "onTouchEnd"},
  105. {eventName: tap.doubletap, methodName: "onDoubleTap"}];
  106. if(!kwArgs){ kwArgs = {}; }
  107. this.axis = kwArgs.axis ? kwArgs.axis : "x";
  108. this.scaleFactor = kwArgs.scaleFactor ? kwArgs.scaleFactor : 1.2;
  109. this.maxScale = kwArgs.maxScale ? kwArgs.maxScale : 100;
  110. this.enableScroll = kwArgs.enableScroll != undefined ? kwArgs.enableScroll : true;
  111. this.enableZoom = kwArgs.enableScroll != undefined ? kwArgs.enableZoom : true;
  112. this._uName = "touchZoomPan"+this.axis;
  113. this.connect();
  114. },
  115. connect: function(){
  116. // summary:
  117. // Connect this action to the chart. On Safari this adds a new glass view plot
  118. // to the chart that's why Chart.render() must be called after connect.
  119. this.inherited(arguments);
  120. // this is needed to workaround issue on Safari + SVG, because a touch start action
  121. // started above a item that is removed during the touch action will stop
  122. // dispatching touch events!
  123. if(has("safari") && this.chart.surface.declaredClass.indexOf("svg")!=-1){
  124. this.chart.addPlot(this._uName, {type: GlassView});
  125. }
  126. },
  127. disconnect: function(){
  128. // summary:
  129. // Disconnect this action from the chart.
  130. if(has("safari") && this.chart.surface.declaredClass.indexOf("svg")!=-1){
  131. this.chart.removePlot(this._uName);
  132. }
  133. this.inherited(arguments);
  134. },
  135. onTouchStart: function(event){
  136. // summary:
  137. // Called when touch is started on the chart.
  138. // we always want to be above regular plots and not clipped
  139. var chart = this.chart, axis = chart.getAxis(this.axis);
  140. var length = event.touches.length;
  141. this._startPageCoord = {x: event.touches[0].pageX, y: event.touches[0].pageY};
  142. if((this.enableZoom || this.enableScroll) && chart._delayedRenderHandle){
  143. // we have pending rendering from a scroll, let's sync
  144. clearTimeout(chart._delayedRenderHandle);
  145. chart._delayedRenderHandle = null;
  146. chart.render();
  147. }
  148. if(this.enableZoom && length >= 2){
  149. this._endPageCoord = {x: event.touches[1].pageX, y: event.touches[1].pageY};
  150. var middlePageCoord = {x: (this._startPageCoord.x + this._endPageCoord.x) / 2,
  151. y: (this._startPageCoord.y + this._endPageCoord.y) / 2};
  152. var scaler = axis.getScaler();
  153. this._initScale = axis.getWindowScale();
  154. var t = this._initData = this.plot.toData();
  155. this._middleCoord = t(middlePageCoord)[this.axis];
  156. this._startCoord = scaler.bounds.from;
  157. this._endCoord = scaler.bounds.to;
  158. }else if(this.enableScroll){
  159. this._startScroll(axis);
  160. // needed for Android, otherwise will get a touch cancel while swiping
  161. eventUtil.stop(event);
  162. }
  163. },
  164. onTouchMove: function(event){
  165. // summary:
  166. // Called when touch is moved on the chart.
  167. var chart = this.chart, axis = chart.getAxis(this.axis);
  168. var length = event.touches.length;
  169. var pAttr = axis.vertical?"pageY":"pageX",
  170. attr = axis.vertical?"y":"x";
  171. if(this.enableZoom && length >= 2){
  172. var newMiddlePageCoord = {x: (event.touches[1].pageX + event.touches[0].pageX) / 2,
  173. y: (event.touches[1].pageY + event.touches[0].pageY) / 2};
  174. var scale = (this._endPageCoord[attr] - this._startPageCoord[attr]) /
  175. (event.touches[1][pAttr] - event.touches[0][pAttr]);
  176. if(this._initScale / scale > this.maxScale){
  177. return;
  178. }
  179. var newMiddleCoord = this._initData(newMiddlePageCoord)[this.axis];
  180. var newStart = scale * (this._startCoord - newMiddleCoord) + this._middleCoord,
  181. newEnd = scale * (this._endCoord - newMiddleCoord) + this._middleCoord;
  182. chart.zoomIn(this.axis, [newStart, newEnd]);
  183. // avoid browser pan
  184. eventUtil.stop(event);
  185. }else if(this.enableScroll){
  186. var delta = axis.vertical?(this._startPageCoord[attr] - event.touches[0][pAttr]):
  187. (event.touches[0][pAttr] - this._startPageCoord[attr]);
  188. chart.setAxisWindow(this.axis, this._lastScale, this._initOffset - delta / this._lastFactor / this._lastScale);
  189. chart.delayedRender();
  190. // avoid browser pan
  191. eventUtil.stop(event);
  192. }
  193. },
  194. onTouchEnd: function(event){
  195. // summary:
  196. // Called when touch is ended on the chart.
  197. var chart = this.chart, axis = chart.getAxis(this.axis);
  198. if(event.touches.length == 1 && this.enableScroll){
  199. // still one touch available, let's start back from here for
  200. // potential pan
  201. this._startPageCoord = {x: event.touches[0].pageX, y: event.touches[0].pageY};
  202. this._startScroll(axis);
  203. }
  204. },
  205. _startScroll: function(axis){
  206. var bounds = axis.getScaler().bounds;
  207. this._initOffset = axis.getWindowOffset();
  208. // we keep it because of delay rendering we might now always have access to the
  209. // information to compute it
  210. this._lastScale = axis.getWindowScale();
  211. this._lastFactor = bounds.span / (bounds.upper - bounds.lower);
  212. },
  213. onDoubleTap: function(event){
  214. // summary:
  215. // Called when double tap is performed on the chart.
  216. var chart = this.chart, axis = chart.getAxis(this.axis);
  217. var scale = 1 / this.scaleFactor;
  218. // are we fit?
  219. if(axis.getWindowScale()==1){
  220. // fit => zoom
  221. var scaler = axis.getScaler(), start = scaler.bounds.from, end = scaler.bounds.to,
  222. oldMiddle = (start + end) / 2, newMiddle = this.plot.toData(this._startPageCoord)[this.axis],
  223. newStart = scale * (start - oldMiddle) + newMiddle, newEnd = scale * (end - oldMiddle) + newMiddle;
  224. chart.zoomIn(this.axis, [newStart, newEnd]);
  225. }else{
  226. // non fit => fit
  227. chart.setAxisWindow(this.axis, 1, 0);
  228. chart.render();
  229. }
  230. eventUtil.stop(event);
  231. }
  232. });
  233. });