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 var rect = t.target.getBoundingClientRect() 422 // we dispense with 'dragGetX_' because all touchBrowsers support pageX 423 touches.push({ 424 pageX: t.pageX, 425 pageY: t.pageY, 426 dataX: g.toDataXCoord(t.clientX - rect.left), 427 dataY: g.toDataYCoord(t.clientY - rect.top) 428 // identifier: t.identifier 429 }); 430 } 431 context.initialTouches = touches; 432 433 if (touches.length == 1) { 434 // This is just a swipe. 435 context.initialPinchCenter = touches[0]; 436 context.touchDirections = { x: true, y: true }; 437 } else if (touches.length >= 2) { 438 // It's become a pinch! 439 // In case there are 3+ touches, we ignore all but the "first" two. 440 441 // only screen coordinates can be averaged (data coords could be log scale). 442 context.initialPinchCenter = { 443 pageX: 0.5 * (touches[0].pageX + touches[1].pageX), 444 pageY: 0.5 * (touches[0].pageY + touches[1].pageY), 445 446 // TODO(danvk): remove 447 dataX: 0.5 * (touches[0].dataX + touches[1].dataX), 448 dataY: 0.5 * (touches[0].dataY + touches[1].dataY) 449 }; 450 451 // Make pinches in a 45-degree swath around either axis 1-dimensional zooms. 452 var initialAngle = 180 / Math.PI * Math.atan2( 453 context.initialPinchCenter.pageY - touches[0].pageY, 454 touches[0].pageX - context.initialPinchCenter.pageX); 455 456 // use symmetry to get it into the first quadrant. 457 initialAngle = Math.abs(initialAngle); 458 if (initialAngle > 90) initialAngle = 90 - initialAngle; 459 460 context.touchDirections = { 461 x: (initialAngle < (90 - 45/2)), 462 y: (initialAngle > 45/2) 463 }; 464 } 465 466 // save the full x & y ranges. 467 context.initialRange = { 468 x: g.xAxisRange(), 469 y: g.yAxisRange() 470 }; 471 }; 472 473 /** 474 * @private 475 */ 476 DygraphInteraction.moveTouch = function(event, g, context) { 477 // If the tap moves, then it's definitely not part of a double-tap. 478 context.startTimeForDoubleTapMs = null; 479 480 var i, touches = []; 481 for (i = 0; i < event.touches.length; i++) { 482 var t = event.touches[i]; 483 touches.push({ 484 pageX: t.pageX, 485 pageY: t.pageY 486 }); 487 } 488 var initialTouches = context.initialTouches; 489 490 var c_now; 491 492 // old and new centers. 493 var c_init = context.initialPinchCenter; 494 if (touches.length == 1) { 495 c_now = touches[0]; 496 } else { 497 c_now = { 498 pageX: 0.5 * (touches[0].pageX + touches[1].pageX), 499 pageY: 0.5 * (touches[0].pageY + touches[1].pageY) 500 }; 501 } 502 503 // this is the "swipe" component 504 // we toss it out for now, but could use it in the future. 505 var swipe = { 506 pageX: c_now.pageX - c_init.pageX, 507 pageY: c_now.pageY - c_init.pageY 508 }; 509 var dataWidth = context.initialRange.x[1] - context.initialRange.x[0]; 510 var dataHeight = context.initialRange.y[0] - context.initialRange.y[1]; 511 swipe.dataX = (swipe.pageX / g.plotter_.area.w) * dataWidth; 512 swipe.dataY = (swipe.pageY / g.plotter_.area.h) * dataHeight; 513 var xScale, yScale; 514 515 // The residual bits are usually split into scale & rotate bits, but we split 516 // them into x-scale and y-scale bits. 517 if (touches.length == 1) { 518 xScale = 1.0; 519 yScale = 1.0; 520 } else if (touches.length >= 2) { 521 var initHalfWidth = (initialTouches[1].pageX - c_init.pageX); 522 xScale = (touches[1].pageX - c_now.pageX) / initHalfWidth; 523 524 var initHalfHeight = (initialTouches[1].pageY - c_init.pageY); 525 yScale = (touches[1].pageY - c_now.pageY) / initHalfHeight; 526 } 527 528 // Clip scaling to [1/8, 8] to prevent too much blowup. 529 xScale = Math.min(8, Math.max(0.125, xScale)); 530 yScale = Math.min(8, Math.max(0.125, yScale)); 531 532 var didZoom = false; 533 if (context.touchDirections.x) { 534 var cFactor = c_init.dataX - swipe.dataX / xScale; 535 g.dateWindow_ = [ 536 cFactor + (context.initialRange.x[0] - c_init.dataX) / xScale, 537 cFactor + (context.initialRange.x[1] - c_init.dataX) / xScale 538 ]; 539 didZoom = true; 540 } 541 542 if (context.touchDirections.y) { 543 for (i = 0; i < 1 /*g.axes_.length*/; i++) { 544 var axis = g.axes_[i]; 545 var logscale = g.attributes_.getForAxis("logscale", i); 546 if (logscale) { 547 // TODO(danvk): implement 548 } else { 549 var cFactor = c_init.dataY - swipe.dataY / yScale; 550 axis.valueRange = [ 551 cFactor + (context.initialRange.y[0] - c_init.dataY) / yScale, 552 cFactor + (context.initialRange.y[1] - c_init.dataY) / yScale 553 ]; 554 didZoom = true; 555 } 556 } 557 } 558 559 g.drawGraph_(false); 560 561 // We only call zoomCallback on zooms, not pans, to mirror desktop behavior. 562 if (didZoom && touches.length > 1 && g.getFunctionOption('zoomCallback')) { 563 var viewWindow = g.xAxisRange(); 564 g.getFunctionOption("zoomCallback").call(g, viewWindow[0], viewWindow[1], g.yAxisRanges()); 565 } 566 }; 567 568 /** 569 * @private 570 */ 571 DygraphInteraction.endTouch = function(event, g, context) { 572 if (event.touches.length !== 0) { 573 // this is effectively a "reset" 574 DygraphInteraction.startTouch(event, g, context); 575 } else if (event.changedTouches.length == 1) { 576 // Could be part of a "double tap" 577 // The heuristic here is that it's a double-tap if the two touchend events 578 // occur within 500ms and within a 50x50 pixel box. 579 var now = new Date().getTime(); 580 var t = event.changedTouches[0]; 581 if (context.startTimeForDoubleTapMs && 582 now - context.startTimeForDoubleTapMs < 500 && 583 context.doubleTapX && Math.abs(context.doubleTapX - t.screenX) < 50 && 584 context.doubleTapY && Math.abs(context.doubleTapY - t.screenY) < 50) { 585 g.resetZoom(); 586 } else { 587 context.startTimeForDoubleTapMs = now; 588 context.doubleTapX = t.screenX; 589 context.doubleTapY = t.screenY; 590 } 591 } 592 }; 593 594 // Determine the distance from x to [left, right]. 595 var distanceFromInterval = function(x, left, right) { 596 if (x < left) { 597 return left - x; 598 } else if (x > right) { 599 return x - right; 600 } else { 601 return 0; 602 } 603 }; 604 605 /** 606 * Returns the number of pixels by which the event happens from the nearest 607 * edge of the chart. For events in the interior of the chart, this returns zero. 608 */ 609 var distanceFromChart = function(event, g) { 610 var chartPos = utils.findPos(g.canvas_); 611 var box = { 612 left: chartPos.x, 613 right: chartPos.x + g.canvas_.offsetWidth, 614 top: chartPos.y, 615 bottom: chartPos.y + g.canvas_.offsetHeight 616 }; 617 618 var pt = { 619 x: utils.pageX(event), 620 y: utils.pageY(event) 621 }; 622 623 var dx = distanceFromInterval(pt.x, box.left, box.right), 624 dy = distanceFromInterval(pt.y, box.top, box.bottom); 625 return Math.max(dx, dy); 626 }; 627 628 /** 629 * Default interation model for dygraphs. You can refer to specific elements of 630 * this when constructing your own interaction model, e.g.: 631 * g.updateOptions( { 632 * interactionModel: { 633 * mousedown: DygraphInteraction.defaultInteractionModel.mousedown 634 * } 635 * } ); 636 */ 637 DygraphInteraction.defaultModel = { 638 // Track the beginning of drag events 639 mousedown: function(event, g, context) { 640 // Right-click should not initiate a zoom. 641 if (event.button && event.button == 2) return; 642 643 context.initializeMouseDown(event, g, context); 644 645 if (event.altKey || event.shiftKey) { 646 DygraphInteraction.startPan(event, g, context); 647 } else { 648 DygraphInteraction.startZoom(event, g, context); 649 } 650 651 // Note: we register mousemove/mouseup on document to allow some leeway for 652 // events to move outside of the chart. Interaction model events get 653 // registered on the canvas, which is too small to allow this. 654 var mousemove = function(event) { 655 if (context.isZooming) { 656 // When the mouse moves >200px from the chart edge, cancel the zoom. 657 var d = distanceFromChart(event, g); 658 if (d < DRAG_EDGE_MARGIN) { 659 DygraphInteraction.moveZoom(event, g, context); 660 } else { 661 if (context.dragEndX !== null) { 662 context.dragEndX = null; 663 context.dragEndY = null; 664 g.clearZoomRect_(); 665 } 666 } 667 } else if (context.isPanning) { 668 DygraphInteraction.movePan(event, g, context); 669 } 670 }; 671 var mouseup = function(event) { 672 if (context.isZooming) { 673 if (context.dragEndX !== null) { 674 DygraphInteraction.endZoom(event, g, context); 675 } else { 676 DygraphInteraction.maybeTreatMouseOpAsClick(event, g, context); 677 } 678 } else if (context.isPanning) { 679 DygraphInteraction.endPan(event, g, context); 680 } 681 682 utils.removeEvent(document, 'mousemove', mousemove); 683 utils.removeEvent(document, 'mouseup', mouseup); 684 context.destroy(); 685 }; 686 687 g.addAndTrackEvent(document, 'mousemove', mousemove); 688 g.addAndTrackEvent(document, 'mouseup', mouseup); 689 }, 690 willDestroyContextMyself: true, 691 692 touchstart: function(event, g, context) { 693 DygraphInteraction.startTouch(event, g, context); 694 }, 695 touchmove: function(event, g, context) { 696 DygraphInteraction.moveTouch(event, g, context); 697 }, 698 touchend: function(event, g, context) { 699 DygraphInteraction.endTouch(event, g, context); 700 }, 701 702 // Disable zooming out if panning. 703 dblclick: function(event, g, context) { 704 if (context.cancelNextDblclick) { 705 context.cancelNextDblclick = false; 706 return; 707 } 708 709 // Give plugins a chance to grab this event. 710 var e = { 711 canvasx: context.dragEndX, 712 canvasy: context.dragEndY, 713 cancelable: true, 714 }; 715 if (g.cascadeEvents_('dblclick', e)) { 716 return; 717 } 718 719 if (event.altKey || event.shiftKey) { 720 return; 721 } 722 g.resetZoom(); 723 } 724 }; 725 726 /* 727 Dygraph.DEFAULT_ATTRS.interactionModel = DygraphInteraction.defaultModel; 728 729 // old ways of accessing these methods/properties 730 Dygraph.defaultInteractionModel = DygraphInteraction.defaultModel; 731 Dygraph.endZoom = DygraphInteraction.endZoom; 732 Dygraph.moveZoom = DygraphInteraction.moveZoom; 733 Dygraph.startZoom = DygraphInteraction.startZoom; 734 Dygraph.endPan = DygraphInteraction.endPan; 735 Dygraph.movePan = DygraphInteraction.movePan; 736 Dygraph.startPan = DygraphInteraction.startPan; 737 */ 738 739 DygraphInteraction.nonInteractiveModel_ = { 740 mousedown: function(event, g, context) { 741 context.initializeMouseDown(event, g, context); 742 }, 743 mouseup: DygraphInteraction.maybeTreatMouseOpAsClick 744 }; 745 746 // Default interaction model when using the range selector. 747 DygraphInteraction.dragIsPanInteractionModel = { 748 mousedown: function(event, g, context) { 749 context.initializeMouseDown(event, g, context); 750 DygraphInteraction.startPan(event, g, context); 751 }, 752 mousemove: function(event, g, context) { 753 if (context.isPanning) { 754 DygraphInteraction.movePan(event, g, context); 755 } 756 }, 757 mouseup: function(event, g, context) { 758 if (context.isPanning) { 759 DygraphInteraction.endPan(event, g, context); 760 } 761 } 762 }; 763 764 export default DygraphInteraction; 765