1 /** 2 * @license 3 * Copyright 2011 Robert Konigsberg (konigsberg@google.com) 4 * MIT-licenced: https://opensource.org/licenses/MIT 5 */ 6 7 /** 8 * @fileoverview The default interaction model for Dygraphs. This is kept out 9 * of dygraph.js for better navigability. 10 * @author Robert Konigsberg (konigsberg@google.com) 11 */ 12 13 /*global Dygraph:false */ 14 "use strict"; 15 16 import * as utils from './dygraph-utils'; 17 18 /** 19 * You can drag this many pixels past the edge of the chart and still have it 20 * be considered a zoom. This makes it easier to zoom to the exact edge of the 21 * chart, a fairly common operation. 22 */ 23 var DRAG_EDGE_MARGIN = 100; 24 25 /** 26 * A collection of functions to facilitate build custom interaction models. 27 * @class 28 */ 29 var DygraphInteraction = {}; 30 31 /** 32 * Checks whether the beginning & ending of an event were close enough that it 33 * should be considered a click. If it should, dispatch appropriate events. 34 * Returns true if the event was treated as a click. 35 * 36 * @param {Event} event 37 * @param {Dygraph} g 38 * @param {Object} context 39 */ 40 DygraphInteraction.maybeTreatMouseOpAsClick = function(event, g, context) { 41 context.dragEndX = utils.dragGetX_(event, context); 42 context.dragEndY = utils.dragGetY_(event, context); 43 var regionWidth = Math.abs(context.dragEndX - context.dragStartX); 44 var regionHeight = Math.abs(context.dragEndY - context.dragStartY); 45 46 if (regionWidth < 2 && regionHeight < 2 && 47 g.lastx_ !== undefined && g.lastx_ != -1) { 48 DygraphInteraction.treatMouseOpAsClick(g, event, context); 49 } 50 51 context.regionWidth = regionWidth; 52 context.regionHeight = regionHeight; 53 }; 54 55 /** 56 * Called in response to an interaction model operation that 57 * should start the default panning behavior. 58 * 59 * It's used in the default callback for "mousedown" operations. 60 * Custom interaction model builders can use it to provide the default 61 * panning behavior. 62 * 63 * @param {Event} event the event object which led to the startPan call. 64 * @param {Dygraph} g The dygraph on which to act. 65 * @param {Object} context The dragging context object (with 66 * dragStartX/dragStartY/etc. properties). This function modifies the 67 * context. 68 */ 69 DygraphInteraction.startPan = function(event, g, context) { 70 var i, axis; 71 context.isPanning = true; 72 var xRange = g.xAxisRange(); 73 74 if (g.getOptionForAxis("logscale", "x")) { 75 context.initialLeftmostDate = utils.log10(xRange[0]); 76 context.dateRange = utils.log10(xRange[1]) - utils.log10(xRange[0]); 77 } else { 78 context.initialLeftmostDate = xRange[0]; 79 context.dateRange = xRange[1] - xRange[0]; 80 } 81 context.xUnitsPerPixel = context.dateRange / (g.plotter_.area.w - 1); 82 83 if (g.getNumericOption("panEdgeFraction")) { 84 var maxXPixelsToDraw = g.width_ * g.getNumericOption("panEdgeFraction"); 85 var xExtremes = g.xAxisExtremes(); // I REALLY WANT TO CALL THIS xTremes! 86 87 var boundedLeftX = g.toDomXCoord(xExtremes[0]) - maxXPixelsToDraw; 88 var boundedRightX = g.toDomXCoord(xExtremes[1]) + maxXPixelsToDraw; 89 90 var boundedLeftDate = g.toDataXCoord(boundedLeftX); 91 var boundedRightDate = g.toDataXCoord(boundedRightX); 92 context.boundedDates = [boundedLeftDate, boundedRightDate]; 93 94 var boundedValues = []; 95 var maxYPixelsToDraw = g.height_ * g.getNumericOption("panEdgeFraction"); 96 97 for (i = 0; i < g.axes_.length; i++) { 98 axis = g.axes_[i]; 99 var yExtremes = axis.extremeRange; 100 101 var boundedTopY = g.toDomYCoord(yExtremes[0], i) + maxYPixelsToDraw; 102 var boundedBottomY = g.toDomYCoord(yExtremes[1], i) - maxYPixelsToDraw; 103 104 var boundedTopValue = g.toDataYCoord(boundedTopY, i); 105 var boundedBottomValue = g.toDataYCoord(boundedBottomY, i); 106 107 boundedValues[i] = [boundedTopValue, boundedBottomValue]; 108 } 109 context.boundedValues = boundedValues; 110 } 111 112 // Record the range of each y-axis at the start of the drag. 113 // If any axis has a valueRange, then we want a 2D pan. 114 // We can't store data directly in g.axes_, because it does not belong to us 115 // and could change out from under us during a pan (say if there's a data 116 // update). 117 context.is2DPan = false; 118 context.axes = []; 119 for (i = 0; i < g.axes_.length; i++) { 120 axis = g.axes_[i]; 121 var axis_data = {}; 122 var yRange = g.yAxisRange(i); 123 // TODO(konigsberg): These values should be in |context|. 124 // In log scale, initialTopValue, dragValueRange and unitsPerPixel are log scale. 125 var logscale = g.attributes_.getForAxis("logscale", i); 126 if (logscale) { 127 axis_data.initialTopValue = utils.log10(yRange[1]); 128 axis_data.dragValueRange = utils.log10(yRange[1]) - utils.log10(yRange[0]); 129 } else { 130 axis_data.initialTopValue = yRange[1]; 131 axis_data.dragValueRange = yRange[1] - yRange[0]; 132 } 133 axis_data.unitsPerPixel = axis_data.dragValueRange / (g.plotter_.area.h - 1); 134 context.axes.push(axis_data); 135 136 // While calculating axes, set 2dpan. 137 if (axis.valueRange) context.is2DPan = true; 138 } 139 }; 140 141 /** 142 * Called in response to an interaction model operation that 143 * responds to an event that pans the view. 144 * 145 * It's used in the default callback for "mousemove" operations. 146 * Custom interaction model builders can use it to provide the default 147 * panning behavior. 148 * 149 * @param {Event} event the event object which led to the movePan call. 150 * @param {Dygraph} g The dygraph on which to act. 151 * @param {Object} context The dragging context object (with 152 * dragStartX/dragStartY/etc. properties). This function modifies the 153 * context. 154 */ 155 DygraphInteraction.movePan = function(event, g, context) { 156 context.dragEndX = utils.dragGetX_(event, context); 157 context.dragEndY = utils.dragGetY_(event, context); 158 159 var minDate = context.initialLeftmostDate - 160 (context.dragEndX - context.dragStartX) * context.xUnitsPerPixel; 161 if (context.boundedDates) { 162 minDate = Math.max(minDate, context.boundedDates[0]); 163 } 164 var maxDate = minDate + context.dateRange; 165 if (context.boundedDates) { 166 if (maxDate > context.boundedDates[1]) { 167 // Adjust minDate, and recompute maxDate. 168 minDate = minDate - (maxDate - context.boundedDates[1]); 169 maxDate = minDate + context.dateRange; 170 } 171 } 172 173 if (g.getOptionForAxis("logscale", "x")) { 174 g.dateWindow_ = [ Math.pow(utils.LOG_SCALE, minDate), 175 Math.pow(utils.LOG_SCALE, maxDate) ]; 176 } else { 177 g.dateWindow_ = [minDate, maxDate]; 178 } 179 180 // y-axis scaling is automatic unless this is a full 2D pan. 181 if (context.is2DPan) { 182 183 var pixelsDragged = context.dragEndY - context.dragStartY; 184 185 // Adjust each axis appropriately. 186 for (var i = 0; i < g.axes_.length; i++) { 187 var axis = g.axes_[i]; 188 var axis_data = context.axes[i]; 189 var unitsDragged = pixelsDragged * axis_data.unitsPerPixel; 190 191 var boundedValue = context.boundedValues ? context.boundedValues[i] : null; 192 193 // In log scale, maxValue and minValue are the logs of those values. 194 var maxValue = axis_data.initialTopValue + unitsDragged; 195 if (boundedValue) { 196 maxValue = Math.min(maxValue, boundedValue[1]); 197 } 198 var minValue = maxValue - axis_data.dragValueRange; 199 if (boundedValue) { 200 if (minValue < boundedValue[0]) { 201 // Adjust maxValue, and recompute minValue. 202 maxValue = maxValue - (minValue - boundedValue[0]); 203 minValue = maxValue - axis_data.dragValueRange; 204 } 205 } 206 if (g.attributes_.getForAxis("logscale", i)) { 207 axis.valueRange = [ Math.pow(utils.LOG_SCALE, minValue), 208 Math.pow(utils.LOG_SCALE, maxValue) ]; 209 } else { 210 axis.valueRange = [ minValue, maxValue ]; 211 } 212 } 213 } 214 215 g.drawGraph_(false); 216 }; 217 218 /** 219 * Called in response to an interaction model operation that 220 * responds to an event that ends panning. 221 * 222 * It's used in the default callback for "mouseup" operations. 223 * Custom interaction model builders can use it to provide the default 224 * panning behavior. 225 * 226 * @param {Event} event the event object which led to the endPan call. 227 * @param {Dygraph} g The dygraph on which to act. 228 * @param {Object} context The dragging context object (with 229 * dragStartX/dragStartY/etc. properties). This function modifies the 230 * context. 231 */ 232 DygraphInteraction.endPan = DygraphInteraction.maybeTreatMouseOpAsClick; 233 234 /** 235 * Called in response to an interaction model operation that 236 * responds to an event that starts zooming. 237 * 238 * It's used in the default callback for "mousedown" operations. 239 * Custom interaction model builders can use it to provide the default 240 * zooming behavior. 241 * 242 * @param {Event} event the event object which led to the startZoom call. 243 * @param {Dygraph} g The dygraph on which to act. 244 * @param {Object} context The dragging context object (with 245 * dragStartX/dragStartY/etc. properties). This function modifies the 246 * context. 247 */ 248 DygraphInteraction.startZoom = function(event, g, context) { 249 context.isZooming = true; 250 context.zoomMoved = false; 251 }; 252 253 /** 254 * Called in response to an interaction model operation that 255 * responds to an event that defines zoom boundaries. 256 * 257 * It's used in the default callback for "mousemove" operations. 258 * Custom interaction model builders can use it to provide the default 259 * zooming behavior. 260 * 261 * @param {Event} event the event object which led to the moveZoom call. 262 * @param {Dygraph} g The dygraph on which to act. 263 * @param {Object} context The dragging context object (with 264 * dragStartX/dragStartY/etc. properties). This function modifies the 265 * context. 266 */ 267 DygraphInteraction.moveZoom = function(event, g, context) { 268 context.zoomMoved = true; 269 context.dragEndX = utils.dragGetX_(event, context); 270 context.dragEndY = utils.dragGetY_(event, context); 271 272 var xDelta = Math.abs(context.dragStartX - context.dragEndX); 273 var yDelta = Math.abs(context.dragStartY - context.dragEndY); 274 275 // drag direction threshold for y axis is twice as large as x axis 276 context.dragDirection = (xDelta < yDelta / 2) ? utils.VERTICAL : utils.HORIZONTAL; 277 278 g.drawZoomRect_( 279 context.dragDirection, 280 context.dragStartX, 281 context.dragEndX, 282 context.dragStartY, 283 context.dragEndY, 284 context.prevDragDirection, 285 context.prevEndX, 286 context.prevEndY); 287 288 context.prevEndX = context.dragEndX; 289 context.prevEndY = context.dragEndY; 290 context.prevDragDirection = context.dragDirection; 291 }; 292 293 /** 294 * TODO(danvk): move this logic into dygraph.js 295 * @param {Dygraph} g 296 * @param {Event} event 297 * @param {Object} context 298 */ 299 DygraphInteraction.treatMouseOpAsClick = function(g, event, context) { 300 var clickCallback = g.getFunctionOption('clickCallback'); 301 var pointClickCallback = g.getFunctionOption('pointClickCallback'); 302 303 var selectedPoint = null; 304 305 // Find out if the click occurs on a point. 306 var closestIdx = -1; 307 var closestDistance = Number.MAX_VALUE; 308 309 // check if the click was on a particular point. 310 for (var i = 0; i < g.selPoints_.length; i++) { 311 var p = g.selPoints_[i]; 312 var distance = Math.pow(p.canvasx - context.dragEndX, 2) + 313 Math.pow(p.canvasy - context.dragEndY, 2); 314 if (!isNaN(distance) && 315 (closestIdx == -1 || distance < closestDistance)) { 316 closestDistance = distance; 317 closestIdx = i; 318 } 319 } 320 321 // Allow any click within two pixels of the dot. 322 var radius = g.getNumericOption('highlightCircleSize') + 2; 323 if (closestDistance <= radius * radius) { 324 selectedPoint = g.selPoints_[closestIdx]; 325 } 326 327 if (selectedPoint) { 328 var e = { 329 cancelable: true, 330 point: selectedPoint, 331 canvasx: context.dragEndX, 332 canvasy: context.dragEndY 333 }; 334 var defaultPrevented = g.cascadeEvents_('pointClick', e); 335 if (defaultPrevented) { 336 // Note: this also prevents click / clickCallback from firing. 337 return; 338 } 339 if (pointClickCallback) { 340 pointClickCallback.call(g, event, selectedPoint); 341 } 342 } 343 344 var e = { 345 cancelable: true, 346 xval: g.lastx_, // closest point by x value 347 pts: g.selPoints_, 348 canvasx: context.dragEndX, 349 canvasy: context.dragEndY 350 }; 351 if (!g.cascadeEvents_('click', e)) { 352 if (clickCallback) { 353 // TODO(danvk): pass along more info about the points, e.g. 'x' 354 clickCallback.call(g, event, g.lastx_, g.selPoints_); 355 } 356 } 357 }; 358 359 /** 360 * Called in response to an interaction model operation that 361 * responds to an event that performs a zoom based on previously defined 362 * bounds.. 363 * 364 * It's used in the default callback for "mouseup" operations. 365 * Custom interaction model builders can use it to provide the default 366 * zooming behavior. 367 * 368 * @param {Event} event the event object which led to the endZoom call. 369 * @param {Dygraph} g The dygraph on which to end the zoom. 370 * @param {Object} context The dragging context object (with 371 * dragStartX/dragStartY/etc. properties). This function modifies the 372 * context. 373 */ 374 DygraphInteraction.endZoom = function(event, g, context) { 375 g.clearZoomRect_(); 376 context.isZooming = false; 377 DygraphInteraction.maybeTreatMouseOpAsClick(event, g, context); 378 379 // The zoom rectangle is visibly clipped to the plot area, so its behavior 380 // should be as well. 381 // See http://code.google.com/p/dygraphs/issues/detail?id=280 382 var plotArea = g.getArea(); 383 if (context.regionWidth >= 10 && 384 context.dragDirection == utils.HORIZONTAL) { 385 var left = Math.min(context.dragStartX, context.dragEndX), 386 right = Math.max(context.dragStartX, context.dragEndX); 387 left = Math.max(left, plotArea.x); 388 right = Math.min(right, plotArea.x + plotArea.w); 389 if (left < right) { 390 g.doZoomX_(left, right); 391 } 392 context.cancelNextDblclick = true; 393 } else if (context.regionHeight >= 10 && 394 context.dragDirection == utils.VERTICAL) { 395 var top = Math.min(context.dragStartY, context.dragEndY), 396 bottom = Math.max(context.dragStartY, context.dragEndY); 397 top = Math.max(top, plotArea.y); 398 bottom = Math.min(bottom, plotArea.y + plotArea.h); 399 if (top < bottom) { 400 g.doZoomY_(top, bottom); 401 } 402 context.cancelNextDblclick = true; 403 } 404 context.dragStartX = null; 405 context.dragStartY = null; 406 }; 407 408 /** 409 * @private 410 */ 411 DygraphInteraction.startTouch = function(event, g, context) { 412 event.preventDefault(); // touch browsers are all nice. 413 if (event.touches.length > 1) { 414 // If the user ever puts two fingers down, it's not a double tap. 415 context.startTimeForDoubleTapMs = null; 416 } 417 418 var touches = []; 419 for (var i = 0; i < event.touches.length; i++) { 420 var t = event.touches[i]; 421 // we dispense with 'dragGetX_' because all touchBrowsers support pageX 422 touches.push({ 423 pageX: t.pageX, 424 pageY: t.pageY, 425 dataX: g.toDataXCoord(t.pageX), 426 dataY: g.toDataYCoord(t.pageY) 427 // identifier: t.identifier 428 }); 429 } 430 context.initialTouches = touches; 431 432 if (touches.length == 1) { 433 // This is just a swipe. 434 context.initialPinchCenter = touches[0]; 435 context.touchDirections = { x: true, y: true }; 436 } else if (touches.length >= 2) { 437 // It's become a pinch! 438 // In case there are 3+ touches, we ignore all but the "first" two. 439 440 // only screen coordinates can be averaged (data coords could be log scale). 441 context.initialPinchCenter = { 442 pageX: 0.5 * (touches[0].pageX + touches[1].pageX), 443 pageY: 0.5 * (touches[0].pageY + touches[1].pageY), 444 445 // TODO(danvk): remove 446 dataX: 0.5 * (touches[0].dataX + touches[1].dataX), 447 dataY: 0.5 * (touches[0].dataY + touches[1].dataY) 448 }; 449 450 // Make pinches in a 45-degree swath around either axis 1-dimensional zooms. 451 var initialAngle = 180 / Math.PI * Math.atan2( 452 context.initialPinchCenter.pageY - touches[0].pageY, 453 touches[0].pageX - context.initialPinchCenter.pageX); 454 455 // use symmetry to get it into the first quadrant. 456 initialAngle = Math.abs(initialAngle); 457 if (initialAngle > 90) initialAngle = 90 - initialAngle; 458 459 context.touchDirections = { 460 x: (initialAngle < (90 - 45/2)), 461 y: (initialAngle > 45/2) 462 }; 463 } 464 465 // save the full x & y ranges. 466 context.initialRange = { 467 x: g.xAxisRange(), 468 y: g.yAxisRange() 469 }; 470 }; 471 472 /** 473 * @private 474 */ 475 DygraphInteraction.moveTouch = function(event, g, context) { 476 // If the tap moves, then it's definitely not part of a double-tap. 477 context.startTimeForDoubleTapMs = null; 478 479 var i, touches = []; 480 for (i = 0; i < event.touches.length; i++) { 481 var t = event.touches[i]; 482 touches.push({ 483 pageX: t.pageX, 484 pageY: t.pageY 485 }); 486 } 487 var initialTouches = context.initialTouches; 488 489 var c_now; 490 491 // old and new centers. 492 var c_init = context.initialPinchCenter; 493 if (touches.length == 1) { 494 c_now = touches[0]; 495 } else { 496 c_now = { 497 pageX: 0.5 * (touches[0].pageX + touches[1].pageX), 498 pageY: 0.5 * (touches[0].pageY + touches[1].pageY) 499 }; 500 } 501 502 // this is the "swipe" component 503 // we toss it out for now, but could use it in the future. 504 var swipe = { 505 pageX: c_now.pageX - c_init.pageX, 506 pageY: c_now.pageY - c_init.pageY 507 }; 508 var dataWidth = context.initialRange.x[1] - context.initialRange.x[0]; 509 var dataHeight = context.initialRange.y[0] - context.initialRange.y[1]; 510 swipe.dataX = (swipe.pageX / g.plotter_.area.w) * dataWidth; 511 swipe.dataY = (swipe.pageY / g.plotter_.area.h) * dataHeight; 512 var xScale, yScale; 513 514 // The residual bits are usually split into scale & rotate bits, but we split 515 // them into x-scale and y-scale bits. 516 if (touches.length == 1) { 517 xScale = 1.0; 518 yScale = 1.0; 519 } else if (touches.length >= 2) { 520 var initHalfWidth = (initialTouches[1].pageX - c_init.pageX); 521 xScale = (touches[1].pageX - c_now.pageX) / initHalfWidth; 522 523 var initHalfHeight = (initialTouches[1].pageY - c_init.pageY); 524 yScale = (touches[1].pageY - c_now.pageY) / initHalfHeight; 525 } 526 527 // Clip scaling to [1/8, 8] to prevent too much blowup. 528 xScale = Math.min(8, Math.max(0.125, xScale)); 529 yScale = Math.min(8, Math.max(0.125, yScale)); 530 531 var didZoom = false; 532 if (context.touchDirections.x) { 533 g.dateWindow_ = [ 534 c_init.dataX - swipe.dataX + (context.initialRange.x[0] - c_init.dataX) / xScale, 535 c_init.dataX - swipe.dataX + (context.initialRange.x[1] - c_init.dataX) / xScale 536 ]; 537 didZoom = true; 538 } 539 540 if (context.touchDirections.y) { 541 for (i = 0; i < 1 /*g.axes_.length*/; i++) { 542 var axis = g.axes_[i]; 543 var logscale = g.attributes_.getForAxis("logscale", i); 544 if (logscale) { 545 // TODO(danvk): implement 546 } else { 547 axis.valueRange = [ 548 c_init.dataY - swipe.dataY + (context.initialRange.y[0] - c_init.dataY) / yScale, 549 c_init.dataY - swipe.dataY + (context.initialRange.y[1] - c_init.dataY) / yScale 550 ]; 551 didZoom = true; 552 } 553 } 554 } 555 556 g.drawGraph_(false); 557 558 // We only call zoomCallback on zooms, not pans, to mirror desktop behavior. 559 if (didZoom && touches.length > 1 && g.getFunctionOption('zoomCallback')) { 560 var viewWindow = g.xAxisRange(); 561 g.getFunctionOption("zoomCallback").call(g, viewWindow[0], viewWindow[1], g.yAxisRanges()); 562 } 563 }; 564 565 /** 566 * @private 567 */ 568 DygraphInteraction.endTouch = function(event, g, context) { 569 if (event.touches.length !== 0) { 570 // this is effectively a "reset" 571 DygraphInteraction.startTouch(event, g, context); 572 } else if (event.changedTouches.length == 1) { 573 // Could be part of a "double tap" 574 // The heuristic here is that it's a double-tap if the two touchend events 575 // occur within 500ms and within a 50x50 pixel box. 576 var now = new Date().getTime(); 577 var t = event.changedTouches[0]; 578 if (context.startTimeForDoubleTapMs && 579 now - context.startTimeForDoubleTapMs < 500 && 580 context.doubleTapX && Math.abs(context.doubleTapX - t.screenX) < 50 && 581 context.doubleTapY && Math.abs(context.doubleTapY - t.screenY) < 50) { 582 g.resetZoom(); 583 } else { 584 context.startTimeForDoubleTapMs = now; 585 context.doubleTapX = t.screenX; 586 context.doubleTapY = t.screenY; 587 } 588 } 589 }; 590 591 // Determine the distance from x to [left, right]. 592 var distanceFromInterval = function(x, left, right) { 593 if (x < left) { 594 return left - x; 595 } else if (x > right) { 596 return x - right; 597 } else { 598 return 0; 599 } 600 }; 601 602 /** 603 * Returns the number of pixels by which the event happens from the nearest 604 * edge of the chart. For events in the interior of the chart, this returns zero. 605 */ 606 var distanceFromChart = function(event, g) { 607 var chartPos = utils.findPos(g.canvas_); 608 var box = { 609 left: chartPos.x, 610 right: chartPos.x + g.canvas_.offsetWidth, 611 top: chartPos.y, 612 bottom: chartPos.y + g.canvas_.offsetHeight 613 }; 614 615 var pt = { 616 x: utils.pageX(event), 617 y: utils.pageY(event) 618 }; 619 620 var dx = distanceFromInterval(pt.x, box.left, box.right), 621 dy = distanceFromInterval(pt.y, box.top, box.bottom); 622 return Math.max(dx, dy); 623 }; 624 625 /** 626 * Default interation model for dygraphs. You can refer to specific elements of 627 * this when constructing your own interaction model, e.g.: 628 * g.updateOptions( { 629 * interactionModel: { 630 * mousedown: DygraphInteraction.defaultInteractionModel.mousedown 631 * } 632 * } ); 633 */ 634 DygraphInteraction.defaultModel = { 635 // Track the beginning of drag events 636 mousedown: function(event, g, context) { 637 // Right-click should not initiate a zoom. 638 if (event.button && event.button == 2) return; 639 640 context.initializeMouseDown(event, g, context); 641 642 if (event.altKey || event.shiftKey) { 643 DygraphInteraction.startPan(event, g, context); 644 } else { 645 DygraphInteraction.startZoom(event, g, context); 646 } 647 648 // Note: we register mousemove/mouseup on document to allow some leeway for 649 // events to move outside of the chart. Interaction model events get 650 // registered on the canvas, which is too small to allow this. 651 var mousemove = function(event) { 652 if (context.isZooming) { 653 // When the mouse moves >200px from the chart edge, cancel the zoom. 654 var d = distanceFromChart(event, g); 655 if (d < DRAG_EDGE_MARGIN) { 656 DygraphInteraction.moveZoom(event, g, context); 657 } else { 658 if (context.dragEndX !== null) { 659 context.dragEndX = null; 660 context.dragEndY = null; 661 g.clearZoomRect_(); 662 } 663 } 664 } else if (context.isPanning) { 665 DygraphInteraction.movePan(event, g, context); 666 } 667 }; 668 var mouseup = function(event) { 669 if (context.isZooming) { 670 if (context.dragEndX !== null) { 671 DygraphInteraction.endZoom(event, g, context); 672 } else { 673 DygraphInteraction.maybeTreatMouseOpAsClick(event, g, context); 674 } 675 } else if (context.isPanning) { 676 DygraphInteraction.endPan(event, g, context); 677 } 678 679 utils.removeEvent(document, 'mousemove', mousemove); 680 utils.removeEvent(document, 'mouseup', mouseup); 681 context.destroy(); 682 }; 683 684 g.addAndTrackEvent(document, 'mousemove', mousemove); 685 g.addAndTrackEvent(document, 'mouseup', mouseup); 686 }, 687 willDestroyContextMyself: true, 688 689 touchstart: function(event, g, context) { 690 DygraphInteraction.startTouch(event, g, context); 691 }, 692 touchmove: function(event, g, context) { 693 DygraphInteraction.moveTouch(event, g, context); 694 }, 695 touchend: function(event, g, context) { 696 DygraphInteraction.endTouch(event, g, context); 697 }, 698 699 // Disable zooming out if panning. 700 dblclick: function(event, g, context) { 701 if (context.cancelNextDblclick) { 702 context.cancelNextDblclick = false; 703 return; 704 } 705 706 // Give plugins a chance to grab this event. 707 var e = { 708 canvasx: context.dragEndX, 709 canvasy: context.dragEndY, 710 cancelable: true, 711 }; 712 if (g.cascadeEvents_('dblclick', e)) { 713 return; 714 } 715 716 if (event.altKey || event.shiftKey) { 717 return; 718 } 719 g.resetZoom(); 720 } 721 }; 722 723 /* 724 Dygraph.DEFAULT_ATTRS.interactionModel = DygraphInteraction.defaultModel; 725 726 // old ways of accessing these methods/properties 727 Dygraph.defaultInteractionModel = DygraphInteraction.defaultModel; 728 Dygraph.endZoom = DygraphInteraction.endZoom; 729 Dygraph.moveZoom = DygraphInteraction.moveZoom; 730 Dygraph.startZoom = DygraphInteraction.startZoom; 731 Dygraph.endPan = DygraphInteraction.endPan; 732 Dygraph.movePan = DygraphInteraction.movePan; 733 Dygraph.startPan = DygraphInteraction.startPan; 734 */ 735 736 DygraphInteraction.nonInteractiveModel_ = { 737 mousedown: function(event, g, context) { 738 context.initializeMouseDown(event, g, context); 739 }, 740 mouseup: DygraphInteraction.maybeTreatMouseOpAsClick 741 }; 742 743 // Default interaction model when using the range selector. 744 DygraphInteraction.dragIsPanInteractionModel = { 745 mousedown: function(event, g, context) { 746 context.initializeMouseDown(event, g, context); 747 DygraphInteraction.startPan(event, g, context); 748 }, 749 mousemove: function(event, g, context) { 750 if (context.isPanning) { 751 DygraphInteraction.movePan(event, g, context); 752 } 753 }, 754 mouseup: function(event, g, context) { 755 if (context.isPanning) { 756 DygraphInteraction.endPan(event, g, context); 757 } 758 } 759 }; 760 761 export default DygraphInteraction; 762