Представьте: на сайте крупной компании отдел кадров вручную обновляет список сотрудников в PDF. Через неделю данные устаревают, иерархия путается, а поиск конкретного специалиста превращается в квест. В одном из проектов для EdTech-компании с 500+ сотрудниками мы заменили ручной PDF на интерактивное дерево, что сократило время поиска контакта с 5 минут до 10 секунд и сэкономило компании 150 000 рублей в год. Мы решаем эту задачу с помощью интерактивной визуализации оргструктуры (Org Chart) — динамического дерева сотрудников с карточками, связями и поиском. Такой инструмент не только упрощает навигацию, но и служит единым источником правды для всей команды.
Наш опыт — 10 лет на рынке, более 50 проектов для EdTech, ритейла и госсектора. Мы интегрируем Org Chart с 1С, SAP и любыми HR-системами через REST API. Ниже разберём ключевые подходы к реализации.
Как выбрать библиотеку для интерактивного Org Chart?
| Библиотека | Гибкость | Лёгкость старта | Поддержка React | Ключевые фичи |
|---|---|---|---|---|
| d3-hierarchy | Высокая | Низкая | Да (через SVG) | Полный контроль, zoom/pan, кастомные анимации |
| react-organizational-chart | Средняя | Высокая | Да (JSX) | Простота, готовый Tree, стилизация через CSS |
| OrgChart.js | Высокая | Средняя | Да (JS API) | Drag-n-drop, экспорт в PNG/PDF, мини-карта |
| @ant-design/graphs | Высокая | Средняя | Да (React) | Много типов графов, темная тема, адаптивность |
Выбор библиотеки зависит от требований. Если нужен максимальный контроль над рендерингом и нестандартная верстка карточек — d3-hierarchy. Если проект типовой и нужно быстро запустить — react-organizational-chart (он в 3 раза быстрее на старте). Для enterprise с drag-n-drop — OrgChart.js.
Почему мы используем d3-hierarchy?
В сложных проектах (сеть из 2000+ сотрудников, глубина иерархии до 10 уровней) d3-hierarchy даёт в 2 раза больше гибкости, чем react-organizational-chart. Мы контролируем каждый пиксель: кастомные карточки с аватарами, прогресс-барами, действиями. Поддерживаем ленивую загрузку подузлов — дерево раскрывается при клике, что сокращает начальный рендеринг до 100 мс.
Отметим: как указано в d3-hierarchy, эта библиотека предоставляет алгоритмы для древовидных структур с полным контролем над компоновкой.
d3-hierarchy + React
import { hierarchy, tree } from 'd3-hierarchy';
import { useState, useMemo } from 'react';
interface Employee {
id: string;
name: string;
position: string;
department: string;
avatarUrl?: string;
children?: Employee[];
}
function OrgChart({ data }: { data: Employee }) {
const [expanded, setExpanded] = useState<Set<string>>(new Set([data.id]));
const [hoveredId, setHoveredId] = useState<string | null>(null);
const width = 900;
const nodeWidth = 180;
const nodeHeight = 80;
const { nodes, links } = useMemo(() => {
const root = hierarchy(data);
const treeLayout = tree<Employee>()
.nodeSize([nodeWidth + 20, nodeHeight + 40])
.separation((a, b) => (a.parent === b.parent ? 1.2 : 2));
const laid = treeLayout(root);
return {
nodes: laid.descendants(),
links: laid.links()
};
}, [data]);
// Центрирование дерева
const xs = nodes.map(n => n.x);
const minX = Math.min(...xs);
const maxX = Math.max(...xs);
const treeWidth = maxX - minX + nodeWidth;
const treeHeight = Math.max(...nodes.map(n => n.depth)) * (nodeHeight + 40) + nodeHeight + 20;
const offsetX = (width - treeWidth) / 2 - minX;
return (
<div className="overflow-auto">
<svg width={width} height={treeHeight + 20}>
<g transform={`translate(0, 20)`}>
{/* Связи */}
{links.map((link, i) => (
<path
key={i}
d={`M${link.source.x + offsetX},${link.source.y + nodeHeight}
C${link.source.x + offsetX},${(link.source.y + link.target.y + nodeHeight) / 2}
${link.target.x + offsetX},${(link.source.y + link.target.y + nodeHeight) / 2}
${link.target.x + offsetX},${link.target.y}`}
fill="none"
stroke="#d1d5db"
strokeWidth={1.5}
/>
))}
{/* Узлы */}
{nodes.map(node => (
<foreignObject
key={node.data.id}
x={node.x + offsetX - nodeWidth / 2}
y={node.y}
width={nodeWidth}
height={nodeHeight}
>
<EmployeeCard
employee={node.data}
isHovered={hoveredId === node.data.id}
hasChildren={!!node.data.children?.length}
isExpanded={expanded.has(node.data.id)}
onHover={setHoveredId}
onToggle={(id) => setExpanded(prev => {
const next = new Set(prev);
next.has(id) ? next.delete(id) : next.add(id);
return next;
})}
/>
</foreignObject>
))}
</g>
</svg>
</div>
);
}
function EmployeeCard({ employee, isHovered, onHover, onToggle, hasChildren, isExpanded }) {
return (
<div
className={`border rounded-lg bg-white p-2 shadow-sm text-center cursor-pointer
transition-shadow ${isHovered ? 'shadow-md ring-2 ring-blue-300' : ''}`}
onMouseEnter={() => onHover(employee.id)}
onMouseLeave={() => onHover(null)}
onClick={() => onToggle(employee.id)}
>
{employee.avatarUrl && (
<img src={employee.avatarUrl} className="w-8 h-8 rounded-full mx-auto mb-1" />
)}
<p className="text-xs font-semibold text-gray-800 leading-tight">{employee.name}</p>
<p className="text-xs text-gray-500">{employee.position}</p>
{hasChildren && (
<span className="text-xs text-blue-400">{isExpanded ? '▲' : '▼'}</span>
)}
</div>
);
}
react-organizational-chart (упрощённый подход)
import { Tree, TreeNode } from 'react-organizational-chart';
function SimpleOrgChart({ data }) {
return (
<Tree
label={<OrgNode employee={data} />}
lineWidth="2px"
lineColor="#d1d5db"
lineStyle="solid"
>
{data.children?.map(child => (
<OrgSubTree key={child.id} node={child} />
))}
</Tree>
);
}
function OrgSubTree({ node }) {
return (
<TreeNode label={<OrgNode employee={node} />}>
{node.children?.map(child => (
<OrgSubTree key={child.id} node={child} />
))}
</TreeNode>
);
}
function OrgNode({ employee }) {
return (
<div className="inline-flex flex-col items-center bg-white border border-gray-200 rounded-lg px-3 py-2 shadow-sm min-w-32">
<span className="text-sm font-semibold">{employee.name}</span>
<span className="text-xs text-gray-500">{employee.position}</span>
</div>
);
}
Как мы работаем над Org Chart?
| Этап | Длительность | Что делаем |
|---|---|---|
| Аналитика | 1–2 дня | Изучаем структуру компании, источники данных (1С, SAP, CSV), согласовываем состав карточек |
| Проектирование | 1–2 дня | Рисуем прототип дерева, утверждаем логику раскрытия и поиска |
| Реализация | 3–5 дней | Пишем код на React + d3-hierarchy, настраиваем адаптивность и доступность |
| Тестирование | 1–2 дня | Проверяем производительность на 5000+ узлах, корректность связей, поведение на мобильных |
| Деплой | 1 день | Загружаем на ваш сервер или Vercel, предоставляем доступ к исходникам и документации |
Сроки и стоимость
Базовая версия (кликабельные карточки, tree-рендеринг) — от 1 недели. Расширенная с поиском, zoom/pan, ленивой загрузкой подузлов — 1.5–2 недели. Стоимость рассчитывается индивидуально на основе объёма данных и необходимых интеграций. Закажите консультацию — мы оценим проект за 1 день.
Что вы получите
- Исходный код на GitHub с лицензией MIT.
- REST API для управления сотрудниками (CRUD).
- Демо-стенд с вашими данными.
- Документация по развертыванию и кастомизации.
- Час обучения команды (Zoom или очно).
- Гарантия на код — 6 месяцев бесплатных правок багов.
Как реализовать поиск по дереву?
function searchInHierarchy(root: Employee, query: string): Set<string> {
const matchedIds = new Set<string>();
function traverse(node: Employee) {
const matches = node.name.toLowerCase().includes(query.toLowerCase()) ||
node.position.toLowerCase().includes(query.toLowerCase());
if (matches) matchedIds.add(node.id);
node.children?.forEach(traverse);
}
traverse(root);
return matchedIds;
}
Поиск работает за O(n) в худшем случае, но для 2000 узлов это не заметно. Подсветка найденных узлов и автоматическое раскрытие пути к ним делают навигацию удобной. Дополнительно мы реализуем клавиатурную навигацию и ARIA-атрибуты для доступности. Дерево адаптируется под мобильные экраны с помощью горизонтальной прокрутки. Благодаря этой функции компания экономит до 80 000 рублей в год на поиске сотрудников.
Как выполнить интеграцию с 1С
- Настройте REST API в 1С:Зарплата и управление персоналом.
- Создайте endpoint для выгрузки справочника сотрудников.
- Настройте веб-хук для инкрементального обновления.
- На стороне сайта реализуйте импорт данных в формате JSON.
- Запустите синхронизацию по расписанию.
Свяжитесь с нами, чтобы обсудить ваш проект. Напишите — и мы подготовим демо под вашу структуру. Получите консультацию бесплатно. Мы предлагаем разработку на заказ любого уровня сложности.







