You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
269 lines
9.9 KiB
269 lines
9.9 KiB
define(["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/event", "dojo/sniff", |
|
"./ChartAction", "../Element", "dojo/touch", "../plot2d/common", "dojo/has!dojo-bidi?../bidi/action2d/ZoomAndPan"], |
|
function(lang, declare, eventUtil, has, ChartAction, Element, touch, common, BidiTouchZoomAndPan){ |
|
var GlassView = declare(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)"); |
|
}, |
|
clear: function(){ |
|
// summary: |
|
// Clear out any parameters set on this plot. |
|
// returns: GlassView |
|
// 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; // GlassView |
|
}, |
|
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; |
|
} |
|
}); |
|
|
|
/*===== |
|
var __TouchZoomAndPanCtorArgs = { |
|
// summary: |
|
// Additional arguments for touch zoom and pan actions. |
|
// axis: String? |
|
// Target axis name for this action. Default is "x". |
|
// scaleFactor: Number? |
|
// The scale factor applied on mouse wheel zoom. Default is 1.2. |
|
// maxScale: Number? |
|
// The max scale factor accepted by this chart action. Default is 100. |
|
// enableScroll: Boolean? |
|
// Whether touch drag gesture should scroll the chart. Default is true. |
|
// enableZoom: Boolean? |
|
// Whether touch pinch and spread gesture should zoom out or in the chart. Default is true. |
|
}; |
|
=====*/ |
|
|
|
var TouchZoomAndPan = declare(has("dojo-bidi")? "dojox.charting.action2d.NonBidiTouchZoomAndPan" : "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 except on Android 2.x and WP8 devices. |
|
// 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: __TouchZoomAndPanCtorArgs? |
|
// Optional arguments for the action. |
|
this._listeners = [ |
|
{eventName: touch.press, methodName: "onTouchStart"}, |
|
{eventName: touch.move, methodName: "onTouchMove"}, |
|
{eventName: touch.release, methodName: "onTouchEnd"} |
|
]; |
|
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 iOS 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 iOS Safari + SVG, because a touch start action |
|
// started above a item that is removed during the touch action will stop |
|
// dispatching touch events! |
|
if(this.chart.surface.declaredClass.indexOf("svg")!=-1){ |
|
this.chart.addPlot(this._uName, {type: GlassView}); |
|
} |
|
}, |
|
|
|
disconnect: function(){ |
|
// summary: |
|
// Disconnect this action from the chart. |
|
if(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 ? event.touches.length : 1; |
|
var coord = event.touches ? event.touches[0] : event; |
|
// in case we have a double tap register previous coord |
|
var prevPageCoord = this._startPageCoord; |
|
this._startPageCoord = {x: coord.pageX, y: coord.pageY}; |
|
if((this.enableZoom || this.enableScroll) && chart._delayedRenderHandle){ |
|
// we have pending rendering from a scroll, let's sync |
|
chart.render(); |
|
} |
|
if(this.enableZoom && length >= 2){ |
|
// we reset double tap |
|
this._startTime = 0; |
|
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{ |
|
// double tap is only for single touch |
|
if(!event.touches || event.touches.length == 1){ |
|
if(!this._startTime){ |
|
this._startTime = new Date().getTime(); |
|
}else if((new Date().getTime() - this._startTime) < 250 && |
|
Math.abs(this._startPageCoord.x - prevPageCoord.x) < 30 && |
|
Math.abs(this._startPageCoord.y - prevPageCoord.y) < 30){ |
|
this._startTime = 0; |
|
this.onDoubleTap(event); |
|
}else{ |
|
// we missed the doubletap, we need to re-init for next time |
|
this._startTime = 0; |
|
} |
|
}else{ |
|
// we missed the doubletap, we need to re-init for next time |
|
this._startTime = 0; |
|
} |
|
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 ? event.touches.length : 1; |
|
var pAttr = axis.vertical?"pageY":"pageX", |
|
attr = axis.vertical?"y":"x"; |
|
// any move action cancel double tap |
|
this._startTime = 0; |
|
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 = this._getDelta(event); |
|
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 || event.touches.length == 1) && this.enableScroll){ |
|
// still one touch available, let's start back from here for |
|
// potential pan |
|
var coord = event.touches ? event.touches[0] : event; |
|
this._startPageCoord = {x: coord.pageX, y: coord.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); |
|
}, |
|
|
|
_getDelta: function(event){ |
|
var axis = this.chart.getAxis(this.axis), |
|
pAttr = axis.vertical?"pageY":"pageX", |
|
attr = axis.vertical?"y":"x"; |
|
var coord = event.touches?event.touches[0]:event; |
|
return axis.vertical?(this._startPageCoord[attr] - coord[pAttr]): |
|
(coord[pAttr] - this._startPageCoord[attr]); |
|
} |
|
}); |
|
return has("dojo-bidi")? declare("dojox.charting.action2d.TouchZoomAndPan", [TouchZoomAndPan, BidiTouchZoomAndPan]) : TouchZoomAndPan; |
|
});
|
|
|