[CSS, JavaScript, Работа с векторной графикой, VueJS] Как реализовать динамическую диаграмму для Vue на основе SVG

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
21-Окт-2020 14:30

Бывает, что на сайте, в корпоративной 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, а как следствие – недовольство пользователей. Если большие вычисления не требуются, то бывает достаточно простых нативных инструментов – именно этот способ мы рассмотрели в статье.
Посмотреть полный пример и поэкспериментировать с исходным кодом можно здесь.
Спасибо за внимание! Надеемся, что этот пример был вам полезен.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_css, #_javascript, #_rabota_s_vektornoj_grafikoj (Работа с векторной графикой), #_vuejs, #_vue, #_js, #_css, #_diagramma (диаграмма), #_dinamicheskij_grafik (динамический график), #_blog_kompanii_simbirsoft (
Блог компании SimbirSoft
)
, #_css, #_javascript, #_rabota_s_vektornoj_grafikoj (
Работа с векторной графикой
)
, #_vuejs
Профиль  ЛС 
Показать сообщения:     

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

Текущее время: 22-Ноя 15:30
Часовой пояс: UTC + 5