Как реализовать динамическую диаграмму для 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, а как следствие – недовольство пользователей. Если большие вычисления не требуются, то бывает достаточно простых нативных инструментов – именно этот способ мы рассмотрели в статье.

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

    Спасибо за внимание! Надеемся, что этот пример был вам полезен.
    SimbirSoft
    Лидер в разработке современных ИТ-решений на заказ

    Похожие публикации

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое