Если вы работаете с BI‑системами, наверняка сталкивались с ситуацией, когда стандартных визуализаций не хватает. Хочется добавить свой график, который идеально подходит под задачи бизнеса.
В Modus BI такая возможность встроена в саму платформу — вы можете создавать свои плагины визуализаций. В этой статье мы шаг за шагом разберем, как собрать с нуля простой, но гибко настраиваемый прогресс‑бар. Руководство будет полезным для разработчиков, которые хотят самостоятельно создавать уникальные визуализации на базе Modus BI.
Что понадобится:
Node.js (версия старше 14.21.3);
шаблон плагина из GitHub‑репозитория Modus BI;
доступ к порталу Modus BI;
редактор кода (VS Code, WebStorm и другие);
Шаг 1. Запускаем проект
Начните с шаблона плагина. Скачайте ZIP‑архив из ветки main репозитория Modus BI, распакуйте его и откройте папку custom-chart в редакторе кода.
Установите зависимости, используя npm:
npm install
Скачайте ядро проекта, следуя официальной инструкции. В корне создайте папку prebuild и распакуйте туда содержимое ядра.
Результат должен выглядеть так:

Запустите проект командой:
npm run start:dev
По умолчанию приложение откроется на http://localhost:7000.
При необходимости измените порт в файле ./config/index.js (свойство server_port).

Теперь в Modus BI откройте новый или существующий отчет и включите режим редактирования.

В списке визуализаций появится плитка DEV — это ваш будущий плагин. Перетащите ее в область «Компонент отображения не задан».
После этого появится пустой шаблон — это значит, плагин подключен и готов к разработке.

Шаг 2. Настраиваем редактор компонента
Настройки редактора компонента подключаются в файле src\modules\CustomSettings\Settings.js и автоматически отображаются в боковой панели редактора. Modus BI предоставляет готовые стандартные секции для базовых настроек.
Этот набор можно расширять набор собственными элементами управления. Например, добавим настройку для выбора цвета фона.
Сначала добавьте свойство для цвета фона в defaultConfig.json:
{
"bgColor": null,
// ... остальные настройки
}
Для удобного доступа к настройкам добавьте файл getDefaultConfig.js:
import defaultConfig from './defaultConfig.json';
export function getDefaultConfig() {
return defaultConfig;
}
/
Теперь создайте компонент выбора цвета.
В папке src/modules/CustomSettings/CommonSettingItems/ добавьте файл ColorSettingItem.jsx.
const ColorSettingItem = ({ pluginImports, title, onChange, color }) => {
const { SettingsItem, SettingsColorPicker } = pluginImports.components;
return (
<SettingsItem type='inline' title={title}>
<SettingsColorPicker color={color} onChange={onChange} />
</SettingsItem>
);
};
Здесь мы используем встроенные компоненты API Modus BI (SettingsItem, SettingsColorPicker). Они реализуют стандартную логику отображения, а мы добавляем дополнительную настройку — выбор цвета фона.
Добавьте обработчик изменения цвета в файле src\modules\CustomReducers\changeCustomChartReducer.js. Этот файл нужен для того, чтобы сохранять и применять настройки диаграммы, относящиеся к плагину. Через API платформы разработчик реализует свою логику обработки команд (например, изменение цвета фона).
if (action.command === 'changeStyleBgColor') {
configDraft.bgColor = action.settings.value;
}
Теперь в редакторе отчета появится опция «Цвет фона». Выбор цвета будет сразу применяться к плагину.

