251 lines
9.3 KiB
JavaScript
251 lines
9.3 KiB
JavaScript
//>>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);
|
|
}
|
|
});
|
|
});
|