Розробка візуалізації оргструктури (Org Chart) на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка візуалізації оргструктури (Org Chart) на сайті
Середня
~3-5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Розробка візуалізації оргструктури (Org Chart) для веб-сайтів

Org Chart — ієрархічне дерево організаційної структури з карточками сотрудників, зв'язками підпорядкування та можливістю навігації по глибокій ієрархії.

Бібліотеки

  • d3-hierarchy — низькорівневий, повна контроль
  • react-organizational-chart — проста open-source
  • OrgChart.js — функціонально багата, drag-drop
  • @ant-design/graphs — Org Chart + інші графи

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>
  );
}

Пошук по дереву

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;
}

Часові межи

Org Chart з d3-hierarchy та кліцькими карточками — 1 тиждень. З пошуком, zoom/pan та ліниво завантаженням підвузлів — 1.5–2 тижні.