Шаг 3. Подготовка структуры осей диаграммы (это правильнее по сути и меньше путаницы с шагом 4)
Прежде чем визуализация сможет работать с данными, нужно настроить для них структуру осей. В Modus BI структура осей (axes) настраивается через полки — области, куда пользователь перетаскивает поля из датасета. Для прогресс‑бара понадобятся:
категории (например, проекты или регионы);
значения (числовые показатели);
фильтры.
Пример структуры в defaultConfig.json:
{
"axes": [
{ "name": "Значения", "title": "Значения", "type": "values", "fields": [] },
{ "name": "Категории", "title": "Категории", "type": "categories", "fields": [] },
{ "name": "filters", "title": "Фильтры", "type": "filters", "fields": [] }
]
}
В файле CustomAxes/index.js настройте сортировку:
export function sortAxes(props) {
const { config } = props;
return config.axes;
}
Кроме сортировки важно определить, как будут выглядеть и работать «пилюли» — визуальные элементы, которые отображают поля, добавленные на полки.
Для этого в CustomAxes/index.js добавьте функции, которые управляют отображением и активностью элементов. Добавляя каждую функцию, мы заменяем ее дефолтную версию на свою кастомную и так кастомизируем диаграмму в целом.
// Настройка видимости пунктов меню
export function isVisibleAxeDragItemMenuOption(props) {
const { optionName, field } = props;
if (field.type === 'values') {
return ['renderTitleInput', 'renderSortMenuItem', 'renderAggregationMenuItem'].includes(optionName);
}
if (field.type === 'categories') {
return ['renderTitleInput', 'renderSortMenuItem'].includes(optionName);
}
if (field.type === 'filters') {
return ['renderTitleInput', 'renderFilterSettings'].includes(optionName);
}
return false;
}
// Настройка визуальных элементов пилюли
export function isVisibleAxeDragItemElement(props) {
const { elementName, field } = props;
if (field.type === 'values') {
return ['renderAggregationSelector', 'renderSortSelector'].includes(elementName);
}
if (field.type === 'categories') {
return ['renderSortSelector'].includes(elementName);
}
return false;
}
Эти функции задают правила для разных типов полей:
для «Значений» отображается селектор агрегации и сортировки;
для «Категорий» — только сортировка;
для «Фильтров» — специфические настройки фильтрации.
В продвинутых случаях можно написать собственный ConfigEditor. Этот модуль отвечает за добавление, обновление и обработку полей, а также за автоматическую агрегацию. В большинстве случаев достаточно дефолтной реализации ConfigEditor.
Полный пример реализации доступен в репозитории Modus BI ConfigEditor.js.
Шаг 4. Работа с данными
Чтобы визуализация могла получать значения из портала, создаем адаптер на основе CommonDataAdaptor. Он нужен для связи сырых данных с компонентом графика.
import CommonDataAdaptor from '../../duplicates/adaptors/CommonChartAdaptor/CommonDataAdaptor';
export default class DataAdaptor extends CommonDataAdaptor {}
Основной метод в CommonDataAdaptor — remapData. Он подготавливает данные из портала и приводит их к структурированному формату, удобному для отображения.
Шаг 5. Создаем визуализацию
Чтобы плагин работал в системе, подключаем компонент в CustomChart.js — это точка входа для всех кастомных визуализаций.
Визуализация реализуется в компоненте ProgressBarChart.jsx. Он использует:
хук useStyles управляет цветами и отступами контейнера;
SCSS‑стили — задают структуру и оформление элементов;
Header.jsx — отдельный компонент для заголовка;
getDescription и getLocal — функции для описания и локализации;
LoadProgress — встроенный компонент для отображения загрузки данных.
Финальный вариант ProgressBarChart.jsx:
const ProgressBarChart = React.forwardRef(({ config, theme, componentId, pluginImports }, ref) => {
const { LoadProgress } = pluginImports.components;
const styles = useStyles(config, theme);
const title = getLocal(config, 'title');
const showHeader = config?.showtitle || false;
return (
<div className='hsChartContainer ProgressBarChart' style={styles.container} ref={ref}>
{showHeader && <Header title={title} />}
<div className='componentBody ProgressBarChartBody'>
<div className='componentContainer ProgressBarChartComponent' style={styles.component}>
<div>
<LinearProgress value={40}>
ProgressBarChart
</LinearProgress>
</div>
</div>
<LoadProgress />
</div>
</div>
);
});

