Общий гайд по созданию HTML виджетов с Apache Echarts

Этап подготовки

После выбора типа виджета «HTML» будут доступны 3 вкладки:

  1. HTML
    • структура карточки-виджета
    • заголовки карточки
    • создание контейнеров для графиков и диаграмм.
  2. CSS Стилизация визуализаций, улучшение их внешнего вида. Определение стилей карточки виджета, цветов, шрифтов и анимаций. Обратите внимание, что в библиотеках для построения графиков(Apache Echarts и другие) настройка внешнего вида обычно производится в JS через передачу свойств стилей в объекте конфигурации(option).
  3. JS(JavaScript) Динамическое создание и управление визуализациями. Подключение библиотек, таких как D3.js или Chart.js, для создания интерактивных графиков и диаграмм, обновление данных в реальном времени.

В первую очередь необходимо подключить библиотеку Apache Echarts. Это можно сделать на вкладке HTML с помощью тега script. Можно подключить через CDN(Content Delivery Network) – в данном случае библиотека будет загружаться через интернет. Пример,

<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.0/echarts.min.js" integrity="sha512-k37wQcV4v2h6jgYf5IUz1MoSKPpDs630XGSmCaCCOXxy2awgAWKHGZWr9nMyGgk3IOxA1NxdkN8r1JHgkUtMoQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Также можно загрузить .js файл с библиотекой напрямую в AW и в src указать название файла, например:

<script src="echarts.min.js"></script>

image
image

Также на этом этапе подготовим данные, добавим поля в группы и агрегаты, настроим сортировку и фильтрацию у полей модели.
image

Осторожнее! Хотя агрегаты можно посчитать и в JavaScript коде, лучше реализовывать их через встроенный OLAP, чтобы не перегружать клиентский код лишними операциями. При необходимости можно реализовать их и в JS, но нужно следить за производительностью. Также не забывайте, что можно вывести данные модели в виде плоской таблицы, если добавить поля в столбцы без указания функции агрегации.

image

Однако такой подход рискован — если будет большая модель из миллионов строк, памяти браузера может не хватить + любая неосторожная операция может привести к зависанию виджета.

Добавляем диаграмму Echarts

Этап подготовки прошли, теперь можно добавить простой пример диаграммы:
добавим в HTML простую разметку нашей карточки-виджета. Обратите внимание на

<div id="chart"></div>

Этот элемент будем использовать для рендера Apache Echarts диаграммы, зададим ему идентификатор id = “chart”.
Код для вкладки HTML представлен ниже:

<article class="widget-card">
    <header>
      <h1 class="main-header">Динамика поступлений на склады</h1>
      <h2 class="sub-header">с 2018 года</h2>
    </header>
    <div class="chart-container">
    <div id="chart"></div>
    </div>
</article>

Добавим стилизацию карточки, для этого на вкладку CSS добавим следующий код:

@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap');

:root {
  --main-color: white;
  --text-color: black;
}
 
:root.dark {
  --main-color: rgb(43, 43, 43);
  --text-color: #d3d6e0;
}

.widget-card {
    font-family: 'Montserrat', sans-serif;
    border: 1px solid #757784;
    border-radius: 24px;
    width: 100%;
    height: 100%;

    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding-inline: 37px;
    padding-block: 28px;
 
    color: var(--text-color);
    background-color: var(--main-color);
}

.main-header {
    font-size: 20px;
    font-weight: 500;
    line-height: 24px;
}

.sub-header {
    font-size: 14px;
    font-weight: 400;
    line-height: 22px; 
}

.chart-container {
    width: 100%;
    height: 100%;
}

#chart {
    width: 100%;
    height: 100%;
}

