Найти тему

Canvas и отрисовка графиков на нем

План статьи

1) Введение, что такое canvas, предыистория знакомства с ним, причины почему хочу использовать графики без каких-либо библиотек.

2) Особенности работы с canvas, о которых стоит знать перед тем, как начать с ним работать.

3) Основной принцип работы с canvas, который необходим для понимания, чтобы работать с любым графиком. Архитектура моего проекта и работа с ней.

4) Основные команды для работы с графиков в canvas.

5) Вывод

Введение, что такое canvas, предыистория знакомства с ним, причины почему хочу использовать графики без каких-либо библиотек.

Всем привет, меня зовут Александр, я являюсь фронтенд разработчиком с 4-х летним опытом. В этой статье я хочу поделится моим опытом работы с тегом canvas, а также выполнениемграфика в нем. Давайте разберемся и вспомним для чего нужен canvas и что это такое? Canvas — это тег, который предназначен для создания графики с помощью языка программирования javascript/typescript в зависимости от предпочтения, в моем случае я его реализовал на typescript. Если простыми словами, то это тег для отрисовки какой-либо графики, также его можно использовать для работы с изображением, но я пока не встречал практических сценариев такого использования.

Мойпервый случай работы с данным тегом был при реализации административной страницы, когда необходимо было выполнить несколько графиков. Для этого я использовал библиотеку chartjs. Да, это мне помогло в большей степени, но не понимание как работать с canvas не давало мне возможности использовать эту библиотеку на полную мощность.

Это выражалось в том, что если необходимо было сделать что-то, что выходило за границы базовой настройки библиотеки, то это сразу вызывало у меня непонимание, как это реализовать. На поиски решения уходило много времени, если добавить сюда, что в проекте я был единственным ui разработчиком, то в сумме это заставляло от таких идей отказаться, потому что на их реализацию уходило бы много времени, которого не было в наличии.

После выполнения этой задачи я поставил у себя заметку в памяти, что необходимо будет выучить для себя работу с canvas на общем уровне, чтобы понимать, как он работает и как можно реализовать не сложные графики. С тех пор прошло более трех лет, но эту идею я не забыл и давайте теперь вместе посмотрим на текущий результат моего обучения.

Текущий результат использования canvas и отрисовка в нем графика на typescript.
Текущий результат использования canvas и отрисовка в нем графика на typescript.

Особенности работы с canvas, о которых стоит знать перед тем, как начать с ним работать.

Немного хочется написать об особенностях, которые необходимо помнить, либо изучить, для более удобной работы с canvas

  • сanvas начинает работу в левом верхнем углу, об следует помнить, начиная работу с этим тегом;
  • соблюдение отступов для корректной отрисовки содержимого контейнера;
  • при каждом рендере график полностью перерисовывается и это есть нормально, не надо этого боятся, главное не допустить утечки памяти;
  • во время работы с canvas использование proxy — это нормальная практика. Он используется в событиях, чтобы вызывать перерисовку графика;
  • требуется вспомнить нюансы работы с событиями наведения курсора на дом элемент и уход курсора с него;
  • при работе с проектом следует выделить ключевую функцию либо класс в отдельный файл, а весь вспомогательный функционал вывести в отдельный файл либо файлы.

Отдельно хочется сказать, что при построении вышеописанного графика, я познакомился с объектом proxy - это объект, который позволяет перехватывать события и производить различные действия с ним. Если взять пример из текущего решения, то я создаю объект proxy и использую событие set, чтобы вызывать перерисовку графика при изменении положения мыши. Саму перерисовку получается перехватить с помощью событий mousemove и mouseleave.

Давайте разберем более подробней, как это происходит. Перед объявлением событий mousemove и mouseleave я объявляю объект proxy, в котором первый аргумент - это пустой объект, и второй — это объект с сетером. Во втором объекте при помощи reflect вызываем сеттер и возвращаем результат без изменений. По факту, нам ничего не надо менять в сеттере, необходимо только при каждом изменении исходного объекта вызывает перерисовку графика в canvas, что и позволяет делать нам сеттер, сама перерисовка происходит в функции paint, которую рассмотрим позже.

Во время написания этого графика я познакомился с такой замечательной функцией, как requestAnimationFrame. Этафункция позволяет работать с графикой и оптимизировать ее работу. Она всегда используется в связке cancelAnimationFrame, чтобы при необходимости сделать сброс данных по функции и избежать утечки памяти.

Инициализация объекта proxy и его использование в событиях mousemove и mouseleave.
Инициализация объекта proxy и его использование в событиях mousemove и mouseleave.

Основной принцип работы с canvas, который необходим для понимания, чтобы работать с любым графиком. Архитектура моего проекта и работа с ней.