Полный код компонентов и стилей смотрите в репозитории Modus BI.
Шаг 6. Подключаем реальные данные
На предыдущем шаге мы добавили визуализацию — прогресс‑бар. Но пока он статичен. Чтобы связать его с реальными данными, используем адаптер.
Адаптер (remapData) — это функция, которая преобразует данные из системы Modus BI в формат, удобный для визуализации.Когда пользователь добавляет поля на полки (axes), данные автоматически проходят через метод remapData.В простейшем случае он возвращает числовое значение, которое мы передаем в компонент прогресс‑бара
Пример работы с данными:
import DataAdaptor from '../dataAdaptor';
export function useLoadData({ config, loadDatas, cacheId, data, editorActive, componentId, reloadDatas, inEditor }) {
const [loaded, setLoaded] = useState(false);
const [loading, setLoading] = useState(false);
const [dataAdaptor, setDataAdaptor] = useState(null);
const datasetId = getDatasetId(config);
const queryObjects = DataAdaptor.getQueryObjects(config);
const stringQueryObjects = JSON.stringify(queryObjects);
useEffect(() => {
if (!datasetId) return;
runLoadData();
}, [datasetId, cacheId, stringQueryObjects, data, editorActive, inEditor]);
const runLoadData = () => {
if (!datasetId) return;
if (!data && cacheId) {
setLoading(true);
loadDatas(datasetId, null, config.filters, queryObjects, { editor: editorActive, componentId })
.then((res) => {
setLoaded(true);
const adapter = new DataAdaptor(res.data, config, null, cacheId);
setDataAdaptor(adapter);
})
.finally(() => {
setLoading(false);
});
} else if (data && !data.fetching) {
setDataAdaptor(new DataAdaptor(data.data, config, null, cacheId));
}
};
return { dataAdaptor, loadedData: loaded, loadingData: loading, };
}
Теперь подключим хук в ProgressBarChart и передадим полученное значение в LinearProgress:
import { useLoadData } from './useLoadData';
const ProgressBarChart = React.forwardRef(({ config, datas, theme, pluginImports }, ref) => {
const { LoadProgress } = pluginImports.components;
const styles = useStyles(config, theme);
const { dataAdaptor, loadingData } = useLoadData({ config, cacheId, loadDatas, reloadDatas, data, componentId, editorActive, inEditor});
let list = [];
const valuesAxe = _.find(config.axes, ['type', 'values']);
if (valuesAxe?.fields.length > 1) {
list = dataAdaptor?.plotData.map(data => ({
id: data['ID0'],
category: data.categories || 'Без категории',
value: getPercentage(data['values0'], data['values1'])
})) || [];
}
return (
<div className='hsChartContainer ProgressBarChart' style={styles.container} ref={ref}>
<div className='componentBody ProgressBarChartBody'>
<div className='componentContainer ProgressBarChartComponent' style={styles.component}>
<div className='ProgressBarChartList'>
{list.length === 0 ? <b>Нет данных</b> : null}
{list.map((item) => (
<LinearProgress key={item.id} value={item.value} containerColor={'white'} linearColor={'red'}>
{item.category}
</LinearProgress>
))}
</div>
</div>
<LoadProgress />
</div>
</div>
);
});
В итоге, получили плагин, который работает с данными.
Шаг 7. Проверка результата
После всех шагов убедитесь, что:
цвет фона меняется через настройки;
заголовок отображается;
отступы контейнера применяются;
данные подтягиваются и отображаются как прогресс‑бар;
оси и фильтры работают корректно.
Если все это выполняется — плагин готов к использованию.
Заключение
Вы создали плагин прогресс-бара для Modus BI, который:
1. Настраивается через редактор (цвет фона, заголовки)
2. Работает с данными через стандартные механизмы платформы
3. Отображает данные в виде прогресс-баров
4. Подключается к реальным данным из портала
Готовый код доступен в ветке tutorial-progress репозитория Modus BI. Этот пример можно использовать как основу для создания других типов визуализаций.