Зум выделением области (drag)

Механизм зума выделением области в Chart.js основан на обработке событий мыши или касания и динамическом изменении диапазона шкал (scales) графика. В отличие от дискретного увеличения через кнопки или программные вызовы, drag-зум предполагает интерактивное выделение прямоугольной области, которая затем становится новым видимым диапазоном данных.

Основная логика построена вокруг трёх этапов:

  • фиксация начальной точки (mousedown / touchstart)
  • отслеживание движения (mousemove / touchmove)
  • завершение выделения (mouseup / touchend)

Во время движения вычисляются координаты прямоугольника выделения, который визуально накладывается поверх canvas. После завершения выделения координаты преобразуются в значения шкал (x/y), и график пересчитывает диапазон отображаемых данных.

Ключевой момент: Chart.js сам по себе не реализует drag-to-zoom, эта функциональность предоставляется через расширение chartjs-plugin-zoom.


Подключение расширения zoom

Базовая интеграция требует подключения плагина масштабирования и панорамирования:

import { Chart } from 'chart.js';
import zoomPlugin from 'chartjs-plugin-zoom';

Chart.register(zoomPlugin);

После регистрации становится доступна конфигурация options.plugins.zoom.


Включение режима выделения области

Режим drag-зума активируется через настройку zoom по оси с использованием drag:

const config = {
  type: 'line',
  data: {
    labels: ['A', 'B', 'C', 'D', 'E'],
    datasets: [{
      label: 'Данные',
      data: [10, 25, 18, 30, 22]
    }]
  },
  options: {
    plugins: {
      zoom: {
        zoom: {
          wheel: {
            enabled: false
          },
          pinch: {
            enabled: false
          },
          drag: {
            enabled: true,
            backgroundColor: 'rgba(54, 162, 235, 0.2)',
            borderColor: 'rgba(54, 162, 235, 0.8)',
            borderWidth: 1
          },
          mode: 'x'
        }
      }
    }
  }
};

Параметр drag.enabled включает механизм выделения области. Параметр mode определяет оси, которые будут масштабироваться:

  • x — только горизонтальная ось
  • y — только вертикальная
  • xy — обе оси одновременно

Преобразование координат canvas в значения шкал

Внутри Chart.js координаты мыши находятся в системе canvas, тогда как шкалы работают в доменных значениях данных. Плагин выполняет преобразование через API шкал:

  • scale.getValueForPixel(pixel)
  • scale.getPixelForValue(value)

Логика выделения области опирается на эти методы для вычисления диапазона:

const xStart = chart.scales.x.getValueForPixel(startX);
const xEnd = chart.scales.x.getValueForPixel(endX);

После вычисления диапазона вызывается обновление шкал:

chart.options.scales.x.min = xStart;
chart.options.scales.x.max = xEnd;
chart.update();

Визуализация прямоугольника выделения

Во время движения мыши плагин отрисовывает overlay-слой. Он создаётся поверх canvas через дополнительный контекст или внутренний plugin hook.

Параметры визуализации:

  • backgroundColor — заливка выделенной области
  • borderColor — рамка прямоугольника
  • borderWidth — толщина линии

При этом область пересчитывается на каждом событии mousemove, что требует оптимизации через requestAnimationFrame для предотвращения избыточных перерисовок.


Ограничение области зума

Для предотвращения ухода в некорректные диапазоны используются ограничения:

limits: {
  x: {
    min: 'original',
    max: 'original'
  },
  y: {
    min: 'original',
    max: 'original'
  }
}

Значение 'original' фиксирует границы исходного набора данных, предотвращая чрезмерное увеличение или смещение.

Также возможно программное ограничение:

zoom: {
  limits: {
    x: { minRange: 5 },
    y: { minRange: 10 }
  }
}

Минимальный диапазон предотвращает слишком сильное приближение, при котором график теряет информативность.


Сброс масштаба

После применения drag-зума часто требуется восстановление исходного состояния:

chart.resetZoom();

Метод сбрасывает значения min и max всех шкал до исходных, определённых при инициализации графика.

В некоторых сценариях используется ручной сброс:

