1 // Code for a variety of interaction models. Used in interaction.html, but split out from
2 // that file so they can be tested in isolation.
4 function downV3(event, g, context) {
5 context.initializeMouseDown(event, g, context);
6 if (event.altKey || event.shiftKey) {
7 Dygraph.startZoom(event, g, context);
9 Dygraph.startPan(event, g, context);
13 function moveV3(event, g, context) {
14 if (context.isPanning) {
15 Dygraph.movePan(event, g, context);
16 } else if (context.isZooming) {
17 Dygraph.moveZoom(event, g, context);
21 function upV3(event, g, context) {
22 if (context.isPanning) {
23 Dygraph.endPan(event, g, context);
24 } else if (context.isZooming) {
25 Dygraph.endZoom(event, g, context);
29 // Take the offset of a mouse event on the dygraph canvas and
30 // convert it to a pair of percentages from the bottom left.
31 // (Not top left, bottom is where the lower value is.)
32 function offsetToPercentage(g, offsetX, offsetY) {
33 // This is calculating the pixel offset of the leftmost date.
34 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
35 var yar0 = g.yAxisRange(0);
37 // This is calculating the pixel of the higest value. (Top pixel)
38 var yOffset = g.toDomCoords(null, yar0[1])[1];
40 // x y w and h are relative to the corner of the drawing area,
41 // so that the upper corner of the drawing area is (0, 0).
42 var x = offsetX - xOffset;
43 var y = offsetY - yOffset;
45 // This is computing the rightmost pixel, effectively defining the
47 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
49 // This is computing the lowest pixel, effectively defining the height.
50 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
52 // Percentage from the left.
53 var xPct = w == 0 ? 0 : (x / w);
54 // Percentage from the top.
55 var yPct = h == 0 ? 0 : (y / h);
57 // The (1-) part below changes it from "% distance down from the top"
58 // to "% distance up from the bottom".
59 return [xPct, (1-yPct)];
62 function dblClickV3(event, g, context) {
63 // Reducing by 20% makes it 80% the original size, which means
64 // to restore to original size it must grow by 25%
66 if (!(event.offsetX && event.offsetY)){
67 event.offsetX = event.layerX - event.target.offsetLeft;
68 event.offsetY = event.layerY - event.target.offsetTop;
71 var percentages = offsetToPercentage(g, event.offsetX, event.offsetY);
72 var xPct = percentages[0];
73 var yPct = percentages[1];
76 zoom(g, -.25, xPct, yPct);
78 zoom(g, +.2, xPct, yPct);
82 var lastClickedGraph = null;
84 function clickV3(event, g, context) {
86 Dygraph.cancelEvent(event);
89 function scrollV3(event, g, context) {
90 if (lastClickedGraph != g) {
93 var normal = event.detail ? event.detail * -1 : event.wheelDelta / 40;
94 // For me the normalized value shows 0.075 for one click. If I took
95 // that verbatim, it would be a 7.5%.
96 var percentage = normal / 50;
98 if (!(event.offsetX && event.offsetY)){
99 event.offsetX = event.layerX - event.target.offsetLeft;
100 event.offsetY = event.layerY - event.target.offsetTop;
103 var percentages = offsetToPercentage(g, event.offsetX, event.offsetY);
104 var xPct = percentages[0];
105 var yPct = percentages[1];
107 zoom(g, percentage, xPct, yPct);
108 Dygraph.cancelEvent(event);
111 // Adjusts [x, y] toward each other by zoomInPercentage%
112 // Split it so the left/bottom axis gets xBias/yBias of that change and
113 // tight/top gets (1-xBias)/(1-yBias) of that change.
115 // If a bias is missing it splits it down the middle.
116 function zoom(g, zoomInPercentage, xBias, yBias) {
117 xBias = xBias || 0.5;
118 yBias = yBias || 0.5;
119 function adjustAxis(axis, zoomInPercentage, bias) {
120 var delta = axis[1] - axis[0];
121 var increment = delta * zoomInPercentage;
122 var foo = [increment * bias, increment * (1-bias)];
123 return [ axis[0] + foo[0], axis[1] - foo[1] ];
125 var yAxes = g.yAxisRanges();
127 for (var i = 0; i < yAxes.length; i++) {
128 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
132 dateWindow: adjustAxis(g.xAxisRange(), zoomInPercentage, xBias),
133 valueRange: newYAxes[0]
137 var v4Active = false;
140 function downV4(event, g, context) {
141 context.initializeMouseDown(event, g, context);
143 Dygraph.Interaction.startTouch(event, g, context);
145 Dygraph.Interaction.moveTouch(event, g, context);
147 Dygraph.Interaction.endTouch(event, g, context);
150 moveV4(event, g, context); // in case the mouse went down on a data point.
155 function moveV4(event, g, context) {
159 var graphPos = Dygraph.findPos(g.graphDiv);
160 var canvasx = Dygraph.pageX(event) - graphPos.x;
161 var canvasy = Dygraph.pageY(event) - graphPos.y;
163 var rows = g.numRows();
165 // [date, [val1, stdev1], [val2, stdev2]]
166 for (var row = 0; row < rows; row++) {
167 var date = g.getValue(row, 0);
168 var x = g.toDomCoords(date, null)[0];
169 var diff = Math.abs(canvasx - x);
171 for (var col = 1; col < 3; col++) {
172 // TODO(konigsberg): these will throw exceptions as data is removed.
173 var vals = g.getValue(row, col);
174 if (vals == null) { continue; }
176 var y = g.toDomCoords(null, val)[1];
177 var diff2 = Math.abs(canvasy - y);
180 for (var i in processed) {
181 var stored = processed[i];
182 if(stored[0] == row && stored[1] == col) {
188 //processed.push([row, col]);
199 function upV4(event, g, context) {
205 function dblClickV4(event, g, context) {
206 restorePositioning(g);
209 function drawV4(x, y) {
212 ctx.strokeStyle = "#000000";
213 //ctx.fillStyle = "#FFFF00";
214 ctx.fillStyle = "#FF0000";
216 ctx.arc(x,y,5,0,Math.PI*2,true);
222 function captureCanvas(canvas, area, g) {
226 function restorePositioning(g) {
233 function newDygraphTouchstart(event, g, context) {
234 // This right here is what prevents IOS from doing its own zoom/touch behavior
235 // It stops the node from being selected too
236 event.preventDefault(); // touch browsers are all nice.
238 if (event.touches.length > 1) {
239 // If the user ever puts two fingers down, it's not a double tap.
240 context.startTimeForDoubleTapMs = null;
244 for (var i = 0; i < event.touches.length; i++) {
245 var t = event.touches[i];
246 // we dispense with 'dragGetX_' because all touchBrowsers support pageX
250 dataX: g.toDataXCoord(t.pageX),
251 dataY: g.toDataYCoord(t.pageY)
252 // identifier: t.identifier
255 context.initialTouches = touches;
257 if (touches.length == 1) {
258 // This is just a swipe.
259 context.initialPinchCenter = touches[0];
260 context.touchDirections = { x: true, y: true };
262 // ADDITION - this needs to select the points
263 //var closestTouchP = g.findClosestPoint(touches[0].pageX,touches[0].pageY);
264 //if(closestTouchP) {
265 //var selectionChanged = g.setSelection(closestTouchP.row, closestTouchP.seriesName);
269 } else if (touches.length >= 2) {
270 // It's become a pinch!
271 // In case there are 3+ touches, we ignore all but the "first" two.
273 // only screen coordinates can be averaged (data coords could be log scale).
274 context.initialPinchCenter = {
275 pageX: 0.5 * (touches[0].pageX + touches[1].pageX),
276 pageY: 0.5 * (touches[0].pageY + touches[1].pageY),
278 // TODO(danvk): remove
279 dataX: 0.5 * (touches[0].dataX + touches[1].dataX),
280 dataY: 0.5 * (touches[0].dataY + touches[1].dataY)
283 // Make pinches in a 45-degree swath around either axis 1-dimensional zooms.
284 var initialAngle = 180 / Math.PI * Math.atan2(
285 context.initialPinchCenter.pageY - touches[0].pageY,
286 touches[0].pageX - context.initialPinchCenter.pageX);
288 // use symmetry to get it into the first quadrant.
289 initialAngle = Math.abs(initialAngle);
290 if (initialAngle > 90) initialAngle = 90 - initialAngle;
292 context.touchDirections = {
293 x: (initialAngle < (90 - 45/2)),
294 y: (initialAngle > 45/2)
298 // save the full x & y ranges.
299 context.initialRange = {