Уявіть: на сайті великої компанії відділ кадрів вручну оновлює список співробітників у 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.
- Запустіть синхронізацію за розкладом.
Зв'яжіться з нами, щоб обговорити ваш проект. Напишіть — і ми підготуємо демо під вашу структуру. Отримайте консультацію безкоштовно. Ми пропонуємо розробку на замовлення будь-якого рівня складності.







