PageSourceSearch

https://ipsego.app/assets/chartjs-plugin-zoom/chartjs-plugin-zoom.js

js ipsego.app collected 2026-10-08 17:19:08 UTC 33,027 bytes, 979 lines download raw bytes

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.