vendor: 33,027 bytes, lines 1-979
1/*! 2* chartjs-plugin-zoom v2.2.0 3* https://www.chartjs.org/chartjs-plugin-zoom/2.2.0/ 4 * (c) 2016-2024 chartjs-plugin-zoom Contributors 5 * Released under the MIT License 6 */ 7(function (global, factory) { 8typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('chart.js'), require('hammerjs'), require('chart.js/helpers')) : 9typeof define === 'function' && define.amd ? define(['chart.js', 'hammerjs', 'chart.js/helpers'], factory) : 10(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ChartZoom = factory(global.Chart, global.Hammer, global.Chart.helpers)); 11})(this, (function (chart_js, Hammer, helpers) { 'use strict'; 12 13const getModifierKey = opts => opts && opts.enabled && opts.modifierKey; 14const keyPressed = (key, event) => key && event[key + 'Key']; 15const keyNotPressed = (key, event) => key && !event[key + 'Key']; 16function directionEnabled(mode, dir, chart) { 17 if (mode === undefined) { 18 return true; 19 } else if (typeof mode === 'string') { 20 return mode.indexOf(dir) !== -1; 21 } else if (typeof mode === 'function') { 22 return mode({chart}).indexOf(dir) !== -1; 23 } 24 return false; 25} 26function directionsEnabled(mode, chart) { 27 if (typeof mode === 'function') { 28 mode = mode({chart}); 29 } 30 if (typeof mode === 'string') { 31 return {x: mode.indexOf('x') !== -1, y: mode.indexOf('y') !== -1}; 32 } 33 return {x: false, y: false}; 34} 35function debounce(fn, delay) { 36 let timeout; 37 return function() { 38 clearTimeout(timeout); 39 timeout = setTimeout(fn, delay); 40 return delay; 41 }; 42} 43function getScaleUnderPoint({x, y}, chart) { 44 const scales = chart.scales; 45 const scaleIds = Object.keys(scales); 46 for (let i = 0; i < scaleIds.length; i++) { 47 const scale = scales[scaleIds[i]]; 48 if (y >= scale.top && y <= scale.bottom && x >= scale.left && x <= scale.right) { 49 return scale; 50 } 51 } 52 return null; 53} 54function getEnabledScalesByPoint(options, point, chart) { 55 const {mode = 'xy', scaleMode, overScaleMode} = options || {}; 56 const scale = getScaleUnderPoint(point, chart); 57 const enabled = directionsEnabled(mode, chart); 58 const scaleEnabled = directionsEnabled(scaleMode, chart); 59 if (overScaleMode) { 60 const overScaleEnabled = directionsEnabled(overScaleMode, chart); 61 for (const axis of ['x', 'y']) { 62 if (overScaleEnabled[axis]) { 63 scaleEnabled[axis] = enabled[axis]; 64 enabled[axis] = false; 65 } 66 } 67 } 68 if (scale && scaleEnabled[scale.axis]) { 69 return [scale]; 70 } 71 const enabledScales = []; 72 helpers.each(chart.scales, function(scaleItem) { 73 if (enabled[scaleItem.axis]) { 74 enabledScales.push(scaleItem); 75 } 76 }); 77 return enabledScales; 78} 79 80const chartStates = new WeakMap(); 81function getState(chart) { 82 let state = chartStates.get(chart); 83 if (!state) { 84 state = { 85 originalScaleLimits: {}, 86 updatedScaleLimits: {}, 87 handlers: {}, 88 panDelta: {}, 89 dragging: false, 90 panning: false 91 }; 92 chartStates.set(chart, state); 93 } 94 return state; 95} 96function removeState(chart) { 97 chartStates.delete(chart); 98} 99 100function zoomDelta(val, min, range, newRange) { 101 const minPercent = Math.max(0, Math.min(1, (val - min) / range || 0)); 102 const maxPercent = 1 - minPercent; 103 return { 104 min: newRange * minPercent, 105 max: newRange * maxPercent 106 }; 107} 108function getValueAtPoint(scale, point) { 109 const pixel = scale.isHorizontal() ? point.x : point.y; 110 return scale.getValueForPixel(pixel); 111} 112function linearZoomDelta(scale, zoom, center) { 113 const range = scale.max - scale.min; 114 const newRange = range * (zoom - 1); 115 const centerValue = getValueAtPoint(scale, center); 116 return zoomDelta(centerValue, scale.min, range, newRange); 117} 118function logarithmicZoomRange(scale, zoom, center) { 119 const centerValue = getValueAtPoint(scale, center); 120 if (centerValue === undefined) { 121 return {min: scale.min, max: scale.max}; 122 } 123 const logMin = Math.log10(scale.min); 124 const logMax = Math.log10(scale.max); 125 const logCenter = Math.log10(centerValue); 126 const logRange = logMax - logMin; 127 const newLogRange = logRange * (zoom - 1); 128 const delta = zoomDelta(logCenter, logMin, logRange, newLogRange); 129 return { 130 min: Math.pow(10, logMin + delta.min), 131 max: Math.pow(10, logMax - delta.max), 132 }; 133} 134function getScaleLimits(scale, limits) { 135 return limits && (limits[scale.id] || limits[scale.axis]) || {}; 136} 137function getLimit(state, scale, scaleLimits, prop, fallback) { 138 let limit = scaleLimits[prop]; 139 if (limit === 'original') { 140 const original = state.originalScaleLimits[scale.id][prop]; 141 limit = helpers.valueOrDefault(original.options, original.scale); 142 } 143 return helpers.valueOrDefault(limit, fallback); 144} 145function linearRange(scale, pixel0, pixel1) { 146 const v0 = scale.getValueForPixel(pixel0); 147 const v1 = scale.getValueForPixel(pixel1); 148 return { 149 min: Math.min(v0, v1), 150 max: Math.max(v0, v1) 151 }; 152} 153function fixRange(range, {min, max, minLimit, maxLimit}, originalLimits) { 154 const offset = (range - max + min) / 2; 155 min -= offset; 156 max += offset; 157 const origMin = originalLimits.min.options ?? originalLimits.min.scale; 158 const origMax = originalLimits.max.options ?? originalLimits.max.scale; 159 const epsilon = range / 1e6; 160 if (helpers.almostEquals(min, origMin, epsilon)) { 161 min = origMin; 162 } 163 if (helpers.almostEquals(max, origMax, epsilon)) { 164 max = origMax; 165 } 166 if (min < minLimit) { 167 min = minLimit; 168 max = Math.min(minLimit + range, maxLimit); 169 } else if (max > maxLimit) { 170 max = maxLimit; 171 min = Math.max(maxLimit - range, minLimit); 172 } 173 return {min, max}; 174} 175function updateRange(scale, {min, max}, limits, zoom = false) { 176 const state = getState(scale.chart); 177 const {options: scaleOpts} = scale; 178 const scaleLimits = getScaleLimits(scale, limits); 179 const {minRange = 0} = scaleLimits; 180 const minLimit = getLimit(state, scale, scaleLimits, 'min', -Infinity); 181 const maxLimit = getLimit(state, scale, scaleLimits, 'max', Infinity); 182 if (zoom === 'pan' && (min < minLimit || max > maxLimit)) { 183 return true; 184 } 185 const scaleRange = scale.max - scale.min; 186 const range = zoom ? Math.max(max - min, minRange) : scaleRange; 187 if (zoom && range === minRange && scaleRange <= minRange) { 188 return true; 189 } 190 const newRange = fixRange(range, {min, max, minLimit, maxLimit}, state.originalScaleLimits[scale.id]); 191 scaleOpts.min = newRange.min; 192 scaleOpts.max = newRange.max; 193 state.updatedScaleLimits[scale.id] = newRange; 194 return scale.parse(newRange.min) !== scale.min || scale.parse(newRange.max) !== scale.max; 195} 196function zoomNumericalScale(scale, zoom, center, limits) { 197 const delta = linearZoomDelta(scale, zoom, center); 198 const newRange = {min: scale.min + delta.min, max: scale.max - delta.max}; 199 return updateRange(scale, newRange, limits, true); 200} 201function zoomLogarithmicScale(scale, zoom, center, limits) { 202 const newRange = logarithmicZoomRange(scale, zoom, center); 203 return updateRange(scale, newRange, limits, true); 204} 205function zoomRectNumericalScale(scale, from, to, limits) { 206 updateRange(scale, linearRange(scale, from, to), limits, true); 207} 208const integerChange = (v) => v === 0 || isNaN(v) ? 0 : v < 0 ? Math.min(Math.round(v), -1) : Math.max(Math.round(v), 1); 209function existCategoryFromMaxZoom(scale) { 210 const labels = scale.getLabels(); 211 const maxIndex = labels.length - 1; 212 if (scale.min > 0) { 213 scale.min -= 1; 214 } 215 if (scale.max < maxIndex) { 216 scale.max += 1; 217 } 218} 219function zoomCategoryScale(scale, zoom, center, limits) { 220 const delta = linearZoomDelta(scale, zoom, center); 221 if (scale.min === scale.max && zoom < 1) { 222 existCategoryFromMaxZoom(scale); 223 } 224 const newRange = {min: scale.min + integerChange(delta.min), max: scale.max - integerChange(delta.max)}; 225 return updateRange(scale, newRange, limits, true); 226} 227function scaleLength(scale) { 228 return scale.isHorizontal() ? scale.width : scale.height; 229} 230function panCategoryScale(scale, delta, limits) { 231 const labels = scale.getLabels(); 232 const lastLabelIndex = labels.length - 1; 233 let {min, max} = scale; 234 const range = Math.max(max - min, 1); 235 const stepDelta = Math.round(scaleLength(scale) / Math.max(range, 10)); 236 const stepSize = Math.round(Math.abs(delta / stepDelta)); 237 let applied; 238 if (delta < -stepDelta) { 239 max = Math.min(max + stepSize, lastLabelIndex); 240 min = range === 1 ? max : max - range; 241 applied = max === lastLabelIndex; 242 } else if (delta > stepDelta) { 243 min = Math.max(0, min - stepSize); 244 max = range === 1 ? min : min + range; 245 applied = min === 0; 246 } 247 return updateRange(scale, {min, max}, limits) || applied; 248} 249const OFFSETS = { 250 second: 500, 251 minute: 30 * 1000, 252 hour: 30 * 60 * 1000, 253 day: 12 * 60 * 60 * 1000, 254 week: 3.5 * 24 * 60 * 60 * 1000, 255 month: 15 * 24 * 60 * 60 * 1000, 256 quarter: 60 * 24 * 60 * 60 * 1000, 257 year: 182 * 24 * 60 * 60 * 1000 258}; 259function panNumericalScale(scale, delta, limits, pan = false) { 260 const {min: prevStart, max: prevEnd, options} = scale; 261 const round = options.time && options.time.round; 262 const offset = OFFSETS[round] || 0; 263 const newMin = scale.getValueForPixel(scale.getPixelForValue(prevStart + offset) - delta); 264 const newMax = scale.getValueForPixel(scale.getPixelForValue(prevEnd + offset) - delta); 265 if (isNaN(newMin) || isNaN(newMax)) { 266 return true; 267 } 268 return updateRange(scale, {min: newMin, max: newMax}, limits, pan ? 'pan' : false); 269} 270function panNonLinearScale(scale, delta, limits) { 271 return panNumericalScale(scale, delta, limits, true); 272} 273const zoomFunctions = { 274 category: zoomCategoryScale, 275 default: zoomNumericalScale, 276 logarithmic: zoomLogarithmicScale, 277}; 278const zoomRectFunctions = { 279 default: zoomRectNumericalScale, 280}; 281const panFunctions = { 282 category: panCategoryScale, 283 default: panNumericalScale, 284 logarithmic: panNonLinearScale, 285 timeseries: panNonLinearScale, 286}; 287 288function shouldUpdateScaleLimits(scale, originalScaleLimits, updatedScaleLimits) { 289 const {id, options: {min, max}} = scale; 290 if (!originalScaleLimits[id] || !updatedScaleLimits[id]) { 291 return true; 292 } 293 const previous = updatedScaleLimits[id]; 294 return previous.min !== min || previous.max !== max; 295} 296function removeMissingScales(limits, scales) { 297 helpers.each(limits, (opt, key) => { 298 if (!scales[key]) { 299 delete limits[key]; 300 } 301 }); 302} 303function storeOriginalScaleLimits(chart, state) { 304 const {scales} = chart; 305 const {originalScaleLimits, updatedScaleLimits} = state; 306 helpers.each(scales, function(scale) { 307 if (shouldUpdateScaleLimits(scale, originalScaleLimits, updatedScaleLimits)) { 308 originalScaleLimits[scale.id] = { 309 min: {scale: scale.min, options: scale.options.min}, 310 max: {scale: scale.max, options: scale.options.max}, 311 }; 312 } 313 }); 314 removeMissingScales(originalScaleLimits, scales); 315 removeMissingScales(updatedScaleLimits, scales); 316 return originalScaleLimits; 317} 318function doZoom(scale, amount, center, limits) { 319 const fn = zoomFunctions[scale.type] || zoomFunctions.default; 320 helpers.callback(fn, [scale, amount, center, limits]); 321} 322function doZoomRect(scale, from, to, limits) { 323 const fn = zoomRectFunctions[scale.type] || zoomRectFunctions.default; 324 helpers.callback(fn, [scale, from, to, limits]); 325} 326function getCenter(chart) { 327 const ca = chart.chartArea; 328 return { 329 x: (ca.left + ca.right) / 2, 330 y: (ca.top + ca.bottom) / 2, 331 }; 332} 333function zoom(chart, amount, transition = 'none', trigger = 'api') { 334 const {x = 1, y = 1, focalPoint = getCenter(chart)} = typeof amount === 'number' ? {x: amount, y: amount} : amount; 335 const state = getState(chart); 336 const {options: {limits, zoom: zoomOptions}} = state; 337 storeOriginalScaleLimits(chart, state); 338 const xEnabled = x !== 1; 339 const yEnabled = y !== 1; 340 const enabledScales = getEnabledScalesByPoint(zoomOptions, focalPoint, chart); 341 helpers.each(enabledScales || chart.scales, function(scale) { 342 if (scale.isHorizontal() && xEnabled) { 343 doZoom(scale, x, focalPoint, limits); 344 } else if (!scale.isHorizontal() && yEnabled) { 345 doZoom(scale, y, focalPoint, limits); 346 } 347 }); 348 chart.update(transition); 349 helpers.callback(zoomOptions.onZoom, [{chart, trigger}]); 350} 351function zoomRect(chart, p0, p1, transition = 'none', trigger = 'api') { 352 const state = getState(chart); 353 const {options: {limits, zoom: zoomOptions}} = state; 354 const {mode = 'xy'} = zoomOptions; 355 storeOriginalScaleLimits(chart, state); 356 const xEnabled = directionEnabled(mode, 'x', chart); 357 const yEnabled = directionEnabled(mode, 'y', chart); 358 helpers.each(chart.scales, function(scale) { 359 if (scale.isHorizontal() && xEnabled) { 360 doZoomRect(scale, p0.x, p1.x, limits); 361 } else if (!scale.isHorizontal() && yEnabled) { 362 doZoomRect(scale, p0.y, p1.y, limits); 363 } 364 }); 365 chart.update(transition); 366 helpers.callback(zoomOptions.onZoom, [{chart, trigger}]); 367} 368function zoomScale(chart, scaleId, range, transition = 'none', trigger = 'api') { 369 const state = getState(chart); 370 storeOriginalScaleLimits(chart, state); 371 const scale = chart.scales[scaleId]; 372 updateRange(scale, range, undefined, true); 373 chart.update(transition); 374 helpers.callback(state.options.zoom?.onZoom, [{chart, trigger}]); 375} 376function resetZoom(chart, transition = 'default') { 377 const state = getState(chart); 378 const originalScaleLimits = storeOriginalScaleLimits(chart, state); 379 helpers.each(chart.scales, function(scale) { 380 const scaleOptions = scale.options; 381 if (originalScaleLimits[scale.id]) { 382 scaleOptions.min = originalScaleLimits[scale.id].min.options; 383 scaleOptions.max = originalScaleLimits[scale.id].max.options; 384 } else { 385 delete scaleOptions.min; 386 delete scaleOptions.max; 387 } 388 delete state.updatedScaleLimits[scale.id]; 389 }); 390 chart.update(transition); 391 helpers.callback(state.options.zoom.onZoomComplete, [{chart}]); 392} 393function getOriginalRange(state, scaleId) { 394 const original = state.originalScaleLimits[scaleId]; 395 if (!original) { 396 return; 397 } 398 const {min, max} = original; 399 return helpers.valueOrDefault(max.options, max.scale) - helpers.valueOrDefault(min.options, min.scale); 400} 401function getZoomLevel(chart) { 402 const state = getState(chart); 403 let min = 1; 404 let max = 1; 405 helpers.each(chart.scales, function(scale) { 406 const origRange = getOriginalRange(state, scale.id); 407 if (origRange) { 408 const level = Math.round(origRange / (scale.max - scale.min) * 100) / 100; 409 min = Math.min(min, level); 410 max = Math.max(max, level); 411 } 412 }); 413 return min < 1 ? min : max; 414} 415function panScale(scale, delta, limits, state) { 416 const {panDelta} = state; 417 const storedDelta = panDelta[scale.id] || 0; 418 if (helpers.sign(storedDelta) === helpers.sign(delta)) { 419 delta += storedDelta; 420 } 421 const fn = panFunctions[scale.type] || panFunctions.default; 422 if (helpers.callback(fn, [scale, delta, limits])) { 423 panDelta[scale.id] = 0; 424 } else { 425 panDelta[scale.id] = delta; 426 } 427} 428function pan(chart, delta, enabledScales, transition = 'none') { 429 const {x = 0, y = 0} = typeof delta === 'number' ? {x: delta, y: delta} : delta; 430 const state = getState(chart); 431 const {options: {pan: panOptions, limits}} = state; 432 const {onPan} = panOptions || {}; 433 storeOriginalScaleLimits(chart, state); 434 const xEnabled = x !== 0; 435 const yEnabled = y !== 0; 436 helpers.each(enabledScales || chart.scales, function(scale) { 437 if (scale.isHorizontal() && xEnabled) { 438 panScale(scale, x, limits, state); 439 } else if (!scale.isHorizontal() && yEnabled) { 440 panScale(scale, y, limits, state); 441 } 442 }); 443 chart.update(transition); 444 helpers.callback(onPan, [{chart}]); 445} 446function getInitialScaleBounds(chart) { 447 const state = getState(chart); 448 storeOriginalScaleLimits(chart, state); 449 const scaleBounds = {}; 450 for (const scaleId of Object.keys(chart.scales)) { 451 const {min, max} = state.originalScaleLimits[scaleId] || {min: {}, max: {}}; 452 scaleBounds[scaleId] = {min: min.scale, max: max.scale}; 453 } 454 return scaleBounds; 455} 456function getZoomedScaleBounds(chart) { 457 const state = getState(chart); 458 const scaleBounds = {}; 459 for (const scaleId of Object.keys(chart.scales)) { 460 scaleBounds[scaleId] = state.updatedScaleLimits[scaleId]; 461 } 462 return scaleBounds; 463} 464function isZoomedOrPanned(chart) { 465 const scaleBounds = getInitialScaleBounds(chart); 466 for (const scaleId of Object.keys(chart.scales)) { 467 const {min: originalMin, max: originalMax} = scaleBounds[scaleId]; 468 if (originalMin !== undefined && chart.scales[scaleId].min !== originalMin) { 469 return true; 470 } 471 if (originalMax !== undefined && chart.scales[scaleId].max !== originalMax) { 472 return true; 473 } 474 } 475 return false; 476} 477function isZoomingOrPanning(chart) { 478 const state = getState(chart); 479 return state.panning || state.dragging; 480} 481 482const clamp = (x, from, to) => Math.min(to, Math.max(from, x)); 483function removeHandler(chart, type) { 484 const {handlers} = getState(chart); 485 const handler = handlers[type]; 486 if (handler && handler.target) { 487 handler.target.removeEventListener(type, handler); 488 delete handlers[type]; 489 } 490} 491function addHandler(chart, target, type, handler) { 492 const {handlers, options} = getState(chart); 493 const oldHandler = handlers[type]; 494 if (oldHandler && oldHandler.target === target) { 495 return; 496 } 497 removeHandler(chart, type); 498 handlers[type] = (event) => handler(chart, event, options); 499 handlers[type].target = target; 500 const passive = type === 'wheel' ? false : undefined; 501 target.addEventListener(type, handlers[type], {passive}); 502} 503function mouseMove(chart, event) { 504 const state = getState(chart); 505 if (state.dragStart) { 506 state.dragging = true; 507 state.dragEnd = event; 508 chart.update('none'); 509 } 510} 511function keyDown(chart, event) { 512 const state = getState(chart); 513 if (!state.dragStart || event.key !== 'Escape') { 514 return; 515 } 516 removeHandler(chart, 'keydown'); 517 state.dragging = false; 518 state.dragStart = state.dragEnd = null; 519 chart.update('none'); 520} 521function getPointPosition(event, chart) { 522 if (event.target !== chart.canvas) { 523 const canvasArea = chart.canvas.getBoundingClientRect(); 524 return { 525 x: event.clientX - canvasArea.left, 526 y: event.clientY - canvasArea.top, 527 }; 528 } 529 return helpers.getRelativePosition(event, chart); 530} 531function zoomStart(chart, event, zoomOptions) { 532 const {onZoomStart, onZoomRejected} = zoomOptions; 533 if (onZoomStart) { 534 const point = getPointPosition(event, chart); 535 if (helpers.callback(onZoomStart, [{chart, event, point}]) === false) { 536 helpers.callback(onZoomRejected, [{chart, event}]); 537 return false; 538 } 539 } 540} 541function mouseDown(chart, event) { 542 if (chart.legend) { 543 const point = helpers.getRelativePosition(event, chart); 544 if (helpers._isPointInArea(point, chart.legend)) { 545 return; 546 } 547 } 548 const state = getState(chart); 549 const {pan: panOptions, zoom: zoomOptions = {}} = state.options; 550 if ( 551 event.button !== 0 || 552 keyPressed(getModifierKey(panOptions), event) || 553 keyNotPressed(getModifierKey(zoomOptions.drag), event) 554 ) { 555 return helpers.callback(zoomOptions.onZoomRejected, [{chart, event}]); 556 } 557 if (zoomStart(chart, event, zoomOptions) === false) { 558 return; 559 } 560 state.dragStart = event; 561 addHandler(chart, chart.canvas.ownerDocument, 'mousemove', mouseMove); 562 addHandler(chart, window.document, 'keydown', keyDown); 563} 564function applyAspectRatio({begin, end}, aspectRatio) { 565 let width = end.x - begin.x; 566 let height = end.y - begin.y; 567 const ratio = Math.abs(width / height); 568 if (ratio > aspectRatio) { 569 width = Math.sign(width) * Math.abs(height * aspectRatio); 570 } else if (ratio < aspectRatio) { 571 height = Math.sign(height) * Math.abs(width / aspectRatio); 572 } 573 end.x = begin.x + width; 574 end.y = begin.y + height; 575} 576function applyMinMaxProps(rect, chartArea, points, {min, max, prop}) { 577 rect[min] = clamp(Math.min(points.begin[prop], points.end[prop]), chartArea[min], chartArea[max]); 578 rect[max] = clamp(Math.max(points.begin[prop], points.end[prop]), chartArea[min], chartArea[max]); 579} 580function getRelativePoints(chart, pointEvents, maintainAspectRatio) { 581 const points = { 582 begin: getPointPosition(pointEvents.dragStart, chart), 583 end: getPointPosition(pointEvents.dragEnd, chart), 584 }; 585 if (maintainAspectRatio) { 586 const aspectRatio = chart.chartArea.width / chart.chartArea.height; 587 applyAspectRatio(points, aspectRatio); 588 } 589 return points; 590} 591function computeDragRect(chart, mode, pointEvents, maintainAspectRatio) { 592 const xEnabled = directionEnabled(mode, 'x', chart); 593 const yEnabled = directionEnabled(mode, 'y', chart); 594 const {top, left, right, bottom, width: chartWidth, height: chartHeight} = chart.chartArea; 595 const rect = {top, left, right, bottom}; 596 const points = getRelativePoints(chart, pointEvents, maintainAspectRatio && xEnabled && yEnabled); 597 if (xEnabled) { 598 applyMinMaxProps(rect, chart.chartArea, points, {min: 'left', max: 'right', prop: 'x'}); 599 } 600 if (yEnabled) { 601 applyMinMaxProps(rect, chart.chartArea, points, {min: 'top', max: 'bottom', prop: 'y'}); 602 } 603 const width = rect.right - rect.left; 604 const height = rect.bottom - rect.top; 605 return { 606 ...rect, 607 width, 608 height, 609 zoomX: xEnabled && width ? 1 + ((chartWidth - width) / chartWidth) : 1, 610 zoomY: yEnabled && height ? 1 + ((chartHeight - height) / chartHeight) : 1 611 }; 612} 613function mouseUp(chart, event) { 614 const state = getState(chart); 615 if (!state.dragStart) { 616 return; 617 } 618 removeHandler(chart, 'mousemove'); 619 const {mode, onZoomComplete, drag: {threshold = 0, maintainAspectRatio}} = state.options.zoom; 620 const rect = computeDragRect(chart, mode, {dragStart: state.dragStart, dragEnd: event}, maintainAspectRatio); 621 const distanceX = directionEnabled(mode, 'x', chart) ? rect.width : 0; 622 const distanceY = directionEnabled(mode, 'y', chart) ? rect.height : 0; 623 const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY); 624 state.dragStart = state.dragEnd = null; 625 if (distance <= threshold) { 626 state.dragging = false; 627 chart.update('none'); 628 return; 629 } 630 zoomRect(chart, {x: rect.left, y: rect.top}, {x: rect.right, y: rect.bottom}, 'zoom', 'drag'); 631 state.dragging = false; 632 state.filterNextClick = true; 633 helpers.callback(onZoomComplete, [{chart}]); 634} 635function wheelPreconditions(chart, event, zoomOptions) { 636 if (keyNotPressed(getModifierKey(zoomOptions.wheel), event)) { 637 helpers.callback(zoomOptions.onZoomRejected, [{chart, event}]); 638 return; 639 } 640 if (zoomStart(chart, event, zoomOptions) === false) { 641 return; 642 } 643 if (event.cancelable) { 644 event.preventDefault(); 645 } 646 if (event.deltaY === undefined) { 647 return; 648 } 649 return true; 650} 651function wheel(chart, event) { 652 const {handlers: {onZoomComplete}, options: {zoom: zoomOptions}} = getState(chart); 653 if (!wheelPreconditions(chart, event, zoomOptions)) { 654 return; 655 } 656 const rect = event.target.getBoundingClientRect(); 657 const speed = zoomOptions.wheel.speed; 658 const percentage = event.deltaY >= 0 ? 2 - 1 / (1 - speed) : 1 + speed; 659 const amount = { 660 x: percentage, 661 y: percentage, 662 focalPoint: { 663 x: event.clientX - rect.left, 664 y: event.clientY - rect.top 665 } 666 }; 667 zoom(chart, amount, 'zoom', 'wheel'); 668 helpers.callback(onZoomComplete, [{chart}]); 669} 670function addDebouncedHandler(chart, name, handler, delay) { 671 if (handler) { 672 getState(chart).handlers[name] = debounce(() => helpers.callback(handler, [{chart}]), delay); 673 } 674} 675function addListeners(chart, options) { 676 const canvas = chart.canvas; 677 const {wheel: wheelOptions, drag: dragOptions, onZoomComplete} = options.zoom; 678 if (wheelOptions.enabled) { 679 addHandler(chart, canvas, 'wheel', wheel); 680 addDebouncedHandler(chart, 'onZoomComplete', onZoomComplete, 250); 681 } else { 682 removeHandler(chart, 'wheel'); 683 } 684 if (dragOptions.enabled) { 685 addHandler(chart, canvas, 'mousedown', mouseDown); 686 addHandler(chart, canvas.ownerDocument, 'mouseup', mouseUp); 687 } else { 688 removeHandler(chart, 'mousedown'); 689 removeHandler(chart, 'mousemove'); 690 removeHandler(chart, 'mouseup'); 691 removeHandler(chart, 'keydown'); 692 } 693} 694function removeListeners(chart) { 695 removeHandler(chart, 'mousedown'); 696 removeHandler(chart, 'mousemove'); 697 removeHandler(chart, 'mouseup'); 698 removeHandler(chart, 'wheel'); 699 removeHandler(chart, 'click'); 700 removeHandler(chart, 'keydown'); 701} 702 703function createEnabler(chart, state) { 704 return function(recognizer, event) { 705 const {pan: panOptions, zoom: zoomOptions = {}} = state.options; 706 if (!panOptions || !panOptions.enabled) { 707 return false; 708 } 709 const srcEvent = event && event.srcEvent; 710 if (!srcEvent) { 711 return true; 712 } 713 if (!state.panning && event.pointerType === 'mouse' && ( 714 keyNotPressed(getModifierKey(panOptions), srcEvent) || keyPressed(getModifierKey(zoomOptions.drag), srcEvent)) 715 ) { 716 helpers.callback(panOptions.onPanRejected, [{chart, event}]); 717 return false; 718 } 719 return true; 720 }; 721} 722function pinchAxes(p0, p1) { 723 const pinchX = Math.abs(p0.clientX - p1.clientX); 724 const pinchY = Math.abs(p0.clientY - p1.clientY); 725 const p = pinchX / pinchY; 726 let x, y; 727 if (p > 0.3 && p < 1.7) { 728 x = y = true; 729 } else if (pinchX > pinchY) { 730 x = true; 731 } else { 732 y = true; 733 } 734 return {x, y}; 735} 736function handlePinch(chart, state, e) { 737 if (state.scale) { 738 const {center, pointers} = e; 739 const zoomPercent = 1 / state.scale * e.scale; 740 const rect = e.target.getBoundingClientRect(); 741 const pinch = pinchAxes(pointers[0], pointers[1]); 742 const mode = state.options.zoom.mode; 743 const amount = { 744 x: pinch.x && directionEnabled(mode, 'x', chart) ? zoomPercent : 1, 745 y: pinch.y && directionEnabled(mode, 'y', chart) ? zoomPercent : 1, 746 focalPoint: { 747 x: center.x - rect.left, 748 y: center.y - rect.top 749 } 750 }; 751 zoom(chart, amount, 'zoom', 'pinch'); 752 state.scale = e.scale; 753 } 754} 755function startPinch(chart, state, event) { 756 if (state.options.zoom.pinch.enabled) { 757 const point = helpers.getRelativePosition(event, chart); 758 if (helpers.callback(state.options.zoom.onZoomStart, [{chart, event, point}]) === false) { 759 state.scale = null; 760 helpers.callback(state.options.zoom.onZoomRejected, [{chart, event}]); 761 } else { 762 state.scale = 1; 763 } 764 } 765} 766function endPinch(chart, state, e) { 767 if (state.scale) { 768 handlePinch(chart, state, e); 769 state.scale = null; 770 helpers.callback(state.options.zoom.onZoomComplete, [{chart}]); 771 } 772} 773function handlePan(chart, state, e) { 774 const delta = state.delta; 775 if (delta) { 776 state.panning = true; 777 pan(chart, {x: e.deltaX - delta.x, y: e.deltaY - delta.y}, state.panScales); 778 state.delta = {x: e.deltaX, y: e.deltaY}; 779 } 780} 781function startPan(chart, state, event) { 782 const {enabled, onPanStart, onPanRejected} = state.options.pan; 783 if (!enabled) { 784 return; 785 } 786 const rect = event.target.getBoundingClientRect(); 787 const point = { 788 x: event.center.x - rect.left, 789 y: event.center.y - rect.top 790 }; 791 if (helpers.callback(onPanStart, [{chart, event, point}]) === false) { 792 return helpers.callback(onPanRejected, [{chart, event}]); 793 } 794 state.panScales = getEnabledScalesByPoint(state.options.pan, point, chart); 795 state.delta = {x: 0, y: 0}; 796 handlePan(chart, state, event); 797} 798function endPan(chart, state) { 799 state.delta = null; 800 if (state.panning) { 801 state.panning = false; 802 state.filterNextClick = true; 803 helpers.callback(state.options.pan.onPanComplete, [{chart}]); 804 } 805} 806const hammers = new WeakMap(); 807function startHammer(chart, options) { 808 const state = getState(chart); 809 const canvas = chart.canvas; 810 const {pan: panOptions, zoom: zoomOptions} = options; 811 const mc = new Hammer.Manager(canvas); 812 if (zoomOptions && zoomOptions.pinch.enabled) { 813 mc.add(new Hammer.Pinch()); 814 mc.on('pinchstart', (e) => startPinch(chart, state, e)); 815 mc.on('pinch', (e) => handlePinch(chart, state, e)); 816 mc.on('pinchend', (e) => endPinch(chart, state, e)); 817 } 818 if (panOptions && panOptions.enabled) { 819 mc.add(new Hammer.Pan({ 820 threshold: panOptions.threshold, 821 enable: createEnabler(chart, state) 822 })); 823 mc.on('panstart', (e) => startPan(chart, state, e)); 824 mc.on('panmove', (e) => handlePan(chart, state, e)); 825 mc.on('panend', () => endPan(chart, state)); 826 } 827 hammers.set(chart, mc); 828} 829function stopHammer(chart) { 830 const mc = hammers.get(chart); 831 if (mc) { 832 mc.remove('pinchstart'); 833 mc.remove('pinch'); 834 mc.remove('pinchend'); 835 mc.remove('panstart'); 836 mc.remove('pan'); 837 mc.remove('panend'); 838 mc.destroy(); 839 hammers.delete(chart); 840 } 841} 842function hammerOptionsChanged(oldOptions, newOptions) { 843 const {pan: oldPan, zoom: oldZoom} = oldOptions; 844 const {pan: newPan, zoom: newZoom} = newOptions; 845 if (oldZoom?.zoom?.pinch?.enabled !== newZoom?.zoom?.pinch?.enabled) { 846 return true; 847 } 848 if (oldPan?.enabled !== newPan?.enabled) { 849 return true; 850 } 851 if (oldPan?.threshold !== newPan?.threshold) { 852 return true; 853 } 854 return false; 855} 856 857var version = "2.2.0"; 858 859function draw(chart, caller, options) { 860 const dragOptions = options.zoom.drag; 861 const {dragStart, dragEnd} = getState(chart); 862 if (dragOptions.drawTime !== caller || !dragEnd) { 863 return; 864 } 865 const {left, top, width, height} = computeDragRect(chart, options.zoom.mode, {dragStart, dragEnd}, dragOptions.maintainAspectRatio); 866 const ctx = chart.ctx; 867 ctx.save(); 868 ctx.beginPath(); 869 ctx.fillStyle = dragOptions.backgroundColor || 'rgba(225,225,225,0.3)'; 870 ctx.fillRect(left, top, width, height); 871 if (dragOptions.borderWidth > 0) { 872 ctx.lineWidth = dragOptions.borderWidth; 873 ctx.strokeStyle = dragOptions.borderColor || 'rgba(225,225,225)'; 874 ctx.strokeRect(left, top, width, height); 875 } 876 ctx.restore(); 877} 878var Zoom = { 879 id: 'zoom', 880 version, 881 defaults: { 882 pan: { 883 enabled: false, 884 mode: 'xy', 885 threshold: 10, 886 modifierKey: null, 887 }, 888 zoom: { 889 wheel: { 890 enabled: false, 891 speed: 0.1, 892 modifierKey: null 893 }, 894 drag: { 895 enabled: false, 896 drawTime: 'beforeDatasetsDraw', 897 modifierKey: null 898 }, 899 pinch: { 900 enabled: false 901 }, 902 mode: 'xy', 903 } 904 }, 905 start: function(chart, _args, options) { 906 const state = getState(chart); 907 state.options = options; 908 if (Object.prototype.hasOwnProperty.call(options.zoom, 'enabled')) { 909 console.warn('The option `zoom.enabled` is no longer supported. Please use `zoom.wheel.enabled`, `zoom.drag.enabled`, or `zoom.pinch.enabled`.'); 910 } 911 if (Object.prototype.hasOwnProperty.call(options.zoom, 'overScaleMode') 912 || Object.prototype.hasOwnProperty.call(options.pan, 'overScaleMode')) { 913 console.warn('The option `overScaleMode` is deprecated. Please use `scaleMode` instead (and update `mode` as desired).'); 914 } 915 if (Hammer) { 916 startHammer(chart, options); 917 } 918 chart.pan = (delta, panScales, transition) => pan(chart, delta, panScales, transition); 919 chart.zoom = (args, transition) => zoom(chart, args, transition); 920 chart.zoomRect = (p0, p1, transition) => zoomRect(chart, p0, p1, transition); 921 chart.zoomScale = (id, range, transition) => zoomScale(chart, id, range, transition); 922 chart.resetZoom = (transition) => resetZoom(chart, transition); 923 chart.getZoomLevel = () => getZoomLevel(chart); 924 chart.getInitialScaleBounds = () => getInitialScaleBounds(chart); 925 chart.getZoomedScaleBounds = () => getZoomedScaleBounds(chart); 926 chart.isZoomedOrPanned = () => isZoomedOrPanned(chart); 927 chart.isZoomingOrPanning = () => isZoomingOrPanning(chart); 928 }, 929 beforeEvent(chart, {event}) { 930 if (isZoomingOrPanning(chart)) { 931 return false; 932 } 933 if (event.type === 'click' || event.type === 'mouseup') { 934 const state = getState(chart); 935 if (state.filterNextClick) { 936 state.filterNextClick = false; 937 return false; 938 } 939 } 940 }, 941 beforeUpdate: function(chart, args, options) { 942 const state = getState(chart); 943 const previousOptions = state.options; 944 state.options = options; 945 if (hammerOptionsChanged(previousOptions, options)) { 946 stopHammer(chart); 947 startHammer(chart, options); 948 } 949 addListeners(chart, options); 950 }, 951 beforeDatasetsDraw(chart, _args, options) { 952 draw(chart, 'beforeDatasetsDraw', options); 953 }, 954 afterDatasetsDraw(chart, _args, options) { 955 draw(chart, 'afterDatasetsDraw', options); 956 }, 957 beforeDraw(chart, _args, options) { 958 draw(chart, 'beforeDraw', options); 959 }, 960 afterDraw(chart, _args, options) { 961 draw(chart, 'afterDraw', options); 962 }, 963 stop: function(chart) { 964 removeListeners(chart); 965 if (Hammer) { 966 stopHammer(chart); 967 } 968 removeState(chart); 969 }, 970 panFunctions, 971 zoomFunctions, 972 zoomRectFunctions, 973}; 974 975chart_js.Chart.register(Zoom); 976 977return Zoom; 978 979}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.