Теперь хочу обсудить принципы работы, которые я понял во время построения текущего графика и буду в дальнейшем применять при построении новых графиков:

  • заранее необходимо продумать все отступы в графике и вычислить все функции, которые будут формировать сам график, по факту получается, что вы заранее проектируете алгоритм, по которому будет происходить вычисление функции в коде и у вас должно быть сформирована итоговая картина в голове, к которой вы будете идти. Говоря простыми словами: мы пишем код сначала на бумаге, а потом переносим его на язык программирования;
  • перед реализацией продумать архитектуру, которую вы хотите реализовать для отрисовки данного графика. Если взять на моем примере, то я сначала инициализировал все константы и размеры полотна, которые необходимы для работы.
Инициализированы все константы для работы с канвасом
Инициализированы все константы для работы с канвасом

После этого реализовал ключевую функцию paint, в рамках которой происходит вычисление графика. Давайте более подробно распишу функцию paint. Сначала происходит очистка полотна от предыдущих зарисовок, без этого шага никуда, ведь нам для работы необходимо чистое полотно) После очистки полотна высчитываем наибольшую и наименьшую точку по оси ординат, чтобы затем вычесть дельту для масштабирования графика по оси ординат, параллельно вычисляем дельту для масштабирования графика по оси абсцисс. Когда мы имеем готовые дельты масштабирования по обоим осям, максимальную и минимальную точки по оси ординат, то теперь можно наложить на полотно оси ординат и абсцисс, вспомогательные оси для графика, которые идут параллельно оси абсцисс.

Реализация осей абсцисс и ординат для графика
Реализация осей абсцисс и ординат для графика

Когда оси графика готовы, то приступаем к отрисовке самого графика и кружочками, которые будут наложены на макет в момент наведения мыши над ними. Здесь есть ключевой момент, что кружочки должны накладываться в момент построения графика. Почему это так важно? Потому что кружочек относится к самому графику и рассоединять их нельзя, потому что иначе рушится архитектура приложения. Лично для меня это весомый аргумент и я решил реализовать это именно вышеописанным способом.

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

Архитектура отрисовки графиков и кружочка для него
Архитектура отрисовки графиков и кружочка для него

Как видно из двух вышеописанных абзацев и картинки «Архитектура отрисовки графиков и кружочка для него» у меня сначала выполняется расчет отдельного графика в функции calculateGraph, а потом на него накладывается кружочек в функции circle. Детали реализации функции я опущу.

Объясняю. Дело в том, что каждый кружочек относится к определенному графику и их количество зависит от количества графиков, которое нужно отобразить на полотне, а тултип у меня один. В связи с этим мне проще сначала выполнить отображение определенного графика и затем на него наложить кружочек. И после того, как все графики были наложены на полотно, мне поверх них проще наложить сам тултип.

Архитектура расчета графиков и тултипа к ним
Архитектура расчета графиков и тултипа к ним

Для общего понимания архитектуры я прикрепил картинку «Архитектура расчета графиков и тултипа к ним», на которой можно проследить, что сначала я произвожу расчет и наложение графиков и все что с ними связано на холст и после этого сверху них накладываю тултип.

По итогу, если подводить итоги по вышеописанной логике отрисовки графиков, тослудуютследующиевыводы:

  • отрисовываем все оси ординат и абсцисс;
  • нанести графики и все что работает с определенным графиком;
  • нанести на полотно, все что может относится к общей информации или выводить информацию по всем графикам.

Далее, все чистые функции вынесены в отдельный файл, чтобы не загаживать корневой файл главной функции. В корневом файле должна быть главная функция, которая собирает в себя всю ключевую логику работы с тегом canvas.

Основные команды для работы с графиков в canvas.

Сразу хочу отметить, что трактовка команд будет отличатся от того, что написано в документации, здесь просто набор команд и что они обозначают в моем понимании.

  • getContext — получение контекста в канвас, через него в дальнейшем происходит вся работа с холстом;
  • clearRect — полностью очищает полотно;
  • beginPath — начало работы с графиком;
  • closePath — окончание работы с графиком;
  • fillText — вставляет текст в точку, которая указана по координатам;
  • fill — производит заливку контура либо фигуры;
  • moveTo — передвижение точки в заданные координаты;
  • lineTo — задает новую точку для отрисовки линии между ранее заданой точкой и указаной в этой функции, сама линия в этой функции не наносится на полотно;
  • stroke — выполняет отрисовку фигуры, которая ранее была задана какими либо функциями;
  • lineWidth, strokeStyle, font, fillStyle — стили для работы с полотном;
  • save и restore - первый сохраняет состояние холста, а второй возвращает его. Не совсем понял для чего практически нужны эти функции, но в дальнейшем, когда буду делать еще графики для себя, уделю внимание на использование этих функций;
  • arc — создает окружность или какую-то ее часть;

Все вышеперечисленные функции были мною использованы для отрисовки графика в теге canvas, который приведен в данной статье в качестве примера.

Вывод

В данной статье я привел пример архитектуры и алгоритм реализации, которые мне удалось реализовать при выполнении текущего графика в теге canvas. Также подробно разобрал моменты в архитектуре, акцентируя внимание, почему некоторые компоненты графика нужно реализовывать вместе, а другие отдельно.

Надеюсь вам было интересно ознакомится с моими первыми попытками знакомства с canvas и вы для себя почерпнули, что-то новое.

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