chart.options.scales.x.min = undefined;
chart.options.scales.x.max = undefined;
chart.update();

Поведение при нескольких осях

При работе с несколькими осями (например, две Y-оси) важно явно указать, какие шкалы участвуют в zoom:

zoom: {
  drag: {
    enabled: true
  },
  mode: 'xy',
  overScaleMode: 'xy'
}

Параметр overScaleMode определяет, какие шкалы реагируют на выделение, если курсор находится над областью пересечения нескольких осей.


Работа с time-scale

При использовании временных шкал (type: 'time') drag-зум оперирует не индексами массива, а timestamp значениями.

Особенность:

  • границы интерпретируются как Unix time
  • возможно дробное масштабирование (до миллисекунд)
  • важно учитывать формат адаптеров дат (date-fns, moment, luxon)

Пример:

scales: {
  x: {
    type: 'time',
    time: {
      unit: 'day'
    }
  }
}

При выделении области плагин возвращает диапазон времени, который напрямую применяется к min и max.


Кастомизация поведения drag

Поведение выделения может быть модифицировано через callbacks:

plugins: {
  zoom: {
    zoom: {
      drag: {
        enabled: true,
        onDragStart: ({ chart }) => {
          chart.canvas.style.cursor = 'crosshair';
        },
        onDrag: ({ chart }) => {
          // пользовательская логика во время перетаскивания
        },
        onDragEnd: ({ chart, range }) => {
          // доступ к рассчитанному диапазону
        }
      }
    }
  }
}

Эти хуки позволяют интегрировать:

  • синхронизацию нескольких графиков
  • сохранение истории зума
  • динамическую фильтрацию данных

Синхронизация нескольких графиков

Частый сценарий — одновременное масштабирование нескольких Chart.js экземпляров. Реализация выполняется через общий обработчик:

onDragEnd: ({ chart, range }) => {
  charts.forEach(c => {
    c.options.scales.x.min = range.x.min;
    c.options.scales.x.max = range.x.max;
    c.update();
  });
}

Такой подход обеспечивает визуальную синхронизацию аналитических панелей.


Обработка конфликтов с tooltip и hover

Drag-зум конфликтует с стандартными интерактивными элементами:

  • tooltip
  • hover highlight
  • click events

Для устранения конфликтов используется отключение hover во время drag:

onDragStart: ({ chart }) => {
  chart.options.plugins.tooltip.enabled = false;
}

После завершения:

onDragEnd: ({ chart }) => {
  chart.options.plugins.tooltip.enabled = true;
}

Оптимизация производительности

При интенсивных данных drag-to-zoom может вызывать нагрузку из-за постоянных пересчётов шкал.

Основные методы оптимизации:

  • ограничение частоты обновления через requestAnimationFrame
  • уменьшение числа точек (decimation)
  • отключение анимации при zoom
animation: false

Также полезно использовать:

normalized: true

для оптимизации обработки больших датасетов.


Особенности мобильных устройств

На сенсорных экранах drag-зум может конфликтовать с прокруткой страницы. Решение заключается в:

  • включении touchAction: 'none' на canvas
  • использовании pinch как альтернативного жеста
zoom: {
  pinch: {
    enabled: true
  }
}

Типовые ошибки реализации

Часто встречающиеся проблемы:

  • отсутствие регистрации плагина zoom
  • конфликт осей при неправильном mode
  • отсутствие сброса min/max после zoom reset
  • неправильное преобразование координат при кастомных scales
  • перерисовка без update() после изменения диапазона

Каждая из этих ошибок приводит либо к отсутствию реакции drag, либо к некорректному отображению данных.


Внутренняя модель изменения масштаба

Изменение масштаба в Chart.js не пересоздаёт график, а модифицирует состояние scales:

  • min
  • max
  • suggestedMin
  • suggestedMax

После drag-зума происходит перерасчёт:

  1. вычисление domain (данные)
  2. установка новых границ шкал
  3. пересчёт layout элементов
  4. перерисовка canvas

Эта модель позволяет сохранять состояние графика и обеспечивает плавность интерактивных операций.