Как реализовать динамическую диаграмму для Vue на основе SVG

от автора

Бывает, что на сайте, в корпоративной IT-системе или другом ПО нужно отображать круговые диаграммы с какими-либо данными. Например, это может быть таймер для отсчета времени или индикатор, сколько товаров продано в той или иной категории. Если это статическое изображение, конечно, можно обойтись форматом svg, png или gif. Однако, зачастую нужно показать данные в динамике – например, для мониторинга или просто для привлечения внимания пользователей, для создания красивой анимации при загрузке сайта. Делимся примером, как можно построить диаграмму из элементов SVG с помощью JS и CSS.

Представим, что нам нужно сделать диаграмму без подключения сторонних библиотек. Создадим независимый VUE-компонент, начнём с основы – шаблона.

<template>   <svg class="diagram" viewBox="0 0 42 42">   	<!—- Фоновый круг, подложка -->     <circle       :class="classCircleBack"       :r="radius"       cx="50%"       cy="50%"       :stroke-dashoffset="dashoffset"     />   	<!—- Внутренний круг, это и есть сам график -->     <circle       ref="mainDiagram"       class="front"       :class="classCircleFront"       :stroke-dasharray="dasharray"       :r="radius"       cx="50%"       cy="50%"     /> <!—- Спутник, необязательный элемент, но может пригодиться —-> <!—- в зависимости от  вашей задачи -->     <circle       v-if="satellite"       class="satellite"       r="1"       cx="101%"       cy="50%"       :style="rotate"     />   </svg> </template>

С помощью атрибутов CX и CY указываем смещение центра окружности фигуры <CIRCLE />, тем самым размещая объект по центру холста. При этом важно помнить, что в svg холстах независимый отсчёт координат, и единицей измерения не являются пиксели. Не забываем в теге svg прописать атрибут viewBox=«0 0 42 42» для указания размера холста.

Далее рассмотрим код на VUE, частично с добавлением TypeScript. Вся “магия” построения диаграммы и работа с анимацией здесь будут происходить за счет изменений свойства stroke-dasharray в теге circle – но сначала опишем входящие свойства компонента:

import Vue, { PropType } from 'vue'   export default Vue.extend({   name: 'Diagram',   props: {     // Свойство, которое принимает массив чисел, где:     // нулевой элемент – это длина отрезка для видимой части stroke-dasharray     // элемент с индексом 1 – это длина всего отрезка     // например, из 78 яблок продано 25, значит, пропс должен принять [25, 78]     dataDasharray: {       type: Array as PropType<number[]>,       required: true     },     // Радиус, необязательный пропс, можно указывать в любых единицах измерения     radius: {       type: String,       required: false,     },     // Кастомный css класс для стилизации фоновой фигуры круга     classCircleBack: {       type: String,     },     // Кастомный css класс для стилизации внешнего круга     classCircleFront: {       type: String,     },     // Если нужна фигура-спутник     satellite: {       type: Boolean,     }   },   data() {     return {       dasharray: '0 0', // начальные данные псевдомассива отрезка       dashoffset: '100', // Длина окружности для фигуры подложки – определяет смещение обводки относительно начального положения       radiusBaseVal: 0, // про эту переменную чуть ниже       circumference: 0 // Длина окружности, которую вычислим позже     }   }

Ранее мы подготовили компонент, теперь к нему нужно описать функционал.

// Вычисляемые свойства для анимации спутника   computed: {     rotate(): string {       // Поворот спутника относительно центра холста для инлайнового стиля       return `transform: rotate(${this.degRotate}deg);`     },     degRotate() {       // Вычисляем градус поворота спутника, основываясь на пропсе dataDasharray       const percent: number = Number(         ((this.dataDasharray[0] * 100) / this.dataDasharray[1]).toFixed(1)       )       return (-360 * (percent / 100) - 90).toFixed(1)     }   }

В этом фрагменте указана числовая константа -360. Она необходима для того, чтобы зеркально отобразить вращение «спутника», иначе сателлит будет двигаться против часовой стрелки – вопреки основной анимации круговой диаграммы.

Подчеркнем, что к следующему шагу мы переходим именно тогда, когда компонент vue будет смонтирован – чтобы обеспечить доступность ref. Затем выставим значения двух важных переменных:

mounted() {     this.radiusBaseVal = (this.$refs.mainDiagram as any).r.baseVal.value     this.circumference = 2 * Math.PI * this.radiusBaseVal   } 

radiusBaseVal – переменная, которая получает внутренний программный радиус фигуры <circle/>. Важно отметить, что этот радиус не связан с радиусом в разметке html.

circumference – переменная для хранения длины окружности (привет школьной тригонометрии!).

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

methods: {     setLengthDasharray(percent, circumference) {       const offset = circumference - (percent / 100) * circumference       this.dasharray = `${offset} ${circumference}`       this.dashoffset = circumference.toFixed(3)     }   }

Далее вся соль заключается в вотчере, где и стартует “магия” компонента:

watch: {     dataDasharray: {       handler() {         // вычисляем процентное соотношение данных из пропса dataDasharray         const percent = (       	(this.dataDasharray[0] * 100) / this.dataDasharray[1]     	).toFixed(1)         // Сетим длину оффсетов для нашей диаграммы         this.setLengthDasharray(percent, this.circumference)       },       deep: true, // Глубокое отслеживание пропса dataDasharray       immediate: true // запуск handler функции при mounted компонента     }   }

И завершающий этап: немного базовых стилей:

<style lang="scss" scoped> .diagram {   width: 100%;   height: 100%;   position: absolute;   top: 0;   left: 0;   overflow: visible; } circle {   fill: transparent;   stroke: rgb(255, 255, 255);   stroke-width: 0.6px;   transform-origin: center;   transform: rotate(-90deg); /* Обязательно повернём circle элемент, так как отсчёт dasharray будет начинаться справа, а не сверху. */   transition: stroke-dasharray 1s ease;     &.front {     stroke: rgb(255, 255, 255);   } }   .satellite {   fill: #fff;   will-change: transform; // скажем браузеру, что ожидается трансформирование для отправки на GPU   stroke-width: 0.4px;   transition: transform 1s ease; } </style>

Выводы

Итак, если вам нужно показать в приложении различные данные в виде диаграммы, есть разные пути решения. Для сложных вычислений можно обратиться к сторонним библиотекам (например, D3), но этот способ зачастую привносит в проект дополнительные риски: например, ухудшение runtime сайта и показателей поисковой оптимизации, увеличение time to Interactive и script execution, а как следствие – недовольство пользователей. Если большие вычисления не требуются, то бывает достаточно простых нативных инструментов – именно этот способ мы рассмотрели в статье.

Посмотреть полный пример и поэкспериментировать с исходным кодом можно здесь.

Спасибо за внимание! Надеемся, что этот пример был вам полезен.

ссылка на оригинал статьи https://habr.com/ru/company/simbirsoft/blog/524220/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *