Перейти к основному содержимому

11 Книга рецептов

Разделение фронтенда Luxms BI на независимые секторы

Подразумевается, что вы решаете одну из следующих общих задач:

  • Хотите разместить на одном инстансе Luxms BI несколько разных стендов, различающихся и по функционалу и по внешнему виду. Чтобы заходя на конкретный атлас вы получали поведение, определяемое его атласом-родителем;
  • Хотите объединить атласы так, чтобы разные пользователи с разными ролями видели разную итоговую картинку и функционал атласов. Причем значительно кастомизированный.

Веб-клиент версии 9 по умолчанию позволяет делать следующее:

  • Ограничивать доступ к атласам в зависимости от прав пользователя и его сайтовой роли;
  • Атласы способны содержать в ресурсах файлы, хранящие React-компоненты, переопределяющие строительные блоки приложения (например, окно логина, стартовая страница, заголовок раздела с дешбордами, левую панель на том же разделе и другое). Так же в его ресурсах могут храниться React-компоненты для тех кастомных визуализаций ,которые используются только в дешлетах данного атласа. То же самое касается темы и локализации.

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

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

Родительский атлас - обычный атлас, который выступает лишь в качестве хранилища ресурсов для своих атласов-детей. Он даже может быть (и обычно так и делается) помечен как “выключенный” в списке атласов (индикатор в плашке горит серым), так как скорее всего даже не будет содержать ни одного дешборда.

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

Правила формирования schema_name вариативны, пример неплохого стиля это ds_кластер_наименованиепроекта_номератласа. По умолчанию на платформе атлас создается с именем схемы вида ds_14, где 14 - идентификатор атласа в таблице атласов

Обычно родительский атлас имеет schema_name. содержащий индекс 0, например ds_myinstance_0.
Соответственно, его “дети” будут иметь schema_name равный ds_myinstance_1, ds_myinstance_2, и т.д. Так видно, что атласы разделяют некое единое пространство имен.

Чтобы сделать какой-либо атлас родительским достаточно указать его значение поля guid в поле parent_guid у атласа, который соответсвенно должен выступать дочерним.

Пример guid : 18d935b4-06e9-4e59-b13e-f0cb3c2f35f0.

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

Замена favicon

Для замены фавиконки загрузите в раздел ресурсов атласа файлы:


favicon.ico //достаточно и этого в базовом виде
apple-touch-icon.png
favicon-16x16.png
favicon-32x32.png
safari-pinned-tab.svg

Если вы загрузите этот набор в ds_res - это коснется фавиконки для всего инстанса.
Если при этом еще и загрузите похожий набор в родительский атлас, то это изменит фавиконки атласов-детей.
Если загрузите еще и в конкретный атлас - то применится только на нем.

Установка логотипа приложения

Блок с лого состоит из самого логотипа и названия приложения.

Логотип Luxms BI.png

Для замены лого загрузите в раздел ресурсов аналогично фавиконке файлы (только один из них):


/* Эти файлы оба вставят лого, но разного формата */
thumbnail.png
thumbnail.svg //или logo.svg

/* Если есть этот файл, то в блоке с логотипом будет только данный логотип, а название проекта скроется */
thumbnailFull.svg

Установка прелоадера

Для замены лого прелоадера (анимированная иконка при первичной загрузке приложения) загрузите в раздел ресурсов атласа ds_res файл:


logo-animated.svg

Загружаем в атлас свои стили, шрифты

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

Они подключаются на страницу при помощи тега <link>.

Общие для всех дешбордов текущего атласа стили зовутся _global.css или styles.css (последнее предположительно скоро будет deprecated).

Так же, учитывая ролевые особенности родительских атласов и ds_res, вы можете разместить файлы стилей _global.css в каждом из них и по итогу получить несколько подключений файлов стилей, по приоритету:

  • сначала стили ds_res (если есть - будут подключаться к каждому атласу и его дешбордам);
  • затем цепочка стилей родительских атласов (только для дочерних атласов и их дешбордов);
  • стили текущего атласа (для всех дешбордов текущего атласа);
  • стили для текущего дешборда текущего атласа будут дополнительными стилями. Такой файл для дешборда с id = 1 будет называться styles.dboard=1.css.

Чтобы добавить на страницу кастомный шрифт - загрузите в ресурсы атласа ds_res файлы веб-шрифта (woff,woff2,ttf,otf,svg) и в файле стилей, например _global.css укажите font-face для шрифта и примените шрифт к html, body или нужному вам селектору на странице

Пример:

@font-face {
font-family: 'DIN Pro, Arial, sans-serif';
font-style: normal;
font-weight: lighter;
src: local('DIN Pro'),
url('/srv/resources/ds_res/fonts/dinpro_light.otf') format('opentype');
}

....

html, body {
font-family: 'DIN Pro, Arial, sans-serif';
}

При написании стилей привязывайтесь к селекторам по человекопонятным классам, а не атомарным, так как последние имеют свойство меняться.

Работа с темами

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

Каждый ключ верхнего уровня в таком файле - идентификатор темы. По умолчанию в коробке Luxms BI две темы: светлая (light) и темная (dark).

Ключи внутри соответствующих тем используют в качестве значения какие-либо CSS-свойства или объекты, необходимые для конфигурации специфических разделов фронтенда (карты, графики на Apache Echarts).

В результате работы с конфигом тем веб-клиент создает набор CSS-переменных (их можно увидеть у тега body в инспекторе браузера). Потому, что именно хранить в значении свойства определяется допустимыми значениями, принимаемыми CSS-переменными.

Список базовых свойств, повторяющийся для всех тем


color1: "Базовый цвет 1";
color2: "Базовый цвет 2";
color3: "Базовый цвет 3";
color4: "Базовый цвет 4";
color5: "Базовый цвет 5";
color6: "Базовый цвет 6";
color7: "Базовый цвет 7";
color8: "Базовый цвет 8";
color9: "Базовый цвет 9";
color10: "Базовый цвет 10";
color11: "Базовый цвет 11";
color12: "Базовый цвет 12";
color13: "Базовый цвет 13";
color14: "Базовый цвет 14";
color15: "Базовый цвет 15";
color16: "Базовый цвет 16";
color17: "Базовый цвет 17";
color18: "Базовый цвет 18";
color19: "Базовый цвет 19";
color20: "Базовый цвет 20";
color_conditions: "Цвет условий";
system_panel: "Системный цвет (панели)";
system_background: "Системный цвет (бекграунд)";
system_effect: "Системный цвет (эффект)";
system_selection: "Системный цвет (Выделение)";
system_element: "Системный цвет (Элементы)";
neutral_300: "Нейтральный цвет (насыщенность 300)";
neutral_400: "Нейтральный цвет (насыщенность 400)";
neutral_500: "Нейтральный цвет (насыщенность 500)";
neutral_600: "Нейтральный цвет (насыщенность 600)";
neutral_700: "Нейтральный цвет (насыщенность 700)";
neutral_800: "Нейтральный цвет (насыщенность 800)";
neutral_900: "Нейтральный цвет (насыщенность 900)";
active_firstdefault: "Цвет заливки для основных кнопок";
active_firsthover: "Цвет заливки для оснвных кнопок (отображение эффекта наведения)";
active_firstcontrast: "Цвет для контрастных элементов на первостепенных кнопках (текст, иконки)";
active_secondcontrast: "Основной цвет для второстепенных элементов (кнопки без заливки, кнопки с обводкой, ссылки)";
active_seconddefault: "Цвет заливки для второстепенных кнопок";
active_secondhover: "Цвет заливки для второстепенных кнопок (эффект наведения)";
state_success: "Цвет состояния 'Успешно выполнено'";
state_warning: "Цвет состояния 'Предупреждение'";
state_error: "Цвет состояния 'Ошибка'";
themeBuilder: "Объект, хранящий доп.настройки для коробочных графиков на ECharts"

Это те свойства, которые любая тема будет иметь по умолчанию. Значения зависят от темы, разумеется. Однако вы можете добавлять и свои свойства в этот набор.

Рассмотрим пример файла themes.json:


{
"dark":{
"color7": "rgba(151, 151, 196, 0.3)",
"color8": "#0070BA",
"color20": "#fff",
"color19": "#212D3C",
"color11": "rgba(255, 255, 255, 0.1)",
"system_panel": "#273445",
"system_background": "#273445",
"system_effect": "#3D4958",
"system_selection": "#212D3D",
"system_element": "#8EA4CE",
"neutral_300": "#F3F4F5",
"neutral_400": "#D4D6DA",
"neutral_500": "#A6ACB8",
"neutral_600": "#828894",
"neutral_700": "#707580",
"neutral_800": "#505661",
"neutral_900": "#3E444F",
"themeBuilder":{
"color":[
"#ADD4DE",
"#F6F6F6",
"#F1D1D7",
"#E88898",
"#EDADB7",
"#F1D1D7",
"#F6F6F6",
"#DAE5F1",
"#BED4EC",
"#A1C2E7",
"#85B1E2"
]
}
},
"light":{
"color7": "rgba(151, 151, 196, 0.3)",
"color8": "#0070BA",
"color20": "#fff",
"color19": "#F2F2F8",
"color11": "rgba(151, 151, 196, 0.1)",
"system_panel": "#FFFFFF",
"system_background": "#878787",
"system_effect": "#F6F6F9",
"system_selection": "#E9E9F5",
"system_element": "#9797C4",
"neutral_300": "#363636",
"neutral_400": "#4F4F4F",
"neutral_500": "#686868",
"neutral_600": "#828282",
"neutral_700": "#9C9C9C",
"neutral_800": "#CECECE",
"neutral_900": "#E8E8E8",
"themeBuilder":{
"color":[
"#54A2AE",
"#FFF1E6",
"#E36378",
"#E88898",
"#EDADB7",
"#F1D1D7",
"#F6F6F6",
"#DAE5F1",
"#BED4EC",
"#A1C2E7",
"#85B1E2"
]
}
}
}

Как вы видите, здесь не полный перечень свойств, указанный выше. Но это и не нужно. Файл themes.json мержится веб-клиентом с тем конфигом тем, который есть из коробки. Т.е. вам достаточно указать только те свойства, которые вы хотите изменить или добавить в конкретной теме, а остальные останутся как есть.

Вам лишь нужно добавить новые свойства, отсутствующие по умолчанию в наборе во все ваши темы. Чтобы при переключении темы в интерфейсе ваши React-компоненты, которые будут использовать эту переменную для стилизации не получили некорректное значение.

Общая настройка темы графиков у коробочных визуализаций

Уникальный ключ themeBuilder для темы хранит конфиг графика на библиотеке Apache ECharts, чей движок используется на платформе по умолчанию, за исключением узкого круга специфических визуализаций.

Этот блок будет автоматически наполняться в интерактивном разделе, но пока вы можете использовать для этого нативный раздел библиотеки тут:

https://echarts.apache.org/en/theme-builder.html

Конструктор темы

Вам нужно будет провести нужные вам настройки для соответствующей темы и по окончании кликнуть по кнопке Download.
Затем перейти в таб JSON version

Загрузка темы

И нажать Copy. Вы скопируете готовый для вставки в качестве значения в themeBuilder объект.

Импорт такого файла не предусмотрен.

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

На примере themes.json выше themeBuilder содержит ключ color с массивом hex-цветов. Этот массив определяет палитру цветов, которую по умолчанию станут использовать графики при включенной теме. Пока еще эта палитра не является замкнутой, то есть как только серий на графике станет больше чем число цветов палитры - для выходящих за рамки будут использоваться дефолтные цвета палитры веб-клиента.

А это вот такой список:

"#AA6FAC",
"#E85498",
"#4F4F9B",
"#4AB6E8",
"#E07921",
"#5FB138",
"#F05045",
"#F2BB05",
"#9797C4"

Создание новых и отключение существующих тем

Если указать напротив какого либа ключа темы null, то вы таким образом выключите ее. Если в результате остается всего одна тема - вы не увидите в тулбаре страницы dashboards иконки смены текущей темы.


{
"light": null
}

Чтобы создать новую тему просто добавьте еще один ключ верхнего уровня и в базовом случае пустой объект


{
"pink": {}
}

У вас создатся тема pink, которая по умолчанию будет наследовать базовые значения темы light из коробки (не того, что вы переопределили здесь, в файле themes.json!) и, памятуя об этом, переопределите те свойства, которые вам потребуются для уникализации новой темы, вплоть до полного переопределения всех свойств.

Иерархическая логика переопределения тем из ресурсов ds_res, родительского атласа(если есть) и к текущему атласу сохраняется. То есть на конкретном атласе могут быть темы, не присутствующие на остальных, или вообще единственная тема.

Сервис для работы с темами ThemeVC

ThemeVC - Singleton observable сервис, который предоставляет вам список существующих тем и их итоговых после всех мержей свойств, а также методы подписки на смену темы, методы эту смену осуществляющие

Модель


{
error: null, // объект возможной ошибки
loading: false, // индикатор готовности сервиса (true значит, что сервис в процессе загрузки модели)
themes: {"light": {...}, "dark": {...}}, // доступные темы
currentTheme: {...}, // объект со свойствами текущей темы
currentThemeId: 'light', // идентификатор текущей темы
}

Метод setTheme для выставления темы


public setTheme(currentThemeId: string) {
/*
данный метод установит вам тему нативным образом, сохранив выбранный ключ темы в localStorage вашего браузера. В будущем, возможно, это будет конфиг пользователя, чтобы тема сохранялась за пользователем вне зависимости от браузера.
*/
let currentTheme = this._model.themes[currentThemeId];
if (currentTheme) {
localStorage.setItem('theme', currentThemeId);
this._applyThemeVariables(currentTheme, currentThemeId);

// Данный метод уведомит всех подписчиков сервиса о смене темы
this._updateModel({
currentTheme,
currentThemeId
});
}
}

Подписка и программное изменение темы в React-компонентах

В классовом

import React from 'react';
import {ThemeVC} from 'bi-internal/services;

class MyComponent extends React.Component {
private _themeVC: ThemeVC;
public state: {
currentTheme: any;
currentThemeId: string;
};

public constructor(props) {
super(props);
this.state = {
currentTheme: null;
currentThemeId: "light"
}
}

public componentDidMount() {
this._themeVC = ThemeVC.getInstance();
this._themeVC.subscribeUpdatesAndNotify(this._onThemeUpdated)
}

private _onThemeUpdated = (model) => {
if (model.loading || model.error) return;

this.setState({
currentTheme: model.currentTheme,
currentThemeId: model.currentThemeId
});
}

private _onChangeTheme = () => {
const {currentThemeId} = this.state;
this._themeVC.setTheme(currentThemeId === 'light' ? 'dark' : 'light');
}

public render() {
const {currentTheme} = this.state;

return (
<>
{currentTheme &&
<div className="ThemeChangerDemo">
<div style={{color: currentTheme.color1}}>Привет мир!</div>}
<button onClick={onChangeTheme}>Сменить тему</button>
</div>
}
</>
);
}
}

export default MyComponent;
В функциональном

import React, {useState} from 'react';
import {useServiceItself, useService, ThemeVC} from 'bi-internal/services;

const MyComponent = (props) => {
const themeVC = useServiceItself<ThemeVC>(ThemeVC);

const onChangeTheme = () => {
const currentThemeId = themeVC.getModel().currentThemeId;
themeVC.setTheme(currentThemeId === 'light' ? 'dark' : 'light');
}

if (themeVC.getModel().loading || themeVC.getModel().error) return null;

return (
<div className="ThemeChangerDemo">
<div style={{color: themeVC.getModel().currentTheme.color1}}>Привет мир!</div>
<button onClick={onChangeTheme}>Сменить тему</button>
</div>
)
}

export default MyComponent;

В данных компонентах я меняю свойство color текста засчет значения переменной темы color1 (я мог выбрать любую переменную) и методом setTheme делаю переключение для темы со светлой на темную и обратно.

Хук useServiceItself вернет мне инстанс сервиса. Таким образом я могу получить и модель и использовать методы.

Если бы я писал функциональный компонент, который только получал бы значение color1 и устанавливал его без возможности сменить в интерфейсе этого же компонента, то я бы мог использовать хук useService, который вернул бы мне не инстанс сервиса, а только лишь значение его модели (themeVC.getModel()). Тогда методы сервиса мне были бы уже недоступны.

Использование темы при стилизации

Лучше всего будет завести специальный файл типа vars.scss в котором хранить все переиспользуемые CSS-переменные, в том числе переменные отвечающие за тему.

$color1: var(--color1);
$color2: var(--color2);
$color3: var(--color3);
$color4: var(--color4);
$color5: var(--color5);
$color6: var(--color6);
$color7: var(--color7);
$color8: var(--color8);
$color9: var(--color9);
$color10: var(--color10);
$color11: var(--color11);
$color12: var(--color12);
$color13: var(--color13);
$color14: var(--color14);
$color15: var(--color15);
$color16: var(--color16);
$color17: var(--color17);
$color18: var(--color18);
$color19: var(--color19);
$color20: var(--color20);
$system_panel: var(--system_panel);
$system_background: var(--system_background);
$system_effect: var(--system_effect);
$system_selection: var(--system_selection);
$system_element: var(--system_element);
$neutral_300: var(--neutral_300);
$neutral_400: var(--neutral_400);
$neutral_500: var(--neutral_500);
$neutral_600: var(--neutral_600);
$neutral_700: var(--neutral_700);
$neutral_800: var(--neutral_800);
$neutral_900: var(--neutral_900);
$active_firstdefault: var(--active_firstdefault);
$active_firsthover: var(--active_firsthover);
$active_firstcontrast: var(--active_firstcontrast);
$active_secondcontrast: var(--active_secondcontrast);
$active_seconddefault: var(--active_seconddefault);
$active_secondhover: var(--active_secondhover);
$state_success: var(--state_success);
$state_warning: var(--state_warning);
$state_error: var(--state_error);

А в файле стилей вашего компонента использовать их так:

// Подключили переменные темы
@import "./vars.scss";

.MyComponent {
width: 100%;
height: 100%;
position: relative;
background-color: $system_background;

&__graphic {
position:absolute;
z-index: 0;
width: 100%;
height: 100%;
}
&__onClickText {
position:absolute;
z-index: 1;
left: 0;
right: 0;
top: 1.5rem;
text-align: center;
color: $color1;
}
}

Работа с локализацией

Язык локализации по умолчанию определяется своим ключом из файла settings.js и наличием файла .json с кратким ключом выбранного языка в обвязке (по умолчанию, в обвязке есть только en.json и ru.json для английского и русского языков соответственно).

Расширение языкового набора возможно, если вы разместите в ресурсах атласа ds_res папку lux-store/locales/ и поместите туда переведенную версию одного из существующих файлов локализации.

Пример файла локализации:

Взять один из сущетвующих файлов локализации вы можете по адресу
имя_вашего_сайта/assets/locales/ru.json
или
имя_вашего_сайта/assets/locales/en.json.


{
"LuxmsBI": "Luxms BI",
"home": "Главная",
"back": "Назад",
"close": "Закрыть",
"collapse": "Скрыть",
"add": "Добавить",
"Between": "Между",
"More": "Больше",
"Less": "Меньше",
"contacts": "Контакты",
"header": "Заголовок",
"Header": "Заголовок",
"create": "Создать",
"Create": "Создать",
"copy": "Копировать",
"docs": "Документация",
"error": "Что-то пошло не так...",
"expand": "Подробнее",
"log_in": "Войти",
"log_out": "Выйти",
"Log out": "Выйти",
"log_in_wrong": "Неверный логин или пароль",
"code_wrong": "Неверный код",
"log_in_too_much_requests": "Вы превысили количество возможных запросов.",
"login": "Логин",
"Login": "Логин",
"no": "Нет",
"No": "Нет",
"norm": "Границы",
"ok": "OK",
"cancel": "Отмена",
"open": "Открыть",
"password": "Пароль",
"Password": "Пароль",
"Password once again": "Пароль ещё раз",
"password_policy": "Парольная политика",
"Password policy": "Парольная политика",
"Change password after": "Сменить пароль после",
"print": "Печать",
"rest": "Остальные",
"check": "Проверить",
"result": "Результат",
"Results": "Результаты",
"resources": "Ресурсы",
"save": "Сохранить",
"Save": "Сохранить",
"save-": "Сохранить",
"save_as": "Сохранить как",
"save-png": "Сохранить как png",
"save-arrow": "Сохранить arrow",
"save-xls": "Сохранить как xls",
"save-parquet": "Сохранить parquet",
"save-csv": "Сохранить как csv",
"save-pdf": "Сохранить как pdf",
"switch_theme": "Переключить тему",
"Save changes": "Сохранить изменения",
"search": "Искать",
// много-много ключей
}

Добавление нового языка

Для добавления, например, французского языка вы должны поместить в раздел ресурсов атласа в иерархии ds_res => родительский атлас => текущий атлас файл fr.json, загрузив его на сервер или средствами проекта BMR или вручную, через drag’n’drop, и изменив его название на полный путь до папки выше: lux-store/locales/fr.json.

Т.е. по итогу у вас должен существовать файл по адресу

имя_вашего_сайта/srv/resources/имя_атласа_в_иерерахии/lux-store/locales/fr.json

Допустим поместили вы файлы переводов в ds_res, тогда ваши файлы будут тут

/srv/resources/ds_res/lux-store/locales/fr.json

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

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

Замена строк в существующей локализации

Допустим , вы хотите поменять слово дешборд на интерактивная панель визуализаций.

Открываете файл исходных данных имя_вашего_сайта/assets/locales/ru.json.

Создаете в ресурсах устраивающего вас атласа файл lux-store/locales/ru.json

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

И в нем пишете:


{
"go_to_dashboards": "Перейти к интерактивным панелям визуализаций",
"startup_dashboard": "Стартовая интерактивная панель визуализаций",
"dashboard": "Интерактивная панель визуализаций",
"dashboards": "Интерактивные панели визуализаций",
"hideDashboard": "Скрыть интерактивную панель визуализаций",
"header_Dashboard": "Формат строки контекста для экрана интерактивной панели визуализаций",
"new_dashboard": "Новая интерактивная панель визуализаций"
}

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

Компонент для локализации L10n

Является компонентом-оберткой на вашим React-компонентом при разработке в рамках проекта bi-magic-resources.

Подразумевается, что все служебные тексты вы пишете на английском, а их перевод храните в файлах типа ru.json и подобным.

Можно оборачивать как конкретный ключ локализации, так и весь компонент. Заменяются текстовые блоки, аттрибуты title, description и placeholder.

Слишком высоко поднимать такой компонент тоже не стоит. Иначе можете получить перевод данных и идентификаторов кубов.

Пример использования:


import React, { useEffect, useState } from "react";
import {L10n, L10nVC} from 'bi-internal/ui';
import MySomeCustomComponent from './MySomeCustomComponent';

const MyComponent = (props) => {
return (
<React.Fragment>
<button className="MyComponentButton">
<L10n>go_to_dashboards</L10n>
</button>
<L10n><div title="some_key"></div></L10n>
<L10n>
<MySomeCustomComponent {...props}/>
</L10n>
</React.Fragment>
);
}

export default MyComponent;

Сервис для работы с локализацией L10nVC

L10nVC - Singleton observable сервис, который предоставляет вам список существующих локализаций и их итоговых после всех мержей свойств, а также методы подписки на смену локализации, методы эту смену осуществляющие

Модель


{
error: null, // объект возможной ошибки
loading: false, // индикатор готовности сервиса (true значит, что сервис в процессе загрузки модели)
locales: {ru: {...}, en:{...}}, // доступные локализации
name: 'ru', // идентификатор локали
current: {LuxmsBI: 'Luxms BI', home: 'Главная', back: 'Назад',...} // доступные ключи текущей локализации
}

Методы setLocale, setLocaleAndSave для выставления локализации


public setLocale(name: string) {
/*
данный метод установит вам выбранную локализацию. Но после перезагрузки страницы вернется дефолтная локаль из settings.js
*/

}

public setLocaleAndSave(name: string) {
/*
данный метод установит выбранную локализацию в localStorage и вернет результат setLocale
*/

}

import {lang} from 'bi-internal/utils';

function lang(key: string, defaultValue?: string) {
/*
данный метод вернет defaultValue, если сервис локализации не загружен или содержит ошибку, переведенное значение ключа в рамках текущей локали и если такого ключа нет - вернет его.

Кроме того key может быть и массивом строк, тогда будет переведен каждый ключ массива по правилам выше и сконкатенирован через символ ''
*/

}

const example = {
title: lang('Data source'),
//...
}

Подписка и программное изменение локализации в React-компонентах

Подписка на изменение локализации выглядит аналогично тому, как это выглядит при работе с темами.

Приведем пример только функционального компонента


import React, {useState} from 'react';
import {useServiceItself, useService} from 'bi-internal/services;
import {L10n, L10nVC} from 'bi-internal/ui;

const MyComponent = (props) => {
const l10nVC = useServiceItself<L10nVC>(L10nVC);

const onChangeLocale = () => {
const name = l10nVC.getModel().name;
l10nVC.setLocaleAndSave(name === 'ru' ? 'en' : 'ru');
}

if (l10nVC.getModel().loading || l10nVC.getModel().error) return null;

// "Click or drag and drop to upload" - это ключ локализации, который имеет соответственно
// два перевода для двух существующих локализаций
return (
<div className="LocaleChangerDemo">
<div><L10n>Click or drag and drop to upload</L10n></div>
<button onClick={onChangeLocale}>Сменить язык</button>
</div>
)
}

export default MyComponent;

Работа с иконками

Спрайт с доступными иконками

Для того, чтобы увидеть все доступные иконки октройте консоль браузера и наберите shell.showSprite().

Вы увидите попап со списком иконок

sprite.png

Подписи под каждой иконкой есть идентификаторы которые вам будет нужно указать в компоненте для отрисовки иконок SVGIcon.

Это классический svg-спрайт, где каждая иконка - symbol

<svg xmlns="http://www.w3.org/2000/svg">
<symbol viewBox="0 0 20 18" id="atlas">
<path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor"
d="M1.6665 0.5C1.11422 0.5 0.666504 0.947715 0.666504 1.5V14C0.666504 14.5523 1.11422 15 1.6665 15H7.49984C7.89766 15 8.27919 15.158 8.5605 15.4393C8.8418 15.7206 8.99984 16.1022 8.99984 16.5C8.99984 17.0523 9.44755 17.5 9.99984 17.5C9.99989 17.5 9.99995 17.5 10 17.5C10.5523 17.5 11 17.0523 11 16.5C11 16.1022 11.158 15.7206 11.4393 15.4393C11.7206 15.158 12.1022 15 12.5 15H18.3333C18.8856 15 19.3333 14.5523 19.3333 14V1.5C19.3333 0.947715 18.8856 0.5 18.3333 0.5H13.3333C12.1841 0.5 11.0819 0.956546 10.2692 1.7692C10.1746 1.86385 10.0847 1.96242 9.99992 2.06454C9.91509 1.96242 9.82528 1.86385 9.73063 1.7692C8.91798 0.956546 7.81577 0.5 6.6665 0.5H1.6665ZM8.99984 4.83333V13.3377C8.53541 13.1174 8.02368 13 7.49984 13H2.6665V2.5H6.6665C7.28534 2.5 7.87884 2.74583 8.31642 3.18342C8.754 3.621 8.99984 4.21449 8.99984 4.83333ZM12.5 13C11.9762 13 11.4644 13.1174 11 13.3377V4.83333C11 4.21449 11.2458 3.621 11.6834 3.18342C12.121 2.74583 12.7145 2.5 13.3333 2.5H17.3333V13H12.5Z"
/>
</symbol>
<symbol viewBox="0 0 24 24" id="book">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M2 2C1.44772 2 1 2.44772 1 3V18C1 18.5523 1.44772 19 2 19H9C9.53043 19 10.0391 19.2107 10.4142 19.5858C10.7893 19.9609 11 20.4696 11 21C11 21.5523 11.4477 22 12 22C12.5523 22 13 21.5523 13 21C13 20.4696 13.2107 19.9609 13.5858 19.5858C13.9609 19.2107 14.4696 19 15 19H22C22.5523 19 23 18.5523 23 18V3C23 2.44772 22.5523 2 22 2H16C14.6739 2 13.4021 2.52678 12.4645 3.46447C12.2962 3.63275 12.1411 3.81178 12 3.99997C11.8589 3.81178 11.7038 3.63275 11.5355 3.46447C10.5979 2.52678 9.32608 2 8 2H2ZM13 17.5359C13.6029 17.1878 14.2918 17 15 17H21V4H16C15.2044 4 14.4413 4.31607 13.8787 4.87868C13.3161 5.44129 13 6.20435 13 7V17.5359ZM11 17.5359V7C11 6.20435 10.6839 5.44129 10.1213 4.87868C9.55871 4.31607 8.79565 4 8 4H3V17H9C9.70823 17 10.3971 17.1878 11 17.5359ZM4 9C4 8.44772 4.44772 8 5 8H9C9.55228 8 10 8.44772 10 9C10 9.55228 9.55228 10 9 10H5C4.44772 10 4 9.55228 4 9ZM15 8C14.4477 8 14 8.44772 14 9C14 9.55228 14.4477 10 15 10H19C19.5523 10 20 9.55228 20 9C20 8.44772 19.5523 8 19 8H15ZM4 13C4 12.4477 4.44772 12 5 12H9C9.55228 12 10 12.4477 10 13C10 13.5523 9.55228 14 9 14H5C4.44772 14 4 13.5523 4 13ZM15 12C14.4477 12 14 12.4477 14 13C14 13.5523 14.4477 14 15 14H19C19.5523 14 20 13.5523 20 13C20 12.4477 19.5523 12 19 12H15Z"
fill="currentColor"/>
</symbol>
<symbol viewBox="0 0 20 20" id="dashboard">
<path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor"
d="M11 2V6L18 6V2L11 2ZM10 4.17233e-07L19 0C19.2652 0 19.5196 0.105357 19.7071 0.292893C19.8946 0.48043 20 0.734784 20 1V7C20 7.55228 19.5523 8 19 8H10C9.73478 8 9.48043 7.89464 9.29289 7.70711C9.10536 7.51957 9 7.26522 9 7V1C9 0.447716 9.44771 4.17233e-07 10 4.17233e-07ZM2 2L2 6L5 6L5 2L2 2ZM1 7.7486e-07L6 5.96046e-07C6.55228 5.36442e-07 7 0.447716 7 1L7 7C7 7.55229 6.55229 8 6 8L1 8C0.734784 8 0.48043 7.89464 0.292894 7.70711C0.105357 7.51957 2.98023e-07 7.26522 2.38419e-07 7L0 1C0 0.447716 0.447715 8.34465e-07 1 7.7486e-07ZM2 12L2 18H8L8 12L2 12ZM1 10L9 10C9.26522 10 9.51957 10.1054 9.70711 10.2929C9.89464 10.4804 10 10.7348 10 11L10 19C10 19.2652 9.89464 19.5196 9.70711 19.7071C9.51957 19.8946 9.26522 20 9 20H1C0.447716 20 8.34465e-07 19.5523 7.7486e-07 19L4.17233e-07 11C4.17233e-07 10.4477 0.447716 10 1 10ZM12.2929 10.2929C12.4804 10.1054 12.7348 10 13 10H19C19.5523 10 20 10.4477 20 11V19C20 19.5523 19.5523 20 19 20H13C12.4477 20 12 19.5523 12 19L12 11C12 10.7348 12.1054 10.4804 12.2929 10.2929ZM14 12L14 18H18V12H14Z"
/>
</symbol>
<symbol viewBox="0 0 17 15" id="edit-pen">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M13.627 1.57895C13.3455 1.57895 13.0848 1.68197 12.8999 1.85156L2.38704 11.4956L1.93876 13.1405L3.83095 12.7066L14.3541 3.0531C14.4451 2.96961 14.5145 2.87313 14.5608 2.77052C14.6071 2.66805 14.6301 2.56008 14.6301 2.45233C14.6301 2.34458 14.6071 2.23661 14.5608 2.13414C14.5145 2.03153 14.4451 1.93505 14.3541 1.85156C14.263 1.76797 14.1521 1.6993 14.0268 1.65166C13.9013 1.604 13.7654 1.57895 13.627 1.57895ZM11.7906 0.701469C12.2856 0.247358 12.947 0 13.627 0C13.9647 0 14.3001 0.0609677 14.6146 0.180443C14.929 0.299938 15.2179 0.476237 15.4634 0.701469C15.709 0.926795 15.9067 1.19694 16.0425 1.49782C16.1785 1.79885 16.2492 2.12334 16.2492 2.45233C16.2492 2.78132 16.1785 3.10581 16.0425 3.40684C15.9067 3.70773 15.709 3.97787 15.4634 4.20319L4.78218 14.0017C4.67862 14.0967 4.55124 14.1634 4.41284 14.1951L0.994855 14.979C0.717941 15.0425 0.427169 14.9596 0.228937 14.7607C0.0307057 14.5617 -0.0458907 14.2758 0.0271763 14.0077L0.881673 10.8722C0.920474 10.7298 0.99935 10.6009 1.10938 10.5L11.7906 0.701469ZM7.69049 14.2105C7.69049 13.7745 8.05292 13.421 8.50001 13.421H16.1905C16.6376 13.421 17 13.7745 17 14.2105C17 14.6465 16.6376 15 16.1905 15H8.50001C8.05292 15 7.69049 14.6465 7.69049 14.2105Z"
fill="currentColor"/>
<!-- И многие другие -->
</symbol>

Со спрайтом по умолчанию можно ознакомиться по адресу site.ru/assets/icons/sprite.svg или набрать в консоли shell.showSprite() и открыть раздел Network браузера. В появившемся запросе за спрайтом будет актуальный его контент.

Чтобы изменить спрайт на свой или заменить лишь некоторые иконки (здесь тоже есть мерж с оригинальным файлом, потому достаточно указать один-два интересующих вас символа спрайта) создаем файл svg, схожий с примером выше и помещаем его в раздел ресурсов нужного вам по иерархии атласа, например в ds_res.

Обратите внимание на значение поля fill у path. currentColor означает, что иконка будет брать в качестве цвета значение свойства color у родительского элемента по правилам CSS.

Компонент для отрисовки иконок SVGIcon

SVGIcon - React- компонент, который может рисовать иконки при помощи различных источников svg-контента.

Пример:


import React from 'react';
import {SVGIcon} from 'bi-internal/ui;

const MyComponent = (props) => {
return (
<div className="IconDemo">
<SVGIcon
style={{position: 'absolute', display: 'none'}}
className="SVGSprite"
path="#atlas"
/>
</div>
)
}

export default MyComponent;

Ключевым props компонента является path. Он может принимать:

  • #atlas - ссылка на элемент спрайта
  • Настоящий svg вроде <svg><path d=""></svg>
  • ссылку на файл .svg

и по итогу рисует иконку как послушный зайка.

Написание кастомных React-компонентов

React-компонент на Apache ECharts, фильтрующий данные по клику, следящий за ресайзом окна дешборда

Рассмотрим конфиг дешлета для которого этот компонент используется:


{
url: 'res:MyComponent.js',
frame: {
h: 8,
w: 12,
x: 0,
y: 0,
},
dataSource: {
koob: 'BeerDataSource.beerKoob',
style: {
measures: {
count_units: {
color: 'red',
},
count_quantity: {
color: 'green',
},
},
},
xAxis: 'category',
yAxis: 'measures',
measures: [
'count(units):count_units',
'count(quantity):count_quantity',
],
dimensions: [
'category',
],
},
onClickDataPoint: "lpe:setKoobFilter('','category',['=',category])",
view_class: 'internal',
title: 'Test',
}

Код самого компонента:


import React, {useEffect, useRef, useState} from "react";
import * as echarts from 'echarts';

/*
KoobFiltersService - Observable сервис, управляющий фильтрами для кубов (по умолчанию его использует упр.дешлет)

useService, useServiceItself - специальные хуки для получения только модели или всего инстанса какого-либо observable сервиса. Принимает по умолчанию класс нужного сервиса и если это не singleton то еще и идентификатор (через запятую)
*/

import {KoobFiltersService, useService, useServiceItself} from 'bi-internal/services';

// специальный класс-наблюдатель за размерами элемента, благодаря ему будем ресайзить график
import {GeometryObserver} from 'bi-internal/face';

import './MyComponent.scss';

const MyComponent = (props) => {
const { cfg, subspace, dp } = props;

// для наглядности покажем на какую точку (Y,X) кликнули
const [clickedPointName, setClickedPointName] = useState<string>("");

// Получили инстанс сервиса фильтров
// через метод koobFiltersService.getModel() можем получить его модель,
// а через метод koobFiltersService.setFilter(koobId, dimensionId, valueArray) // ("", "category", ["=", "Beer"])
// можем фильтровать данные дешлетов, которые подписаны в своих блоках filters на изменение этого дименшна (содержат "category": true)
const koobFiltersService = useServiceItself<KoobFiltersService>(KoobFiltersService);

// Храним реф-ссылку на контейнер для графика, сам инстанс Echarts и опции, которые ему передаем
let containerRef = useRef(null);
let chart = null;
let options = {};

// Обрабатываем клик по точке графика. Проверяем, есть ли в конфиге дешлета свойство onClickDataPoint, отвечающая
// в коробке за логику клика на точку по умолчанию
// если нет - просто реализована произвольная логика выставления текущего значения дименшна в фильтр + для наглядности
// показываем в интерфейсе строчку с "координатами" кликнутой точки

const onChartClick = (params): void => {
// о том, что входит в params можно подглядеть тут https://echarts.apache.org/en/api.html#events.Mouse%20events
if (cfg.getRaw().hasOwnProperty('onClickDataPoint')) {
// Формируем объект информации о точке для встроенного контроллера обработки клика по точке
const vcpv = {m: undefined, l: undefined, p: undefined, z: undefined, y: params.data.y, x: params.data.x, v: params.value};
cfg.controller.handleVCPClick(params.event, vcpv)
} else {
const koobFiltersModel = koobFiltersService.getModel();
if (koobFiltersModel.loading || koobFiltersModel.error) return;
koobFiltersService.setFilter('', params.data.x.axisIds[0], ["=",params.name]);
}
setClickedPointName(`${params.data.y.title} ${params.data.x.title}`);
}
const resize = () => {
if (chart) {
chart.resize();
}
}
const renderChart = (data) => {
// На инит рефа создаем с нуля или обновляем существующий инстанс Echarts и подаем ему опции на вход
// конфигурацию графиков Echarts смотрите тут https://echarts.apache.org/en/option.html#title
if (containerRef.current && data.length) {
if (!chart) {
chart = echarts.init(containerRef.current, null, {renderer: 'svg'});
GeometryObserver.getInstance().addSubscription(containerRef.current, resize);
}
options = {
title: {
show: false
},
tooltip: {
trigger: 'item',
appendToBody: true,
show: true
},
xAxis: {
type: 'category',
data: subspace.xs.map(x => x.title)
},
yAxis: {
type: 'value'
},
series: subspace.ys.map((y, yIndex) => ({
data: subspace.xs.map((x, xIndex) => ({
name: x.title,
itemStyle: {
// контроллер, который получает информацию о цвете автоматически, исходя из контекста
color: cfg.getColor(y, null, yIndex),
},
x,
y,
value: data[yIndex][xIndex] // мы получали матрицу YX
})),
name: y.title,
type: 'bar', // я задал этот тип явно, но это можно прочитать из конфига дешлета
//как переменную cfg.getRaw().chartType например
showBackground: true,
})),
legend: {
show: true,
data: subspace.ys.map((y, yIndex) => ({
name: y.title,
icon: 'circle',
itemStyle: {
// контроллер, который получает информацию о цвете автоматически, исходя из контекста
color: cfg.getColor(y, null, yIndex),
},
}))
},
};
chart.setOption(options);
chart.resize(); // принудительно заставляем расшириться на весь контейнер
chart.on('click', 'series', onChartClick); // Обрабатываем клик по серии, если нужно
}
}

useEffect(() => {
// Получаем полное декартово произведение для указанного конфига в дешлете
// ожидаем матрицу [subspace.ys.length][subspace.xs.length]

dp.getMatrixYX(subspace).then(dataArr => {
renderChart(dataArr);
});
return () => {
GeometryObserver.getInstance().removeSubscription(containerRef.current, resize);
}
},[cfg, subspace]);

return (
<div className="MyComponent">
{clickedPointName != "" && <div className="MyComponent__onClickText">Вы кликнули на {clickedPointName}</div>}
<div ref={containerRef} className="MyComponent__graphic"></div>
</div>
);
}
export default MyComponent;

Стили:

// Подключили переменные темы
@import "./vars.scss";

.MyComponent {
width: 100%;
height: 100%;
position: relative;

&__graphic {
position:absolute;
z-index: 0;
width: 100%;
height: 100%;
}
&__onClickText {
position:absolute;
z-index: 1;
left: 0;
right: 0;
top: 1.5rem;
text-align: center;
color: $color1;
}
}

Этот компонент строит по указанным из конфига данным график типа bar по правилам Apache ECharts. Умеет обрабатывать клик по точке графика, а именно выставлять в качестве фильтра на дименшн category значение этого дименшна в данной точке (за это отвечает запись вида setKoobFilters в блоке onClickDataPoint конфига). Это значит, что все соседние дешлеты, если содержат в своих конфигах запись вида


filters: {
category: true
}

автоматически перезапросят данные с указанным вашим кликом фильтром. Для наглядности мы еще показываем “координаты” кликнутой точки вверху графика.

Имейте, ввиду: если вы укажите запись вида category: true как в фильтрах выше у текущего дешлета, то получите фильтрацию дешлетом самого себя. Автоматически вернуться к прежнему состоянию без перезагрузки вы не сможете. Будьте внимательны с тем, чтобы четко определять какой дешлет должен выступать фильтрующим, а какой фильтруемым.

Ответ сервера для компонента:


{"category":"Cider","count_units":230,"count_quantity":230}
{"category":"Beer","count_units":852,"count_quantity":852}

Результат рендера компонента выше:

component.png

Написание компонента-наследника базовых классов, реализующих коробочные визуализации на ECharts

Основной класс из коробки, который формирует React-компонент, который вставляет график с готовым конфигом и функционалом Echarts - это BaseVizelEcharts

Он реализует несколько абстрактных методов, которые все его наследники определяют под свои нужды:


protected abstract _getEchartsConfig(vm) {
/*
принимает вью-модель текущего дешлета и на основе данных из нее и логики текущего графика формирует и возвращает объект конфигурации графика на Echarts.
При написани своего компонента-наследника - именно этот метод вам и нужно переопределить, чтобы компонент сразу был готов к использованию
*/
}

protected __createChart(vm: VM): any {
/*
Основной метод, формирующий инстанс Echarts, навешивающий обработчики и применяющий темы к графикам, берет конфиг, возвращаемый _getEchartsConfig
*/
}
/*
Далее обработчики событий Echarts из __createChart
*/
protected _onChartCreated(chart: any): void {
/*
График заинициализирован и конфиг был применен
*/
}
protected _onChartClick = (event): void => {
// обрабатывает событие click и tap по графику
// https://echarts.apache.org/en/api.html#events.Mouse%20events.click
}
protected _onChartMouseOver = (params): void => {
// обрабатывает событие mouseover по графику
// https://echarts.apache.org/en/api.html#events.Mouse%20events.mouseover
}
protected _onLegendClick = (event): void => {
// обрабатывает событие legendselectchanged графика
// https://echarts.apache.org/en/api.html#events.Mouse%20events.legendselectchanged
}
protected _onDataZoom(params, vm: any): any {
// обрабатывает событие datazoom графика
// https://echarts.apache.org/en/api.html#events.Mouse%20events.datazoom
}
protected _onChartDidMount(): any {
// обрабатывает событие rendered графика
// https://echarts.apache.org/en/api.html#events.Mouse%20events.datazoom
}

protected abstract _updateChart(vm, preVM) {
/*
Если произошла смена осей или еще чего-то в конфиге, то принимает вью-модель текущую и ее версию до смены. Можно использовать для формирования нового конфига ECharts и его установки в инстансе с графиком
*/
}

Немного об объекте типа vm (вью-модель):

это объект, описываемый интерфейсом IVizelXYVM (не все поля, только значимые)


interface IVizelXYVM {
loading?: boolean; // загружена ли вью-модель
error?: string; // есть ли ошибка
schema_name: string; // имя схема атласа
vAxes: IVAxis[]; // массив осей, которые сформировались в результате разброса сущностей или работе с единицами измерения
series: ISerie[]; // список серий, хранящие массивы данных на каждой, название и другое
categories: IEntity[]; // Категории, которые обычно обозначают
noData: boolean; // есть ли вообще данные
noNumericData: boolean; // нет ли числовых данных
userSortOrder: string; // выбранное пользователем направление сортировки (ASC-DESC)
userSortBy: string; // выбранная пользователем сортировка по полю
subspace: ISubspace; // описывает сабспейс, хранящий инфу об элементах на осях, ориентации осей

// events
onToggleSort: (userSortBy: string) => void; // вызываем эту функцию для триггера сортировки по указанному полю с уже известным направлением сортировки
}

interface IVAxis {
index: number;
id: string;
unit: IUnit;
opposite: boolean;
dataMin?: number;
dataMax?: number;
}

interface ISerie {
id: string;
index: number;
e: IEntity;
title: string;
vAxisIndex: number;
stackGroup: string;
values: IValue[];
strValues: string[]; // string or numerics - formatted
numValues: number[]; // numerics or nulls
imgValues?: string[];
bgColors: string[];
isColorX?: boolean;
}

interface IEntity {
id: number | string;
title: string;
ids?: Array<string | number>;
readonly titles?: string[];
readonly axisId?: string;
readonly axisIds?: string[];
readonly formula?: string[];
readonly children?: IEntity[];
readonly parent?: IEntity;
readonly description?: string;
config?: any;
unit?: any;
color?: string;
}

Помимо базового класса BaseVizelEcharts есть еще его наследник EPlot - это класс, который подготавливает дополнительные расчеты и обработку событий. По умолчанию его используют визуализации типа “Линии” и “Области”.

И BaseVizelEcharts и EPlot могут быть заимпортированы из модуля bi-internal/ui.

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


import {EPlot} from 'bi-internal/ui';

class MyComponent extends EPlot {

protected _getEchartsConfig(vm) {
let newConfig = super._getEchartsConfig(vm);

// тут логика по правке конфига под себя, например

newConfig = {
...newConfig,
series: [
...newConfig.series,
{
type: "line",
name: "test",
data: [123, 46, 67]
}
]
};

return newConfig;
}

}

export default MyComponent;

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

Переопределяем нативные визуализации

Допустим, что вы хотите, чтобы вместо стандартного Пирог встраивался ваш пай, который вы сами написали.

Для этого вам нужно создать в ресурсах атласа ds_res компонент React, чье имя совпадает с одним из нижеперечисленных (регистр важен):

board // Доска
tabs // Вкладки
column // Столбики (вертикальные) и штабели
bar // столбики (горизонтальные) и штабели
column1d // Столбики одномерные (две остальные оси фиксированные)
line // Линии
scatter // Точки
spline // Сплайн
area // Области и штабели
bublik // Пончик
pie // Пирог
gauge // Спидометр
halfgauge // Спидометр (половинный)
radar // Радар
radar1d // Одномерный радар
thermometer // Термометр
label // Значение, Текст
funnel // Воронка
plan // Схема (Карта или план здания на svg с предварительной разметкой атрибутами)
waterfall // Водопад
koob-table-simple // тип визуализации Данные
scales // Весы
pivot // Пивот-таблица
tableP // Таблица
whatif // What-if
sankey // Санкей
treemap // Древовидная карта
grid // Сетчатая диаграмма
abc // ABC анализ
axes-selector // Селектор осей
map // Карта (контейнер)
mapdots // Точки на карте (слой на карте)
mapcharts // Графики на карте (слой на карте)
mapareas // Области на карте (слой на карте)
mapheat // тепловая карта (слой на карте)
axes-selector // Селектор осей

// Список скоро пополнится

Так вот вам достаточно создать файл pie.tsx, который есть наследник BaseVizelEcharts по умолчанию (но вообще как вы видели ранее это просто React.Component). Где прописать логику отрисовки так, как вы работаете с обычным React- компонентом, Однако если это наследник BaseVizelEcharts, то такой компонент будет ожидать от вас обязательно функции _getEchartsConfig, которая должна вернуть конфиг графика для ECharts, на основании своего аргумента vm.