На вкладке JavaScript добавим следующие строки:

  1. Найдем наш div-контейнер для диаграммы по id:

    let chartDom = document.getElementById(‘chart’);

  2. Инициализируем объект echarts в этом контейнере. Если библиотеку подключили правильно(см. Этап подготовки), то библиотека будет доступна по имени echarts:

    let chart = echarts.init(chartDom);

  3. Добавим обработчик события изменения размеров окна. В нем будем вызывать аналогичный метод для изменения размеров Echarts диаграммы. С этим кодом наш график станет "резиновым":

    window.addEventListener(‘resize’, () => { chart.resize(); })

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

    console.log(window.DATA)

    и откроем консоль разработчика в браузере(Ctrl + Shift + I). Отметим, что также для дебага также можно использовать ключевое слово debugger и пользоваться всеми возможностями devtools. Данные модели будут доступны в свойстве DATA глобального объекта window. На скриншоте представлен пример соответствия данных из табличного представления и из объекта JavaScript.
    image

  5. Для отрисовки Apache Echarts диаграммы необходимо передать объект настроек(option) экземпляру echarts. Cоздадим объект option вне функции render.

    Основные свойства, которые понадобятся для настройки:

    • grid - настройка сетки диаграммы;
    • xAxis, yAxis - настройка осей;
    • series - серии данных, на одной диаграмме может быть несколько серий с разными типами;

    Подробнее можно узнать в документации библиотеки:

    https://echarts.apache.org/en/option.html#title

    В нашем примере объект будет иметь следующую структуру:

    const option = {
      title: {
        text: 'Waterfall Chart',
        show: false
      },
      tooltip: {
        show: true,
        trigger: 'item'
      },
      xAxis: { type: 'category', data: [], axisLabel: { show: true } },
      yAxis: {},
      series: [
        {
          name: 'Placeholder',
          type: 'bar',
          stack: 'Total',
          itemStyle: {
            borderColor: 'transparent',
            color: 'transparent'
          },
          emphasis: {
            itemStyle: {
              borderColor: 'transparent',
              color: 'transparent'
            }
          },
          data: [],
          tooltip: {
            show: false,
            extraCssText: 'display: none;'
          },
          stackStrategy: 'all'
        },
        {
          name: 'Итог за месяц',
          type: 'bar',
          stack: 'Total',
          stackStrategy: 'all',
          data: [],
          label: {
            show: true,
            position: 'outside'
          },
          // tooltip: {},
          itemStyle: { color: v => { return v.value > 0 ? '#65cccc' : '#f96c89' } },
        }
      ]
    };
    
  6. При создании кастомных виджетов основной задачей является правильно передать данные из модели AW в option Apache Echarts. Для этого данные нужно преобразовать к подходящему виду. Они могут быть представлены в свойстве dataset или series.data. Подробное описание также можно посмотреть в документации.

    В нашем случае будем создавать waterfall-диаграмму. Для этого необходимо определить 2 серии данных:

    • отображаемую часть;
    • невидимую часть столбца, placeholder;

    Объявим функцию transformData. Аргументом передадим данные из модели и внутри функции получим 3 массива:

    • currentCategories - данные для оси oX;
    • currentData - данные для отображаемой серии;
    • dsum - данные placeholder`а;
    function transformData(data) {
      const currentData = [];
      const currentCategories = [];
      let dsum = [0];
      let sum = 0;
    
      data.forEach(row => {
        currentData.push(row.calc__income_exprense_diff.agg_value);
        currentCategories.push(row.calc__year_month.value);
    
        sum += row.calc__income_exprense_diff.agg_value;
        dsum.push(sum);
      });
    
      return [currentCategories, currentData, dsum];
    }
    
  7. И заключительным шагом в функции render вызовем функцию трансформации и установим преобразованные массивы в качестве данных для серий:
    option.series[0].data = dsum;
    option.series[1].data = currentData;
    

    Добавим значения осей:

    option.xAxis.data = currentCategories;
    

    И наконец обновим объект диаграммы новой конфигурацией настроек:

    option && chart.setOption(option);
    

    Код функции render представлен ниже:

    function render() {
      if (!window.DATA.data.length) return;
      const [currentCategories, currentData, dsum] = 
      transformData(window.DATA.data);
    
      option.series[0].data = dsum;
      option.series[1].data = currentData;
      option.xAxis.data = currentCategories;
      option && chart.setOption(option);
    }
    

Итог

Полный код вкладки HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.0/echarts.min.js" integrity="sha512-k37wQcV4v2h6jgYf5IUz1MoSKPpDs630XGSmCaCCOXxy2awgAWKHGZWr9nMyGgk3IOxA1NxdkN8r1JHgkUtMoQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<article class="widget-card">
  <header>
    <h1 class="main-header">Динамика поступлений на склады</h1>
    <h2 class="sub-header">с 2018 года</h2>
  </header>
  <div class="chart-container">
    <div id="chart"></div>
  </div>
</article>  
Полный код вкладки CSS
@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap');

:root {
  --main-color: white;
  --text-color: black;
}
 
:root.dark {
  --main-color: rgb(43, 43, 43);
  --text-color: #d3d6e0;
}

.widget-card {
    font-family: 'Montserrat', sans-serif;
    border: 1px solid #757784;
    border-radius: 24px;
    width: 100%;
    height: 100%;

    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding-inline: 37px;
    padding-block: 28px;
 
    color: var(--text-color);
    background-color: var(--main-color);
}

.main-header {
    font-size: 20px;
    font-weight: 500;
    line-height: 24px;
}

.sub-header {
    font-size: 14px;
    font-weight: 400;
    line-height: 22px; 
}

.chart-container {
    width: 100%;
    height: 100%;
}

#chart {
    width: 100%;
    height: 100%;
}
Полный код вкладки JS
// ECHARTS INIT
let chartDom = document.getElementById('chart');
let chart = echarts.init(chartDom);

window.addEventListener('resize', () => {
  chart.resize();
})

const option = {
  title: {
    text: 'Waterfall Chart',
    show: false
  },
  tooltip: {
    show: true,
    trigger: 'item'
  },
  xAxis: { type: 'category', data: [], axisLabel: { show: true } },
  yAxis: {},
  series: [
    {
      name: 'Placeholder',
      type: 'bar',
      stack: 'Total',
      itemStyle: {
        borderColor: 'transparent',
        color: 'transparent'
      },
      emphasis: {
        itemStyle: {
          borderColor: 'transparent',
          color: 'transparent'
        }
      },
      data: [],
      tooltip: {
        show: false,
        extraCssText: 'display: none;'
      },
      stackStrategy: 'all'
    },
    {
      name: 'Итог за месяц',
      type: 'bar',
      stack: 'Total',
      stackStrategy: 'all',
      data: [],
      label: {
        show: true,
        position: 'outside'
      },
      // tooltip: {},
      itemStyle: { color: v => { return v.value > 0 ? '#65cccc' : '#f96c89' } },
    }
  ]
};

function transformData(data) {
  const currentData = [];
  const currentCategories = [];
  let dsum = [0];
  let sum = 0;

  data.forEach(row => {
    currentData.push(row.calc__income_exprense_diff.agg_value);
    currentCategories.push(row.calc__year_month.value);

    sum += row.calc__income_exprense_diff.agg_value;
    dsum.push(sum);
  });

  return [currentCategories, currentData, dsum];
}

function render() {
  if (!window.DATA.data.length) return;
  const [currentCategories, currentData, dsum] = transformData(window.DATA.data);
  option.series[0].data = dsum;
  option.series[1].data = currentData;
  option.xAxis.data = currentCategories;
  option && chart.setOption(option);
}

В результате получили кастомную waterfall-диаграмму. Общие принципы из данного примера можно использовать для создания других типов визуализаций.

Примеры доступны для демо-пользователей https://aw-demo.ru/, запись прямого эфира можно посмотреть по ссылке:
https://www.youtube.com/watch?v=qRS1QGojVUc&t=1s

image

2 лайка