Если вы работаете с BI‑системами, наверняка сталкивались с ситуацией, когда стандартных визуализаций не хватает. Хочется добавить свой график, который идеально подходит под задачи бизнеса.

В Modus BI такая возможность встроена в саму платформу — вы можете создавать свои плагины визуализаций. В этой статье мы шаг за шагом разберем, как собрать с нуля ��ростой, но гибко настраиваемый прогресс‑бар. Руководство будет полезным для разработчиков, которые хотят самостоятельно создавать уникальные визуализации на базе Modus BI.

Что понадобится:

Шаг 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. Этот пример можно использовать как основу для создания других типов визуализаций.