Unovis во Vue: графики и аналитика — мастерская и 8 интерфейсов
Восемь аналитических интерфейсов на настоящем Unovis: выручка, план и факт, загрузка, проекты, потоки и связи. Живая мастерская, JSON/PHP, CSV и исходники.

Unovis: от списка чисел к понятному решению
В административной панели уже есть данные: оплаты, заявки, часы сотрудников и связи сервисов. Но длинная таблица не всегда отвечает на вопрос, который сейчас волнует человека. Руководителю нужно заметить падение выручки, менеджеру — увидеть перегрузку команды, разработчику — понять, какие интеграции зависят от одного API. График полезен, когда сокращает путь от этих данных к следующему действию.
Unovis помогает строить такие интерфейсы из отдельных компонентов. В этой статье используем Vue 3, реальные диаграммы библиотеки и общий учебный набор данных сервисной компании. Разберём восемь экранов: динамику, сравнение плана и факта, структуру выручки, загрузку команды, портфель проектов, потоки заявок, связи сервисов и общую аналитическую панель.
На странице можно менять фильтры, выбирать значения, разбирать пустой ответ и ошибку загрузки, скачивать CSV и JSON. Исходники содержат готовую сборку, компоненты Vue, тесты и небольшой PHP endpoint. Начать можно с браузерной мастерской, а затем перенести подход в собственную CRM, кабинет клиента или служебную панель.
Что делает библиотека и что остаётся приложению
Unovis — слой визуализации: он получает данные и настройки, рассчитывает графическое представление и обновляет его при изменениях. Библиотека не знает, что в вашей компании считается выручкой, как определяются права сотрудника и почему отсутствует запись за конкретный день. Эти решения принимает приложение до передачи данных на график.
В нашей архитектуре PHP или статический JSON отвечают за получение набора, функции JavaScript — за фильтрацию и агрегацию, Vue — за состояние интерфейса, Unovis — за рисунок. Такой порядок помогает диагностировать ошибку: неверная сумма может появиться раньше, чем библиотека вообще увидит массив. Поэтому под графиками оставлена таблица тех же значений.
У Unovis есть ядро TypeScript и адаптеры для разных UI-фреймворков. Для нашего проекта нужны два пакета: @unovis/ts и @unovis/vue. HTML5 и Bootstrap 5 формируют окружающий интерфейс. Начальная установка и устройство пакетов описаны в официальном введении.
Выбирать график по вопросу пользователя
Если нужно проследить изменение показателя во времени, начните с линии. Для сравнения нескольких отделов подойдут столбцы. Доли небольшого числа категорий можно показать кольцевой диаграммой, а распределение нагрузки по дням и интервалам — тепловой картой. Эти варианты решают разные вопросы, поэтому один универсальный график редко удобен для всей панели.
Scatter пригодится, когда у каждого объекта есть две числовые характеристики: бюджет и факт, длительность и стоимость, объём и задержка. Sankey показывает движение количества между этапами. Граф связей отвечает на вопрос, кто с кем взаимодействует; он не должен изображать денежный поток только потому, что линии выглядят эффектно.
Перед версткой сформулируйте короткое действие: «найти проект с перерасходом», «сравнить отделы», «узнать зависимые сервисы». Затем определите подписи, единицы и способ получить точное число. Если читатель после просмотра не понимает, что делать дальше, стоит изменить задачу экрана, а не добавить ещё одну анимацию.
Версии, зависимости и начало проекта
Мастерская подготовлена на Unovis 1.8.1, Vue 3.5.45 и Bootstrap 5.3.9. Сборку выполняют Vite 8.3.8 и Vue-плагин 6.0.11; версии сверены 13 ноября 2026 года. Для команд архива используйте Node.js 24 LTS не ниже 24.15. PHP 8.2+ нужен только для отдельного серверного варианта.
В исходниках сохранён package-lock.json. Команда npm ci устанавливает согласованное дерево зависимостей; обновление библиотек лучше выполнять отдельным изменением с повторной проверкой графиков. Наши компоненты импортируются из конкретных модулей пакета. Это убирает ненужную карту и её worker из сборки, хотя поддерживаемые Graph алгоритмы раскладки всё ещё могут создавать отдельные ленивые chunks.
Готовая папка public уже находится в архиве. Чтобы просто посмотреть её, достаточно локального HTTP-сервера. Чтобы менять компоненты или проверять расчёты, установите зависимости и пересоберите проект. Прямое открытие index.html через file:// для такой мастерской не подходит: модули и JSON должны загружаться по HTTP.
npm install --save-exact @unovis/ts@1.8.1 @unovis/vue@1.8.1 vue@3.5.45
# В скачанном проекте уже есть lock-файл:
npm ci --ignore-scripts
npm run build
npm test
Мини-мастерская: восемь интерфейсов с настоящими графиками
Выберите задачу в верхнем списке или перейдите кнопкой «Следующий опыт». У каждого экрана есть описание пользы и конкретная подсказка, что попробовать. Для динамики, сравнения, структуры и общей панели доступны период и отдел. У остальных экранов свои параметры: сотрудник, проект, когорта заявок или выбранный сервис.
Графики выполняются настоящим Unovis во время работы страницы. Данные загружаются fetch-запросом к соседнему JSON-файлу на этом сайте: это серверная доставка фиксированной учебной фикстуры, а не подключение к живой CRM. Переключатель «Ошибка HTTP 404» действительно запрашивает отсутствующий файл; «Пустой набор» получает корректный ответ без записей.
Под диаграммой можно выбрать значение без наведения мыши и увидеть подробность текстом. Таблица и экспорт используют тот же подготовленный набор. Внизу доступны короткий код текущего графика и полные исходники компонентов. Если встроенная область тесная, откройте мастерскую отдельно; состояние между отдельными открытиями не синхронизируется.
Контракт данных: числа, даты, пропуски и единицы
В дневной записи есть дата, отдел, фактическая сумма, план и число заявок. Денежные значения хранятся числами, а форматирование в рубли выполняется при выводе. Строка «120 000 ₽» удобна человеку, но неудобна для суммирования и построения шкалы. Дата имеет стабильный формат, а идентификатор отдела не меняется вместе с его отображаемым названием.
Значение null в нашем контракте означает отсутствие данных, а ноль — известный нулевой результат. Это различие влияет и на график, и на KPI. В учебном наборе специально оставлены оба случая, чтобы их можно было проверить. Функция validateData отвергает повтор даты и отдела, неподходящие значения и ссылки графа на неизвестные узлы.
Для рабочего API добавьте версию схемы, выбранный часовой пояс и период, к которому относятся суммы. Не смешивайте в одном столбце копейки, рубли и тысячи рублей. Если источник меняет валюту или трактовку показателя, это должно быть явным полем контракта, а не скрытой договорённостью между двумя разработчиками.
{
"date": "2026-10-12",
"department": "design",
"actual": null,
"plan": 42000,
"orders": null
}
XYContainer и SingleContainer: два способа собрать диаграмму
Для линий, областей, точек и столбцов используется XYContainer. Он связывает компоненты с данными, размерами и шкалами. Внутри можно совместить линию и точки, добавить оси, задать отступы и начальную границу по вертикали. В нашей динамике область служит дополнительным визуальным акцентом, а точное значение остаётся доступно через выбор дня.
Кольцевая диаграмма, тепловая карта, Sankey и Graph размещаются в SingleContainer. Здесь структура отображения отличается от обычных координат X/Y. Важно не переносить параметры одной модели в другую только по похожему названию: например, значение сегмента кольца и вес связи потока имеют разные роли.
Контейнеру нужна реальная доступная ширина. Если родитель скрыт или имеет нулевой размер, первое построение может оказаться пустым. В приложении стоит проверять момент открытия вкладки и смены раскладки. Устройство общего координатного контейнера разобрано в XY Container reference.
<VisXYContainer :data="daily" :height="280">
<VisLine :x="d => d.x" :y="d => d.actual" />
<VisAxis type="x" />
<VisAxis type="y" />
</VisXYContainer>
Опыт 1. Динамика выручки и выбор конкретного дня
Первый экран показывает дневную сумму за выбранный период. Сократите диапазон до семи дней и выберите один отдел: линия и KPI пересчитаются вместе. Переключите показатель на заявки, затем отключите заливку. Эти действия помогают отделить собственно данные от способа их оформления.
Список дней под графиком решает две задачи. Он позволяет получить точное число на телефоне без попадания в маленькую точку и даёт доступ с клавиатуры. Дополнительное наведение с crosshair можно добавить позже, но оно не должно становиться единственным способом прочитать результат. Для административного интерфейса надёжная числовая расшифровка важнее эффектного hover.
На рабочем экране рядом с динамикой полезно показывать дату обновления и описание метрики: поступившие оплаты, выставленные счета и признанная выручка — разные показатели. Название «Продажи» слишком расплывчато, если разные сотрудники понимают его по-разному. API линии описан в документации Line.
const daily = computed(() => dailyTotals(selectedRows(
data.value, Number(days.value), department.value
)))
const x = row => row.x
const y = row => row.actual
Не соединять пропуски с нулями незаметно для человека
В наборе дизайна за 12 октября сумма неизвестна. Если заменить её нулём, на графике получится резкий провал, которого никто не наблюдал. Если просто удалить день, линия может визуально соединить соседние точки и скрыть проблему выгрузки. В нашей подготовке дневная сумма с пропуском остаётся null, а интерфейс сообщает о неполноте данных. У Line в Unovis 1.8.1 есть важная особенность: null трактуется как нулевое значение. Поэтому наш accessor передаёт undefined для пропуска; заливка Area в таком ряду временно скрыта, чтобы не маскировать разрыв.
При этом 21 октября у поддержки стоит настоящий ноль. Его необходимо показать как нулевое значение, а не удалить вместе с отсутствующими данными. В агрегатах выводится известная сумма и количество пропусков. Такой KPI не выдаётся за полный финансовый итог, пока источник не восстановлен.
Для серьёзного отчёта согласуйте правило с владельцем данных. Иногда разрешён перенос последнего наблюдения, иногда нужна отдельная метка качества, иногда отчёт вообще нельзя формировать до восстановления. Библиотека графиков не может выбрать эту политику за бизнес. Хорошая мастерская помогает увидеть разницу, а производственный контракт закрепляет принятое решение.
// Сохраняем неизвестность, а не маскируем её нулём.
const complete = dayRecords.every(row => row.actual !== null)
const actual = complete
? dayRecords.reduce((sum, row) => sum + row.actual, 0)
: null
// API и таблица сохраняют null. Для Line 1.8.1 нужен именно undefined:
const y = row => row.actual === null ? undefined : row.actual
// Заливку Area скрываем для неполного ряда, чтобы она не маскировала разрыв.
Опыт 2. План и факт: суммы и проценты отвечают на разные вопросы
На втором экране рядом стоят фактическая сумма и план каждого отдела. Такой вид показывает денежный масштаб отклонения. Переключатель процентов отвечает на другой вопрос: насколько отдел приблизился к своему плану. Маленький отдел может перевыполнить план в процентах, но принести меньшую абсолютную сумму.
Оба режима полезны, если ясно подписаны единицы и общий смысл. В мастерской легенда остаётся рядом с графиком, цвет факта одинаков во всех категориях, план нейтральный. Расшифровка выбранного отдела показывает исходные суммы и количество отсутствующих записей. Неполная выручка не превращается незаметно в окончательную оценку работы команды.
Для проекта со множеством отделов лучше дать поиск и ограничить число одновременно показанных категорий. Тридцать узких групп столбцов на телефоне не станут понятнее от уменьшения шрифта. Варианты настройки сгруппированных столбцов находятся в GroupedBar reference.
<VisGroupedBar
:x="d => d.x"
:y="[d => d.factValue, d => d.planValue]"
:color="(d, i) => i === 0 ? '#197d78' : '#aeb9bc'"
/>
<!-- factValue: actual или actual / plan * 100 -->
Опыт 3. Структура доходов и разумное использование Donut
Третий экран делит известную выручку по трём направлениям. Можно выбрать сегмент или тот же отдел в списке и получить сумму с долей. Центральная подпись объясняет предмет диаграммы, а таблица позволяет сравнить числа без оценки углов на глаз.
Кольцо удобно для небольшого числа категорий, когда интересует состав целого. Если доли почти равны или категорий много, столбцы часто читаются проще. Не стоит создавать двадцать похожих оттенков и надеяться, что легенда решит проблему. Категория «Прочее» допустима только при понятном правиле объединения и возможности раскрыть состав.
В нашей мастерской доли рассчитаны от известной суммы. При пропущенных данных рядом остаётся предупреждение: это не полный финансовый отчёт. При пустом наборе показывается отдельное состояние, а не декоративное кольцо, которое можно принять за нормальную диаграмму. Настройки сегментов и центральных подписей описаны в документации Donut.
<VisSingleContainer :data="groups" :height="280">
<VisDonut :value="d => d.actual" :color="d => d.color"
:arcWidth="42" :padAngle="0.025"
centralLabel="Выручка" centralSubLabel="по отделам" />
</VisSingleContainer>
Опыт 4. Heatmap: загрузка команды по дням и интервалам
Тепловая карта показывает пять рабочих дней и четыре временных интервала. Выберите сотрудника, затем ячейку или соответствующий пункт списка. В каждом интервале доступно два часа; если задач назначено на три или четыре часа, появляется перегрузка. Ноль означает свободное время, а не неизвестное значение.
Шкала цвета закреплена от нуля до четырёх для всех сотрудников. Это позволяет честно сравнивать переключения. Если автоматически растягивать шкалу под каждый набор, одинаковый тёмный цвет может означать два часа у одного сотрудника и восемь у другого. В оперативной панели такая незаметная смена смысла опаснее отсутствия анимации.
Heatmap в Unovis получает плоский массив, а порядок заполнения сетки задаётся настройкой. Наши записи идут по дням, внутри дня — четыре интервала; колонка заполняется сверху вниз. Подписи и таблица помогают проверить соответствие. Параметры размещения, пустых ячеек и цветового диапазона перечислены в Heatmap reference.
<VisHeatmap :value="d => d.hours"
layout="column" :numRows="4" :numColumns="5"
:colorDomain="[0, 4]" :cellPadding="4"
:rowLabel="i => slots[i]" :columnLabel="i => weekdays[i]" />
Опыт 5. Scatter: найти проект с выходом за бюджет
На диаграмме проектов горизонтальная координата — бюджет, вертикальная — фактическая сумма. Единицы обеих осей — тысячи рублей. Рыжие точки обозначают превышение бюджета. После выбора проекта выводятся его название, обе суммы, разница и отработанные часы.
Такой интерфейс полезен в портфельной аналитике: можно быстро увидеть объекты с необычным сочетанием параметров, затем открыть подробную карточку. Однако расположение точки не объясняет причину перерасхода. Дополнительные работы по согласованию и ошибка планирования могут дать одинаковые числа, но требуют разных действий менеджера.
В мастерской точку можно нажать, а список проектов предоставляет надёжную альтернативу для клавиатуры и телефона. Размер выделенной точки меняется, но все остальные сохраняют одинаковый размер: мы не кодируем третью метрику без необходимости. Настройки координат, размеров и событий доступны в документации Scatter.
const projectColor = p => p.actual > p.budget ? '#b66b30' : '#197d78'
const selectedProject = computed(() => projects.value.find(p => p.id === selected.value))
// X — бюджет; Y — фактическая сумма; обе величины в тысячах рублей.
Опыт 6. Sankey: откуда пришли и куда ушли заявки
Sankey показывает потоки между этапами: заявка прошла квалификацию, получила предложение и завершилась оплатой либо осталась в ожидании. Толщина связи соответствует количеству заявок. Переключение сентября и октября позволяет сравнить две учебные когорты без смешивания разных периодов в одном потоке.
Главное здесь — согласованность данных. Для промежуточного этапа сумма входящих заявок должна соответствовать сумме исходящих, если ваша модель не предусматривает отдельное накопление или появление объектов. В наших данных это проверяется тестом. Нельзя складывать количество на каждом этапе и называть результат общим числом клиентов: одна заявка проходит несколько связей.
Такой экран полезен для маршрутов обработки, категорий обращений, переходов между системами и распределения ресурсов. Для простой линейной конверсии обычная таблица иногда понятнее. В маленьком экране выбирайте короткие подписи и сохраняйте числовой список переходов. Модель узлов и связей описана в Sankey reference.
const flow = {
nodes: [{ id: 'lead', label: 'Заявки' }, { id: 'qualified', label: 'Квалификация' }],
links: [{ source: 'lead', target: 'qualified', value: 120 }]
}
// В полном примере также есть отказ, предложение, оплата и ожидание.
Опыт 7. Graph: связи сервисов без хаотичной анимации
На седьмом экране показаны сайт, API, CRM, база, очередь и почта. Выберите сервис и включите только его соседей. Диаграмма сократится до интересующей области, а текст перечислит прямые связи. Это небольшая модель интеграций, которую можно использовать как отправную точку для служебной панели.
Мы выбрали круговую раскладку и отключили перетаскивание и масштабирование. Для шести узлов это даёт спокойный, предсказуемый результат и не перехватывает прокрутку на телефоне. Силовая раскладка полезна в других задачах, но постоянное движение узлов само по себе не делает интерфейс удобнее.
Связь означает наличие взаимодействия, а не установленную причину аварии. Рабочая панель может дополнить её направлением, частотой ошибок и ссылкой на трассы, но каждое такое поле должно приходить из реального источника. Отфильтрованный граф обязан сохранять обе стороны каждой показанной связи. Возможные раскладки и параметры собраны в документации Graph.
<VisGraph layoutType="circular"
:nodeLabel="d => d.label" :nodeSize="30"
:disableZoom="true" :disableDrag="true" :disableBrush="true"
:nodeFill="d => d.id === selected ? '#b66b30' : '#197d78'" />
Опыт 8. Общая панель: один фильтр, согласованные показатели
Последний экран объединяет динамику и структуру с KPI. Период и отдел изменяют общий набор строк, из которого получаются и графики, и таблица, и экспорт. Это защищает от распространённого сбоя, когда пользователь выбирает месяц, но одна карточка продолжает показывать сумму за весь год.
В Vue исходные записи остаются отдельными от вычисляемого представления. Через computed мы получаем отфильтрованные строки, затем дневные суммы и группы отделов. Не нужно независимо менять несколько массивов при каждом событии select: так проще сохранить согласованность и проверить расчёт тестом.
Для настоящего кабинета добавьте понятное состояние применения фильтра, адрес страницы с выбранным периодом и возможность восстановить его после перехода. Если фильтр затрагивает серверный запрос, решите, когда он отправляется: сразу, с задержкой или после кнопки. Основы вычисляемых значений разобраны в руководстве Vue.
const filtered = computed(() => selectedRows(data.value, days.value, department.value))
const daily = computed(() => dailyTotals(filtered.value))
const groups = computed(() => byDepartment(filtered.value))
const kpi = computed(() => summary(filtered.value))
Загрузка, пустой ответ и ошибка — три разных экрана
Успешный ответ с пустым массивом означает, что запрос выполнен, но подходящих записей нет. Ошибка HTTP означает, что данные не получены. Во время загрузки результат ещё неизвестен. Если во всех трёх случаях рисовать пустые оси, человек может сделать ложный вывод о нулевых продажах или отсутствии задач.
В мастерской эти состояния разделены. При загрузке показывается сообщение ожидания, при пустом наборе — объяснение и возврат учебных данных, при ошибке — HTTP-код и понятная кнопка восстановления. Старые показатели не остаются на экране как будто они относятся к новому успешно загруженному набору.
В рабочем продукте иногда полезно сохранять предыдущий результат во время обновления. Тогда его нужно явно пометить как устаревший, а новый период не подставлять в заголовок до получения соответствующих данных. Удобство здесь зависит от того, насколько критично пользователю различать свежие и прошлые значения.
const response = await fetch(url, { signal: controller.signal })
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const next = validateData(await response.json())
status.value = next.daily.length ? 'ready' : 'empty'
data.value = next
Отмена запросов и защита от устаревшего ответа
Представьте, что пользователь быстро переключил два отдела. Первый запрос задержался, второй завершился раньше. Если просто присваивать результат любого завершившегося запроса, старые данные могут заменить новые. На панели при этом останется последнее выбранное название отдела, что особенно неприятно для отчётных сумм.
Мы отменяем предыдущий fetch через AbortController и дополнительно сравниваем номер запроса. Отмена экономит ненужную работу на клиенте, а проверка номера защищает само присваивание результата. При размонтировании компонента активный запрос тоже отменяется. Ошибка AbortError не показывается как авария сервиса: это ожидаемое следствие нового действия.
Для более сложного приложения с кешем, фоновым обновлением и зависимыми запросами можно использовать отдельный слой работы с данными. На сайте есть TanStack Query во Vue. Unovis при этом остаётся визуализацией и получает уже выбранный результат.
const id = ++requestId
controller?.abort()
controller = new AbortController()
const next = await loadDataset(controller.signal)
if (id !== requestId) return
data.value = next
Доступность: точное значение не должно зависеть от hover
График может быть красивым и при этом неудобным для человека, который использует клавиатуру, экранный диктор или телефон. В нашей мастерской рядом с интерактивными точками есть обычные select-поля, а ниже — таблица. При смене выбранного объекта текстовая подробность обновляется в aria-live области.
Цвет не используется как единственная форма объяснения. Перерасход проекта дополнительно выражен числами, пропуск — текстом, выбранный сервис — названием и списком соседей. У кнопок есть видимый фокус, а подписи связаны с полями через label. Эти небольшие решения полезны всем, даже если специальными средствами доступа пользуется меньшинство аудитории.
Автоматический анализ разметки не заменяет ручной проход. Попробуйте решить задачу без мыши: сменить период, выбрать проект, получить число и скачать таблицу. Если для одного из шагов требуется попасть в тонкий SVG-элемент, добавьте обычный элемент управления. Удобный график может иметь несколько равноправных способов чтения.
Мобильный экран: менять компоновку, а не уменьшать всё подряд
На узком экране фильтры и кнопки переходят в одну колонку, KPI перестраиваются, а два графика общей панели располагаются друг под другом. Высота диаграммы остаётся достаточной для подписи и выбора. Мы не уменьшаем весь интерфейс до миниатюры, в которую невозможно попасть пальцем.
Широкая таблица прокручивается внутри своей области и не растягивает всю страницу. Для данных с множеством столбцов это честнее, чем сжимать каждое слово до нечитаемого вида. Графики при этом должны помещаться в доступную ширину; внешняя горизонтальная прокрутка всего документа указывает на ошибку компоновки.
Проверяйте реальные короткие и длинные подписи, ошибки и пустой результат. Один удачный первый экран не доказывает адаптивность всех состояний. На телефоне особенно заметны перехват прокрутки графом, слишком плотная легенда и кнопки, которые становятся недоступными после появления сообщения об ошибке.
.filter-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.table-wrap { max-width: 100%; overflow: auto; }
@media (max-width: 650px) {
.filter-grid, .dashboard-charts { grid-template-columns: 1fr; }
}
Анимации: помогать сравнивать, учитывать reduced motion
Плавный переход полезен, когда помогает проследить изменение тех же объектов после фильтра. Но длительная анимация каждого элемента может задерживать чтение, а постоянное движение мешает сосредоточиться. Поэтому в мастерской есть ручной переключатель и поддержка системного prefers-reduced-motion.
Если уменьшение движения включено в системе, длительность обновления графиков становится нулевой независимо от положения ручного переключателя. Это понятное правило: пользователь уже сообщил предпочтение на уровне устройства. Окружающие CSS-переходы тоже отключаются соответствующим media query.
В продукте стоит отдельно оценить анимацию загрузки, перестановку категорий и изменение масштаба. Иногда спокойное обновление числа и короткая отметка времени полезнее сложного морфинга диаграммы. Любой переход должен сохранять смысл: один цвет продолжает обозначать ту же категорию, а подпись единиц не исчезает во время изменения.
const duration = computed(() =>
animation.value && !reducedMotion.value ? 350 : 0
)
// Передаём duration компонентам Unovis и учитываем media query в CSS.
CSV и JSON: выгружать именно то, что сейчас показано
Кнопки выгрузки сохраняют текущую таблицу, включая применённые фильтры. JSON дополнительно содержит имя сценария, период и отдел, чтобы набор было проще понять вне страницы. Пользователь не получает неожиданно полный массив за все периоды, когда на экране выбрана последняя неделя.
CSV требует отдельной обработки: кавычки внутри ячейки удваиваются, разделители и переносы остаются внутри заключённого в кавычки значения. Потенциальные формулы в текстовых значениях нейтрализуются. Пропуск сохраняется пустым, а не превращается в ноль. Для русскоязычного табличного редактора использованы UTF-8 BOM и разделитель точка с запятой.
Рабочий экспорт должен соблюдать те же права, что и интерфейс. Если в скрытом массиве находятся чужие клиенты, одного визуального фильтра недостаточно. Большие выгрузки разумнее готовить на сервере с отдельным контролем доступа и лимитом, а в браузере показывать прогресс готовности файла.
const payload = {
scenario: scenario.value,
days: Number(days.value),
department: department.value,
rows: currentTable.value
}
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' })
PHP endpoint: тот же интерфейс с настоящим серверным обработчиком
В архиве находится server/data.php. Он принимает только GET, проверяет значение dataset по небольшому разрешённому списку и отдаёт соответствующий JSON. Пользовательский параметр не используется как путь к файлу. Учебная ошибка возвращает 404, неизвестный вариант — 400, неподдерживаемый метод — 405.
После запуска PHP-сервера откройте страницу с параметром source=php. Интерфейс начнёт получать данные через /api/analytics, сохранив те же графики и состояния. Это позволяет увидеть, как визуализация отделяется от способа получения данных: замена источника не требует переписывать восемь компонентов.
Для подключения базы замените чтение фикстуры на подготовленный запрос и верните тот же согласованный контракт. Добавьте авторизацию и проверку доступных подразделений. Данные финансового отчёта нельзя защищать только тем, что в select отсутствует чужой отдел. Базовые примеры PHP собраны в полном мануале PHP на сайте.
$dataset = $_GET['dataset'] ?? 'normal';
if (!is_string($dataset) || !in_array($dataset, ['normal','empty','error'], true)) {
http_response_code(400);
echo json_encode(['error' => 'invalid_dataset']);
exit;
}
// Путь выбирается сервером из фиксированного набора, не из введённой строки.
Windows: запуск готовой мастерской и PHP-режима
Распакуйте архив в отдельную папку и откройте PowerShell в её корне. Проверьте установленную версию Node.js, выполните npm ci и сборку. В PowerShell удобно писать npm.cmd, чтобы команда не зависела от политики выполнения npm.ps1. Статический сервер слушает только 127.0.0.1 и не публикует ваш проект в сети.
Обычный режим открывается по адресу http://127.0.0.1:8510/. Если нужен только просмотр уже готового public, можно запустить node tools/serve.mjs без установки зависимостей: сервер использует стандартные модули Node. Для изменения компонентов установка и новая сборка всё равно понадобятся.
PHP-вариант запускается отдельной командой на порту 8511. Проверьте php –version и откройте /?source=php на этом порту. Если порт занят, выберите свободный для собственного стенда, не останавливая неизвестный процесс. После работы завершите только свои серверы через Ctrl+C.
node --version
npm.cmd ci --ignore-scripts
npm.cmd run build
npm.cmd test
npm.cmd start
# В отдельном терминале, если нужен PHP:
php -S 127.0.0.1:8511 -t public server/router.php
# http://127.0.0.1:8511/?source=php
Linux: та же сборка, проверки и удалённый учебный просмотр
На Linux используются те же package.json, lock-файл и команды проекта. Установите поддерживаемый Node.js 24 LTS из доверенного источника для вашего дистрибутива. Для PHP-варианта нужен PHP 8.2 или новее; тест сам поднимает дочерний сервер на свободном loopback-порту и завершает его после проверки.
Если вы изучаете мастерскую на удалённой машине, необязательно открывать учебный порт наружу. Оставьте привязку к 127.0.0.1 и создайте SSH-туннель. Тогда браузер на вашем компьютере увидит локальный адрес, а сервер не превратится в публичный экспериментальный endpoint.
Команды разработки и команды размещения стоит разделять. Для статической сборки на VPS не нужен постоянно работающий npm start, если файлы уже отдаёт Nginx или Caddy. Node остаётся инструментом сборки, а веб-сервер обслуживает public. Это уменьшает число процессов, которые приходится сопровождать на рабочем сервере.
node --version
npm ci --ignore-scripts
npm run build
npm test
npm run test:php
npm start
# На своём компьютере для удалённого учебного стенда:
ssh -L 8510:127.0.0.1:8510 user@your-server
Linux/VPS: разместить public и подключить PHP-FPM
Для обычной мастерской достаточно разместить public под отдельным доменом или префиксом существующего сайта. Относительный base в Vite сохраняет работоспособность ссылок на assets и JSON в подкаталоге. Переносите вместе всю сборку: одного index.html недостаточно. Настройку домена и HTTPS выполняйте в принятом процессе вашего сервера.
В README есть пример Nginx с корнем /srv/unovis/public. Для PHP-варианта добавляется точный location /api/analytics, который передаёт запрос конкретному server/data.php через PHP-FPM. Сам каталог server остаётся вне публичного корня. Имя сокета зависит от установленной версии PHP и должно соответствовать реальной службе.
Перед reload выполните nginx -t. Затем проверьте реальный URL браузером и curl: статус, Content-Type, структуру JSON, загрузку assets и поведение ошибки. Локальный успешный тест не подтверждает DNS, сертификат, FPM и firewall чужого VPS. Эти части проверяются именно после размещения в целевой среде.
location = /api/analytics {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /srv/unovis/server/data.php;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location / { try_files $uri $uri/ =404; }
Кеширование и обновление без смешивания версий
Хешированные JavaScript и CSS можно кешировать надолго, потому что новое содержимое получает новое имя файла. HTML должен обновляться быстрее: именно он указывает на актуальный набор assets. JSON с оперативными данными требует собственной политики, зависящей от частоты обновления и допустимой задержки.
Не удаляйте предыдущие assets мгновенно после выкладки. У пользователя может оставаться открытой старая страница, которая позже запросит ленивый модуль графика. Если файл уже исчез, переключение сценария закончится ошибкой, хотя новая главная страница открывается нормально. Сохраняйте несколько последних сборок или используйте каталог версии.
Рабочий API может использовать ETag, ограниченный кеш и дату актуальности в ответе. Но прежде определите, какие данные персональны и зависят от прав. Общий публичный кеш для ответа одного сотрудника способен превратить оптимизацию в утечку. Статические учебные данные нашей мастерской таких сведений не содержат.
location /assets/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=31536000, immutable";
}
location /data/ {
try_files $uri =404;
add_header Cache-Control "no-cache";
}
Производительность: контролировать объём данных и состав сборки
Для восьми примеров достаточно небольшого фиксированного набора: 84 дневные записи, 60 интервалов нагрузки, 12 проектов и шесть сервисов. Это позволяет изучать взаимодействие без ожидания тяжёлой агрегации. Такой объём не является обещанием, что произвольный миллион точек будет столь же удобен на телефоне.
При росте данных сначала определите нужную детализацию. Для годового обзора часто достаточно дневных или недельных агрегатов, а отдельные события загружаются по выбранному диапазону. Не передавайте в браузер все записи базы только ради того, чтобы затем выбросить большую часть локальным фильтром.
В сборке проекта используется отдельная загрузка компонента графиков и прямые imports модулей Unovis. Graph поддерживает несколько раскладок, поэтому Vite создаёт дополнительные ленивые файлы; большая запись в отчёте сборщика ещё не означает, что этот файл загружается при первом открытии. Проверяйте фактические запросы Network и время взаимодействия, а не только общий размер папки.
Как проверены исходники и что ещё проверять в своём проекте
Тесты модели проверяют суммы, фильтры, различие null и нуля, согласованность Sankey, корректность подграфа и экспорт CSV. Отдельный тест загружает готовую сборку Vue и настоящие компоненты Unovis в JSDOM, проходит восемь сценариев, меняет фильтры и проверяет восстановление после пустого ответа и HTTP-ошибки.
JSDOM не рисует страницу как полноценный браузер: геометрия SVG, текста и canvas в тесте подставляется. Поэтому такие проверки подтверждают логику, создание компонентов и связи состояний, но не качество подписи на телефоне. Визуальная приёмка и реальные клики выполняются отдельно. Настоящий PHP endpoint проверяется HTTP-запросами к отдельному процессу.
Проект собирался и проверялся на Windows и в изолированной Linux-среде с чистой установкой зависимостей. При переносе в собственную систему дополните набор проверками ваших прав доступа, часового пояса, больших значений и длинных названий. После обновления библиотеки повторите именно пользовательские задачи, а не только успешную команду сборки.
npm run build
npm test
npm run test:php
php -l server/data.php
php -l server/router.php
Когда выбрать Unovis, а когда начать с другого решения
Unovis удобно рассматривать, когда нужны разные семейства визуализации в одном интерфейсе: координатные графики, потоки, сети и тепловые карты. Компонентный подход хорошо сочетается с Vue-состоянием и собственным оформлением. Но сам по себе широкий каталог компонентов не делает библиотеку лучшим выбором для любого проекта.
Если в приложении уже работает другая библиотека и ей хватает возможностей, миграция ради нового названия может не окупиться. Для одного простого графика важны привычность команды, доступность нужного вида и понятная интеграция. На сайте можно сравнить практические примеры Chart.js и Apache ECharts.
Сравнивайте на одной задаче и одних данных: мобильная подпись, выбор значения, ошибка загрузки, экспорт, размер реально загруженного кода. Так решение опирается на ваш продукт, а не на количество картинок в галерее. Для точных табличных операций иногда правильнее улучшить таблицу и добавить только один небольшой график.
Исходники, лицензии и следующий практический шаг
Скачайте архив и выберите один экран, который похож на задачу вашего сервиса. Замените учебный JSON небольшим набором собственных обезличенных данных, сохранив контракт. Сначала убедитесь, что таблица и KPI верны, затем настройте подписи, события и внешний вид. Только после этого подключайте реальный защищённый API.
Наш код распространяется по MIT, Unovis — по Apache-2.0; уведомления зависимостей собраны в THIRD-PARTY-LICENSES.txt. В архиве есть README с командами Windows/Linux и примером VPS, компонентная сборка Vue, PHP endpoint и тесты. Закреплённые версии помогают повторить материал, а обновление требует отдельной проверки.
Для продолжения используйте официальное руководство Unovis, репозиторий и лицензии и документацию компонентов рядом с каждым опытом. Практический результат этой статьи — не только набор красивых графиков, а понятный путь от контракта данных до экрана, на котором человек способен найти число, увидеть проблему и выполнить следующий шаг.


