Skip to content

Instantly share code, notes, and snippets.

@pahaz
Last active November 24, 2025 17:52
Show Gist options
  • Select an option

  • Save pahaz/dd091bf0f55c1323cc5f82714baa42a3 to your computer and use it in GitHub Desktop.

Select an option

Save pahaz/dd091bf0f55c1323cc5f82714baa42a3 to your computer and use it in GitHub Desktop.
Чеклист CTO vs проблемы организации
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<title>Граф связей: чек-лист CTO ↔ боли</title>
<style>
body {
margin: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
display: flex;
flex-direction: column;
height: 100vh;
}
#graph {
flex: 1;
min-height: 0; /* чтобы flex-контейнер нормально сжимался по высоте */
}
#graph svg {
width: 100%;
height: auto; /* сохраняем пропорции, подстраиваем ширину под окно */
display: block; /* убирает лишние отступы снизу */
}
.legend {
padding: 8px 12px;
font-size: 14px;
border-bottom: 1px solid #ddd;
background: #fafafa;
display: flex;
align-items: center;
flex-wrap: wrap;
}
.legend span {
display: inline-flex;
align-items: center;
margin-right: 16px;
}
.legend .dot {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 6px;
display: inline-block;
}
.legend .view-toggle {
margin-left: auto;
font-size: 13px;
display: inline-flex;
align-items: center;
gap: 4px;
}
.legend button {
padding: 3px 8px;
font-size: 12px;
border-radius: 4px;
border: 1px solid #ccc;
background: #fff;
cursor: pointer;
}
.legend button.active {
font-weight: 600;
border-color: #333;
background: #e0e0e0;
}
.node text {
pointer-events: none;
font-size: 11px;
}
.node circle {
stroke: #fff;
stroke-width: 1.5px;
}
.node.checklist circle {
fill: #1f77b4;
}
.node.pain circle {
fill: #ff7f0e;
}
.node.selected circle {
stroke: #000;
stroke-width: 3px;
}
.link {
stroke: #aaa;
opacity: 0.6;
transition: opacity 0.15s, stroke 0.15s;
}
/* подсветка связей в двудольном режиме */
.link.highlight {
stroke: #000;
opacity: 1;
}
.link.dimmed {
opacity: 0.1;
}
/* текст комментариев по связям в двудольном режиме */
.note-text {
fill: #555;
font-size: 8px;
opacity: 0;
}
</style>
</head>
<body>
<div class="legend">
<span><span class="dot" style="background:#1f77b4"></span>Пункты чек-листа CTO</span>
<span><span class="dot" style="background:#ff7f0e"></span>Боли (1–36)</span>
<span style="margin-left:24px;opacity:.7;">
Силовой: клик по узлу — фокус и кольца. Двудольный: клик подсвечивает связи узла.
</span>
<span class="view-toggle">
Вид:
<button id="mode-force" class="active">Силовой</button>
<button id="mode-bipartite">Двудольный</button>
</span>
</div>
<div id="graph">
<svg
width="1400" height="1100"
viewBox="0 0 1400 1100"
preserveAspectRatio="xMidYMid meet"
></svg>
</div>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
// Пункты чек-листа CTO
const checklistItems = [
{ id: 'c1', label: 'Синхронизируем видение рынка и стратегии' },
{ id: 'c2', label: 'Встраиваем технологическое видение в стратегию компании' },
{ id: 'c3', label: 'Прикидываем unit-экономику, чтобы тех-инвестиции соответствовали ожиданиям' },
{ id: 'c4', label: 'Синхронизируем roadmap, цели и задачи' },
{ id: 'c5', label: 'Приоритизируем продуктовый и технический бэклог' },
{ id: 'c6', label: 'Договариваемся про бюджеты' },
{ id: 'c7', label: 'Расставляем веса и ищем компромиссы между «быстро / дорого / надёжно»' },
{ id: 'c8', label: 'Строим стратегию найма' },
{ id: 'c9', label: 'Балансируем «скорость найма / стоимость поиска / риски культурных расхождений»' },
{ id: 'c10', label: 'Проверяем, соответствует ли «рабочая среда» ожиданиям' },
{ id: 'c11', label: 'Выделяем и декомпозируем ключевые эпики и инициативы' },
{ id: 'c12', label: 'Согласовываем общие OKR / KPI / дашборды / ключевые метрики' },
{ id: 'c13', label: 'Доносим, что реализуемо, где фантазии и как управляем рисками' },
{ id: 'c14', label: 'Определяем структуру команд' },
{ id: 'c15', label: 'Формируем культуру, ценности и архитектурные принципы' },
{ id: 'c16', label: 'Определяем клиентские пути' },
{ id: 'c17', label: 'Определяем ценность для клиентов и как будем её доставлять' },
{ id: 'c18', label: 'Выравниваем ценности, ожидания и доступные инструменты' },
{ id: 'c19', label: 'Стандартизируем инженерные практики и подходы' },
{ id: 'c20', label: 'Отслеживаем сигналы от тех, кто ежедневно создаёт ценность' },
{ id: 'c21', label: 'Собираем обратную связь, поддерживаем и анализируем мотивацию' },
{ id: 'c22', label: 'Разрабатываем грейды, систему оценки и ценностей' },
{ id: 'c23', label: 'Публичные выступления, технический бренд, репутация компании' },
{ id: 'c24', label: 'Выстраиваем канал получения обратной связи с «передовой» от клиентов' },
{ id: 'c25', label: 'Определяем общую культуру и ценности' },
{ id: 'c26', label: 'Формируем внешнее позиционирование технологий' },
{ id: 'c27', label: 'Поддерживаем сложные сделки и коммитменты' },
{ id: 'c28', label: 'Работа с ключевыми партнёрами' },
{ id: 'c29', label: 'Прохождение внешних аудитов' }
];
// Боли 1–36
const pains = [
{
id: 'p1',
label: '1. Работа без фокуса',
notes: [
'Компания гоняется за всеми хотелками и трендами.',
'Нет единого плана и отказа от лишнего.'
]
},
{
id: 'p2',
label: '2. Установка на переработки',
notes: [
'Переработки считаются нормой и «героизмом».',
'Скорость выжимают из людей, а не из организации.'
]
},
{
id: 'p3',
label: '3. Избыток встреч',
notes: [
'Календарь забит совещаниями вместо работы.',
'Встречи плохо готовятся и мало что решают.'
]
},
{
id: 'p4',
label: '4. Чрезмерная бюрократия',
notes: [
'Любое изменение тонет в согласованиях.',
'Решения откладывают, ответственность размыта.'
]
},
{
id: 'p5',
label: '5. Совместительство и размытие зон ответственности',
notes: [
'На людей вешают несколько конфликтующих ролей.',
'Никто не отвечает за результат целиком, все быстрее выгорают.'
]
},
{
id: 'p6',
label: '6. Медленное принятие решений и бессмысленная работа',
notes: [
'Фичи делаются, но релизы и эффект откладываются на месяцы.',
'У людей исчезает ощущение смысла и влияния.'
]
},
{
id: 'p7',
label: '7. Сбой в Work-life и невозможность отключиться',
notes: [
'Работа растягивается на вечера, выходные, другие часовые пояса.',
'Дежурства и ожидание звонка не дают восстановиться.'
]
},
{
id: 'p8',
label: '8. Отсутствие чётких и адекватных требований',
notes: [
'Руководство не формулирует понятных целей и критериев.',
'Команды постоянно угадывают, «что имели в виду наверху».'
]
},
{
id: 'p9',
label: '9. Изолированность команд и дефицит коммуникаций',
notes: [
'Люди работают в «коробочках» без нормального общения.',
'Продукт и команда фрагментируются, падает вовлечённость.'
]
},
{
id: 'p10',
label: '10. Отсутствие контроля над жизнью',
notes: [
'Сотрудники почти ни на что не влияют.',
'Решения принимаются «над головой», рождая беспомощность.'
]
},
{
id: 'p11',
label: '11. Непрозрачность карьерного трека',
notes: [
'Неясно, как расти и сколько это займет.',
'Карьера ощущается как туман с высоким стрессом.'
]
},
{
id: 'p12',
label: '12. Выстроена система обмана',
notes: [
'Обещают рост и компенсации, но постоянно переносят.',
'Доверие ломается, мотивация держится на манипуляциях.'
]
},
{
id: 'p13',
label: '13. Метрики превыше смысла',
notes: [
'Процессы подстраивают под KPI, а не под ценность.',
'Люди играют в цифры вместо улучшения системы.'
]
},
{
id: 'p14',
label: '14. Хайп превыше смысла',
notes: [
'Технологии и фичи выбирают по моде, а не по пользе.',
'Ресурсы уходят на «хайп-проекты» вместо ключевых задач.'
]
},
{
id: 'p15',
label: '15. Обязательный возврат в офис',
notes: [
'Насильный отказ от гибрида и удалёнки.',
'Теряется гибкость, растёт недовольство и отток.'
]
},
{
id: 'p16',
label: '16. Слишком много или совсем без документации',
notes: [
'Пишут толстые документы ради процесса.',
'Или обходятся без описаний, всё держится в головах.'
]
},
{
id: 'p17',
label: '17. Отсутствие системности информации',
notes: [
'Документы разрознены, противоречат и быстро стареют.',
'Непонятно, где искать ответы и как в это всё въехать.'
]
},
{
id: 'p18',
label: '18. Негативный новостной фон',
notes: [
'Постоянные новости про сокращения, кризисы, угрозы.',
'Люди перестают верить в будущее компании.'
]
},
{
id: 'p19',
label: '19. Отсутствие или несоблюдение стратегии',
notes: [
'Стратегия либо отсутствует, либо живет только в презентации.',
'Руководство почти не тратит время на стратегическую работу.'
]
},
{
id: 'p20',
label: '20. Отсутствие приоритетов',
notes: [
'Сразу много «самых важных» задач.',
'Командам приходится самим решать, что делать и чем жертвовать.'
]
},
{
id: 'p21',
label: '21. Дискредитация стратегии',
notes: [
'Формально делают «свои мини-стратегии», не связанные с общей.',
'Стратегия превращается в формальность, которой никто не верит.'
]
},
{
id: 'p22',
label: '22. Постоянные отвлечения',
notes: [
'Почта, мессенджеры, «на минуточку» рвут фокус весь день.',
'Много времени уходит на переключения вместо глубокой работы.'
]
},
{
id: 'p23',
label: '23. Организационный долг',
notes: [
'Временные костыли в ролях и процессах становятся постоянными.',
'Любое изменение упирается в старые «временные решения».'
]
},
{
id: 'p24',
label: '24. Технический долг',
notes: [
'Качество, тесты и рефакторинг системно откладывают.',
'Система становится хрупкой, дорогой и тормозит развитие.'
]
},
{
id: 'p25',
label: '25. Хаос в процессах',
notes: [
'Нет единых правил, кто и как что делает.',
'Всё держится на ручном управлении и личных договорённостях.'
]
},
{
id: 'p26',
label: '26. Борьба с конфликтами',
notes: [
'Конфликты стараются гасить, а не разбирать причины.',
'Потеря сигналов о проблемах и общая апатия.'
]
},
{
id: 'p27',
label: '27. Фасилитация проблем',
notes: [
'Фасилитация используется как постоянное «обезболивающее».',
'Корневые организационные баги не лечатся.'
]
},
{
id: 'p28',
label: '28. Агенты изменений не видят всей картины',
notes: [
'Тем, кто тянет изменения, не дают контекста и полномочий.',
'Инициативы остаются локальными и разрозненными.'
]
},
{
id: 'p29',
label: '29. Простои и панические релизы',
notes: [
'Частые аварии, инциденты и «пожарные» выкаты.',
'Команда живёт в постоянном режиме тушения огня.'
]
},
{
id: 'p30',
label: '30. Отсутствие резервов',
notes: [
'Люди и мощности загружены под 100%.',
'Малейший сбой рушит план и перегружает всех.'
]
},
{
id: 'p31',
label: '31. Митинги по утрам, работа — вечером',
notes: [
'Самое продуктивное время занято совещаниями и перепиской.',
'Настоящая работа уезжает на вечер и ночь.'
]
},
{
id: 'p32',
label: '32. Отсутствие онбординга (особенно для тимлидов)',
notes: [
'Новичков и новых менеджеров плохо вводят в роль.',
'Высокий стресс, ошибки и ранние увольнения.'
]
},
{
id: 'p33',
label: '33. Раздавать задачи сверху',
notes: [
'Руководитель сам раздаёт задачи как считает нужным.',
'Не учитываются сильные стороны и интересы людей.'
]
},
{
id: 'p34',
label: '34. Использовать метрики, ревью и прочие практики для критики',
notes: [
'Метрики и оценка превращаются в инструмент давления.',
'Ревью бьёт по самооценке вместо развития.'
]
},
{
id: 'p35',
label: '35. Неправильный выбор стека технологий и подходов',
notes: [
'Технологии выбирают по привычке, цене или хайпу.',
'Стек удорожает проект и повышает риск провала.'
]
},
{
id: 'p36',
label: '36. Массовые сокращения',
notes: [
'Увольнения используют как главный способ «оптимизации».',
'Долгосрочно рушат результаты и выталкивают лучших людей.'
]
}
];
// Рёбра: какая боль лечится какими пунктами чек-листа + weight (1..3)
const links = [
// p1 «1. Работа без фокуса» ← c1 «Синхронизируем видение рынка и стратегии» — главный стратегический рычаг фокуса
{ source: 'c1', target: 'p1', weight: 3, notes: 'Привязывает работу к общему контексту рынка.' },
// p1 «1. Работа без фокуса» ← c2 «Встраиваем технологическое видение в стратегию компании» — соединяет тех и стратегию
{ source: 'c2', target: 'p1', weight: 2, notes: 'Соединяет технологические решения со стратегией.' },
// p1 «1. Работа без фокуса» ← c3 «Прикидываем unit-экономику, чтобы тех-инвестиции соответствовали ожиданиям» — экономический фильтр
{ source: 'c3', target: 'p1', weight: 2, notes: 'Фильтрует инициативы через экономический смысл.' },
// p1 «1. Работа без фокуса» ← c4 «Синхронизируем roadmap, цели и задачи» — фокус закрепляется в планах
{ source: 'c4', target: 'p1', weight: 2, notes: 'Фиксирует фокус в планах и целях.' },
// p1 «1. Работа без фокуса» ← c5 «Приоритизируем продуктовый и технический бэклог» — локальное отсечение лишнего
{ source: 'c5', target: 'p1', weight: 1, notes: 'Отбрасывает лишние задачи в бэклоге.' },
// p1 «1. Работа без фокуса» ← c12 «Согласовываем общие OKR / KPI / дашборды / ключевые метрики» — подкрепляет фокус измеримыми целями
{ source: 'c12', target: 'p1', weight: 2, notes: 'Связывает фокус с измеримыми результатами, а не с хотелками.' },
// p1 «1. Работа без фокуса» ← c13 «Доносим, что реализуемо, где фантазии и как управляем рисками» — отрезвление хотелок
{ source: 'c13', target: 'p1', weight: 1, notes: 'Управление рисками и реалистичностью отсекает расфокус на «фантазии».' },
// p2 «2. Установка на переработки» ← c6 «Договариваемся про бюджеты» — главный рычаг: ресурсы вместо переработок
{ source: 'c6', target: 'p2', weight: 3, notes: 'Закладывает ресурсы без опоры на переработки.' },
// p2 «2. Установка на переработки» ← c7 «Расставляем веса и ищем компромиссы между «быстро / дорого / надёжно»» — баланс скорости и качества
{ source: 'c7', target: 'p2', weight: 2, notes: 'Ограничивает скорость за счёт качества, а не людей.' },
// p2 «2. Установка на переработки» ← c3 «Прикидываем unit-экономику, чтобы тех-инвестиции соответствовали ожиданиям» — учитывает цену выгорания
{ source: 'c3', target: 'p2', weight: 2, notes: 'Учитывает цену выгорания и текучки.' },
// p2 «2. Установка на переработки» ← c8 «Строим стратегию найма» — найм вместо вечного аврала
{ source: 'c8', target: 'p2', weight: 2, notes: 'Планирует найм вместо вечного аврала.' },
// p2 «2. Установка на переработки» ← c9 «Балансируем «скорость найма / стоимость поиска / риски культурных расхождений»» — сглаживает качели найма
{ source: 'c9', target: 'p2', weight: 2, notes: 'Не допускает «хиринг во что бы то ни стало».' },
// p2 «2. Установка на переработки» ← c10 «Проверяем, соответствует ли «рабочая среда» ожиданиям» — нормализует ожидания по режиму
{ source: 'c10', target: 'p2', weight: 1, notes: 'Фиксирует разумные ожидания по режиму работы.' },
// p2 «2. Установка на переработки» ← c15 «Формируем культуру, ценности и архитектурные принципы» — закрепляем норму устойчивой, а не героической работы.
{ source: 'c15', target: 'p2', weight: 1, notes: 'Культура устойчивой скорости, а не героизма.' },
// p2 «2. Установка на переработки» ← c21 «Собираем обратную связь, поддерживаем и анализируем мотивацию» — видим и лечим выгорание.
{ source: 'c21', target: 'p2', weight: 1, notes: 'Отслеживаем выгорание и корректируем практики.' },
// p2 «2. Установка на переработки» ← c19 «Стандартизируем инженерные практики и подходы» — автоматизация рутины
{ source: 'c19', target: 'p2', weight: 1, notes: 'Автоматизация снижает объем ручного труда и овертаймов.' },
// p3 «3. Избыток встреч» ← c4 «Синхронизируем roadmap, цели и задачи» — главный рычаг: заменяет статус-митинги планом
{ source: 'c4', target: 'p3', weight: 3, notes: 'Снимает нужду в статус-митингах через общий план.' },
// p3 «3. Избыток встреч» ← c11 «Выделяем и декомпозируем ключевые эпики и инициативы» — ясные задачи вместо созвонов «что делать?»
{ source: 'c11', target: 'p3', weight: 2, notes: 'Делает задачи ясными и декомпозированными.' },
// p3 «3. Избыток встреч» ← c12 «Согласовываем общие OKR / KPI / дашборды / ключевые метрики» — статус считывается из дашборда
{ source: 'c12', target: 'p3', weight: 2, notes: 'Переносит статус в дашборды, а не в созвоны.' },
// p3 «3. Избыток встреч» ← c10 «Проверяем, соответствует ли «рабочая среда» ожиданиям» — формат и объём митингов подстраивают под людей
{ source: 'c10', target: 'p3', weight: 1, notes: 'Подстраивает формат встреч под комфорт работы.' },
// p3 «3. Избыток встреч» ← c19 «Стандартизируем инженерные практики и подходы» — можно ввести правила по встречам и синкам.
{ source: 'c19', target: 'p3', weight: 1, notes: 'Стандарты включают правила про встречи и синки.' },
// p4 «4. Чрезмерная бюрократия» ← c14 «Определяем структуру команд» — понятные зоны ответственности вместо согласований по кругу.
{ source: 'c14', target: 'p4', weight: 3, notes: 'Ясная структура команд проясняет, кто решает и за что отвечает.' },
// p4 «4. Чрезмерная бюрократия» ← c19 «Стандартизируем инженерные практики и подходы» — стандарты вместо ручного контроля
{ source: 'c19', target: 'p4', weight: 2, notes: 'Задаёт простые стандарты вместо ручной бюрократии.' },
// p4 «4. Чрезмерная бюрократия» ← c4 «Синхронизируем roadmap, цели и задачи» — меньше формальных согласований ради галочки.
{ source: 'c4', target: 'p4', weight: 1, notes: 'Прозрачные цели уменьшают количество согласований ради галочки.' },
// p4 «4. Чрезмерная бюрократия» ← c13 «Доносим, что реализуемо, где фантазии и как управляем рисками» — главный фильтр того, что надо согласовывать
{ source: 'c13', target: 'p4', weight: 1, notes: 'Определяет, какие решения действительно требуют согласований.' },
// p4 «4. Чрезмерная бюрократия» ← c11 «Выделяем и декомпозируем ключевые эпики и инициативы» — понятные owner-ы решений
{ source: 'c11', target: 'p4', weight: 1, notes: 'Назначает владельцев инициатив и решений.' },
// p5 «5. Совместительство и размытие зон ответственности» ← c14 «Определяем структуру команд» — главный рычаг: ясная оргструктура
{ source: 'c14', target: 'p5', weight: 3, notes: 'Разводит роли и ответственность по командам.' },
// p5 «5. Совместительство и размытие зон ответственности» ← c15 «Формируем культуру, ценности и архитектурные принципы» — культура one owner
{ source: 'c15', target: 'p5', weight: 2, notes: 'Фиксирует принцип «one owner» в культуре.' },
// p5 «5. Совместительство и размытие зон ответственности» ← c4 «Синхронизируем roadmap, цели и задачи» — цели закреплены за конкретными командами
{ source: 'c4', target: 'p5', weight: 2, notes: 'Привязывает цели к конкретным владельцам.' },
// p5 «5. Совместительство и размытие зон ответственности» ← c8 «Строим стратегию найма» — найм закрывает системные дыры вместо совместительства
{ source: 'c8', target: 'p5', weight: 1, notes: 'Добирает людей вместо загрузки одних и тех же.' },
// p5 «5. Совместительство и размытие зон ответственности» ← c9 «Балансируем «скорость найма / стоимость поиска / риски культурных расхождений»» — не форсим совместительство ради скорости.
{ source: 'c9', target: 'p5', weight: 1, notes: 'Балансируем скорость найма и культурные риски, снижая совместительство.' },
// p6 «6. Медленное принятие решений и бессмысленная работа» ← c11 «Выделяем и декомпозируем ключевые эпики и инициативы» — главный рычаг: короткие циклы
{ source: 'c11', target: 'p6', weight: 3, notes: 'Рубит большие проекты на управляемые шаги.' },
// p6 «6. Медленное принятие решений и бессмысленная работа» ← c4 «Синхронизируем roadmap, цели и задачи» — ускоряет согласование решений.
{ source: 'c4', target: 'p6', weight: 2, notes: 'Синхронизация roadmap ускоряет решения по фичам и релизам.' },
// p6 «6. Медленное принятие решений и бессмысленная работа» ← c12 «Согласовываем общие OKR / KPI / дашборды / ключевые метрики» — видны цели и результаты
{ source: 'c12', target: 'p6', weight: 2, notes: 'Фокусирует команды на измеримых результатах.' },
// p6 «6. Медленное принятие решений и бессмысленная работа» ← c16 «Определяем клиентские пути» — задачи привязаны к конкретным сценариям
{ source: 'c16', target: 'p6', weight: 2, notes: 'Привязывает задачи к понятным пользовательским сценариям.' },
// p6 «6. Медленное принятие решений и бессмысленная работа» ← c13 «Доносим, что реализуемо, где фантазии и как управляем рисками» — убирает бессмысленные ожидания и подвешенные фичи.
{ source: 'c13', target: 'p6', weight: 2, notes: 'Описываем, что реально и как управляем рисками.' },
// p6 «6. Медленное принятие решений и бессмысленная работа» ← c14 «Определяем структуру команд» — автономность ускоряет
{ source: 'c14', target: 'p6', weight: 2, notes: 'Автономность ускоряет принятие решений.' },
// p6 «6. Медленное принятие решений и бессмысленная работа» ← c5 «Приоритизируем продуктовый и технический бэклог» — в первую очередь доезжает ценность
{ source: 'c5', target: 'p6', weight: 2, notes: 'Выстраивает очередь задач по ценности.' },
// p6 «6. Медленное принятие решений и бессмысленная работа» ← c24 «Выстраиваем канал получения обратной связи с «передовой» от клиентов» — быстрая проверка эффекта
{ source: 'c24', target: 'p6', weight: 1, notes: 'Быстро замыкает цикл от клиента до команды.' },
// p6 «6. Медленное принятие решений и бессмысленная работа» ← c27 «Поддерживаем сложные сделки и коммитменты» — делаем обязательства реалистичными и управляемыми.
{ source: 'c27', target: 'p6', weight: 1, notes: 'Поддержка сложных коммитов делает их реалистичнее.' },
// p7 «7. Сбой в Work-life и невозможность отключиться» ← c10 «Проверяем, соответствует ли «рабочая среда» ожиданиям» — главный рычаг WL-баланса
{ source: 'c10', target: 'p7', weight: 3, notes: 'Формализует границы рабочей среды и времени.' },
// p7 «7. Сбой в Work-life и невозможность отключиться» ← c21 «Собираем обратную связь, поддерживаем и анализируем мотивацию» — вовремя замечаем перегруз и меняем процессы.
{ source: 'c21', target: 'p7', weight: 2, notes: 'Отслеживаем выгорание и своевременно поддерживаем людей.' },
// p7 «7. Сбой в Work-life и невозможность отключиться» ← c7 «Расставляем веса и ищем компромиссы между «быстро / дорого / надёжно»» — не всё всегда «быстро»
{ source: 'c7', target: 'p7', weight: 2, notes: 'Убирает режим «всегда срочно» через договорённости.' },
// p7 «7. Сбой в Work-life и невозможность отключиться» ← c6 «Договариваемся про бюджеты» — ресурсы на поддержку вместо ночных дежурств всех подряд
{ source: 'c6', target: 'p7', weight: 2, notes: 'Закладывает бюджет на поддержку и SRE, а не людей по ночам.' },
// p7 «7. Сбой в Work-life и невозможность отключиться» ← c14 «Определяем структуру команд» — нормальные on-call, ротации и зоны ответственности
{ source: 'c14', target: 'p7', weight: 1, notes: 'Разводит on-call и зоны ответственности.' },
// p8 «8. Отсутствие чётких и адекватных требований» ← c1 «Синхронизируем видение рынка и стратегии» — главный источник смысла требований
{ source: 'c1', target: 'p8', weight: 3, notes: 'Задаёт рамку, для чего вообще делаем продукт.' },
// p8 «8. Отсутствие чётких и адекватных требований» ← c16 «Определяем клиентские пути» — требования от customer journey
{ source: 'c16', target: 'p8', weight: 3, notes: 'Формализует сценарии, под которые пишем требования.' },
// p8 «8. Отсутствие чётких и адекватных требований» ← c2 «Встраиваем технологическое видение в стратегию компании» — отсекает лишние тех-хотелки
{ source: 'c2', target: 'p8', weight: 2, notes: 'Отсекает «невписывающиеся» тех-хотелки.' },
// p8 «8. Отсутствие чётких и адекватных требований» ← c24 «Выстраиваем канал получения обратной связи с «передовой» от клиентов» — получаем реальные запросы, а не догадки.
{ source: 'c24', target: 'p8', weight: 2, notes: 'Канал с «передовой» уточняет реальные ожидания клиентов.' },
// p8 «8. Отсутствие чётких и адекватных требований» ← c4 «Синхронизируем roadmap, цели и задачи» — согласованные цели и сроки
{ source: 'c4', target: 'p8', weight: 2, notes: 'Согласовывает цели и сроки фич между сторонами.' },
// p8 «8. Отсутствие чётких и адекватных требований» ← c17 «Определяем ценность для клиентов и как будем её доставлять» — требования = ценность
{ source: 'c17', target: 'p8', weight: 2, notes: 'Фокусирует требования на клиентской ценности.' },
// p8 «8. Отсутствие чётких и адекватных требований» ← c18 «Выравниваем ценности, ожидания и доступные инструменты» — продажи и маркетинг не обещают лишнего
{ source: 'c18', target: 'p8', weight: 1, notes: 'Синхронизирует продажи и маркетинг с реальными возможностями.' },
// p9 «9. Изолированность команд и дефицит коммуникаций» ← c14 «Определяем структуру команд» — главный рычаг: как команды стыкуются
{ source: 'c14', target: 'p9', weight: 3, notes: 'Описывает, как команды стыкуются друг с другом.' },
// p9 «9. Изолированность команд и дефицит коммуникаций» ← c12 «Согласовываем общие OKR / KPI / дашборды / ключевые метрики» — цель объединяет
{ source: 'c12', target: 'p9', weight: 3, notes: 'Общие цели заставляют выходить из изоляции ради результата.' },
// p9 «9. Изолированность команд и дефицит коммуникаций» ← c15 «Формируем культуру, ценности и архитектурные принципы» — ценности про открытость
{ source: 'c15', target: 'p9', weight: 2, notes: 'Поощряет открытость и горизонтальные связи.' },
// p9 «9. Изолированность команд и дефицит коммуникаций» ← c19 «Стандартизируем инженерные практики и подходы» — общие ритуалы и процессы
{ source: 'c19', target: 'p9', weight: 2, notes: 'Задаёт общие инженерные ритуалы и форматы общения.' },
// p9 «9. Изолированность команд и дефицит коммуникаций» ← c4 «Синхронизируем roadmap, цели и задачи» — объединяем команды вокруг общих целей и релизов.
{ source: 'c4', target: 'p9', weight: 2, notes: 'Общий roadmap связывает команды вокруг общих целей.' },
// p9 «9. Изолированность команд и дефицит коммуникаций» ← c20 «Отслеживаем сигналы от тех, кто ежедневно создаёт ценность» — сигналы о стыках и разрывах
{ source: 'c20', target: 'p9', weight: 1, notes: 'Подсвечивает проблемы взаимодействия «с земли».' },
// p10 «10. Отсутствие контроля над жизнью» ← c21 «Собираем обратную связь, поддерживаем и анализируем мотивацию» — главный канал влияния сотрудников
{ source: 'c21', target: 'p10', weight: 3, notes: 'Создаёт канал, где люди могут влиять на решения.' },
// p10 «10. Отсутствие контроля над жизнью» ← c25 «Определяем общую культуру и ценности» — закрепляем уважение к автономии как норму.
{ source: 'c25', target: 'p10', weight: 2, notes: 'Культура уважает личные границы и автономию.' },
// p10 «10. Отсутствие контроля над жизнью» ← c20 «Отслеживаем сигналы от тех, кто ежедневно создаёт ценность» — поднимает сигналы о перегрузе и бессмысленной работе
{ source: 'c20', target: 'p10', weight: 2, notes: 'Поднимает сигналы о перегрузе и бессмысленной работе.' },
// p10 «10. Отсутствие контроля над жизнью» ← c10 «Проверяем, соответствует ли «рабочая среда» ожиданиям» — влияет на режим и формат работы
{ source: 'c10', target: 'p10', weight: 2, notes: 'Настраивает режим работы под ожидания команды.' },
// p10 «10. Отсутствие контроля над жизнью» ← c4 «Синхронизируем roadmap, цели и задачи» — показывает связь работы с целями
{ source: 'c4', target: 'p10', weight: 2, notes: 'Объясняет, как вклад команды связан с целями.' },
// p10 «10. Отсутствие контроля над жизнью» ← c22 «Разрабатываем грейды, систему оценки и ценностей» — прозрачная связь вклада и роста
{ source: 'c22', target: 'p10', weight: 1, notes: 'Прозрачно связывает вклад с ростом и оценкой.' },
// p10 «10. Отсутствие контроля над жизнью» ← c18 «Выравниваем ценности, ожидания и доступные инструменты» — честно проговариваем, что возможно и на каких условиях.
{ source: 'c18', target: 'p10', weight: 1, notes: 'Выравниваем ожидания и доступные инструменты.' },
// p11 «11. Непрозрачность карьерного трека» ← c22 «Разрабатываем грейды, систему оценки и ценностей» — главный источник понятного трека
{ source: 'c22', target: 'p11', weight: 3, notes: 'Фиксирует уровни, критерии и ожидания по ролям.' },
// p11 «11. Непрозрачность карьерного трека» ← c21 «Собираем обратную связь, поддерживаем и анализируем мотивацию» — явные планы развития
{ source: 'c21', target: 'p11', weight: 2, notes: 'Выявляет карьерные ожидания и формирует планы роста.' },
// p11 «11. Непрозрачность карьерного трека» ← c14 «Определяем структуру команд» — роли и уровни встроены в структуру
{ source: 'c14', target: 'p11', weight: 1, notes: 'Привязывает роли и уровни к структуре команд.' },
// p11 «11. Непрозрачность карьерного трека» ← c25 «Определяем общую культуру и ценности» — поддерживающая культура снижает тревожность вокруг роста.
{ source: 'c25', target: 'p11', weight: 1, notes: 'Культура поддерживает развитие, а не только «героизм».' },
// p11 «11. Непрозрачность карьерного трека» ← c8 «Строим стратегию найма» — внутренний рост vs внешний найм
{ source: 'c8', target: 'p11', weight: 1, notes: 'Растим внутри или нанимаем.' },
// p12 «12. Выстроена система обмана» ← c25 «Определяем общую культуру и ценности» — главный рычаг честности
{ source: 'c25', target: 'p12', weight: 3, notes: 'Закрепляет честность и прозрачность как норму.' },
// p12 «12. Выстроена система обмана» ← c18 «Выравниваем ценности, ожидания и доступные инструменты» — перестаём обещать то, что не собираемся делать.
{ source: 'c18', target: 'p12', weight: 2, notes: 'Выравниваем ожидания вместо ложных обещаний.' },
// p12 «12. Выстроена система обмана» ← c22 «Разрабатываем грейды, систему оценки и ценностей» — убирает произвол в повышениях
{ source: 'c22', target: 'p12', weight: 2, notes: 'Убирает произвол в повышениях и оценках.' },
// p12 «12. Выстроена система обмана» ← c21 «Собираем обратную связь, поддерживаем и анализируем мотивацию» — сверка обещаний и реальности
{ source: 'c21', target: 'p12', weight: 1, notes: 'Регулярно сверяет реальность с обещаниями.' },
// p13 «13. Метрики превыше смысла» ← c12 «Согласовываем общие OKR / KPI / дашборды / ключевые метрики» — главный рычаг: метрики как навигация
{ source: 'c12', target: 'p13', weight: 3, notes: 'Делает метрики инструментом навигации, а не кнута.' },
// p13 «13. Метрики превыше смысла» ← c25 «Определяем общую культуру и ценности» — смысл работы важнее цифр
{ source: 'c25', target: 'p13', weight: 2, notes: 'Ставит смысл работы выше цифр в отчёте.' },
// p13 «13. Метрики превыше смысла» ← c3 «Прикидываем unit-экономику, чтобы тех-инвестиции соответствовали ожиданиям» — метрики привязаны к экономике
{ source: 'c3', target: 'p13', weight: 2, notes: 'Связывает метрики с реальной экономикой продукта.' },
// p13 «13. Метрики превыше смысла» ← c17 «Определяем ценность для клиентов и как будем её доставлять» — возвращаем метрикам связь с реальной пользой.
{ source: 'c17', target: 'p13', weight: 2, notes: 'Фокус на ценности для клиента возвращает смысл метрикам.' },
// p13 «13. Метрики превыше смысла» ← c14 «Определяем структуру команд» — продуктовая ориентация
{ source: 'c14', target: 'p13', weight: 1, notes: 'Команды отвечают за ценность.' },
// p13 «13. Метрики превыше смысла» ← c21 «Собираем обратную связь, поддерживаем и анализируем мотивацию» — живые сигналы важнее графика
{ source: 'c21', target: 'p13', weight: 1, notes: 'Даёт живую обратную связь поверх цифр.' },
// p14 «14. Хайп превыше смысла» ← c1 «Синхронизируем видение рынка и стратегии» — главный фильтр хайпа через рынок/стратегию
{ source: 'c1', target: 'p14', weight: 3, notes: 'Фильтрует хайп-фичи через контекст рынка.' },
// p14 «14. Хайп превыше смысла» ← c2 «Встраиваем технологическое видение в стратегию компании» — технологические решения под стратегию
{ source: 'c2', target: 'p14', weight: 3, notes: 'Оценивает стеки через призму стратегии.' },
// p14 «14. Хайп превыше смысла» ← c3 «Прикидываем unit-экономику, чтобы тех-инвестиции соответствовали ожиданиям» — экономика хайп-проектов
{ source: 'c3', target: 'p14', weight: 2, notes: 'Считает, окупится ли хайповая инициатива.' },
// p14 «14. Хайп превыше смысла» ← c17 «Определяем ценность для клиентов и как будем её доставлять» — хайп сверяется с ценностью
{ source: 'c17', target: 'p14', weight: 2, notes: 'Проверяет, есть ли от хайпа ценность клиенту.' },
// p14 «14. Хайп превыше смысла» ← c5 «Приоритизируем продуктовый и технический бэклог» — хайп не попадает в топ бэклога без обоснования
{ source: 'c5', target: 'p14', weight: 1, notes: 'Не пускает хайп в топ бэклога без обоснований.' },
// p14 «14. Хайп превыше смысла» ← c26 «Формируем внешнее позиционирование технологий» — вынуждает объяснять, зачем именно эти технологии бизнесу.
{ source: 'c26', target: 'p14', weight: 2, notes: 'Внешнее позиционирование требует осмысленных решений.' },
// p15 «15. Обязательный возврат в офис» ← c10 «Проверяем, соответствует ли «рабочая среда» ожиданиям» — главный рычаг формата работы
{ source: 'c10', target: 'p15', weight: 3, notes: 'Собирает сигналы про формат работы и гибкость.' },
// p15 «15. Обязательный возврат в офис» ← c18 «Выравниваем ценности, ожидания и доступные инструменты» — честно обсуждаем гибрид/удалёнку/офис.
{ source: 'c18', target: 'p15', weight: 2, notes: 'Выравниваем ожидания по поводу гибрида/офиса/удалёнки.' },
// p15 «15. Обязательный возврат в офис» ← c25 «Определяем общую культуру и ценности» — ценность доверия и автономии
{ source: 'c25', target: 'p15', weight: 2, notes: 'Формулирует ценность доверия и автономии.' },
// p15 «15. Обязательный возврат в офис» ← c8 «Строим стратегию найма» — стратегия найма учитывает гибрид/удалёнку
{ source: 'c8', target: 'p15', weight: 1, notes: 'Закладывает гибрид и удалёнку в стратегию найма.' },
// p16 «16. Слишком много или совсем без документации» ← c19 «Стандартизируем инженерные практики и подходы» — главный рычаг: что и как документируем
{ source: 'c19', target: 'p16', weight: 3, notes: 'Определяет, что и как документируем.' },
// p16 «16. Слишком много или совсем без документации» ← c11 «Выделяем и декомпозируем ключевые эпики и инициативы» — дока вокруг эпиков
{ source: 'c11', target: 'p16', weight: 2, notes: 'Привязывает документы к эпикам, а не к «всему подряд».' },
// p16 «16. Слишком много или совсем без документации» ← c10 «Проверяем, соответствует ли «рабочая среда» ожиданиям» — комфортный объём и формат документации
{ source: 'c10', target: 'p16', weight: 1, notes: 'Учитывает, сколько документации людям комфортно.' },
// p16 «16. Слишком много или совсем без документации» ← c16 «Определяем клиентские пути» — документация обслуживает конкретный сценарий, а не сама себя.
{ source: 'c16', target: 'p16', weight: 1, notes: 'Клиентские пути задают минимально нужный уровень описаний.' },
// p16 «16. Слишком много или совсем без документации» ← c17 «Определяем ценность для клиентов и как будем её доставлять» — оставляем только ту документацию, которая помогает доставлять ценность.
{ source: 'c17', target: 'p16', weight: 1, notes: 'Фокус на ценности отсеивает бессмысленную документацию.' },
// p16 «16. Слишком много или совсем без документации» ← c22 «Разрабатываем грейды, систему оценки и ценностей» — требование к команде
{ source: 'c22', target: 'p16', weight: 1, notes: 'Навык документирования можем включить в грейды.' },
// p17 «17. Отсутствие системности информации» ← c19 «Стандартизируем инженерные практики и подходы» — главный набор правил знания-шеринга
{ source: 'c19', target: 'p17', weight: 3, notes: 'Вводит единые правила знания-шеринга.' },
// p17 «17. Отсутствие системности информации» ← c11 «Выделяем и декомпозируем ключевые эпики и инициативы» — структурируем информацию вокруг эпиков и инициатив.
{ source: 'c11', target: 'p17', weight: 2, notes: 'Декомпозиция инициатив структурирует информацию по эпикам.' },
// p17 «17. Отсутствие системности информации» ← c15 «Формируем культуру, ценности и архитектурные принципы» — культурная норма делиться знаниями
{ source: 'c15', target: 'p17', weight: 2, notes: 'Закрепляет культуру делиться знаниями.' },
// p17 «17. Отсутствие системности информации» ← c24 «Выстраиваем канал получения обратной связи с «передовой» от клиентов» — заводим клиентские инсайты в единую систему.
{ source: 'c24', target: 'p17', weight: 1, notes: 'Каналы от клиентов заводим в понятную систему знаний.' },
// p17 «17. Отсутствие системности информации» ← c20 «Отслеживаем сигналы от тех, кто ежедневно создаёт ценность» — показывают, где инфа реально рвётся
{ source: 'c20', target: 'p17', weight: 1, notes: 'Показывает, где информация реально теряется.' },
// p18 «18. Негативный новостной фон» ← c23 «Публичные выступления, технический бренд, репутация компании» — главный канал внешних новостей
{ source: 'c23', target: 'p18', weight: 3, notes: 'Отстраивает честную и стабильную внешнюю коммуникацию.' },
// p18 «18. Негативный новостной фон» ← c25 «Определяем общую культуру и ценности» — внутренняя честность про проблемы и будущее
{ source: 'c25', target: 'p18', weight: 2, notes: 'Определяет, как говорить о проблемах внутри.' },
// p18 «18. Негативный новостной фон» ← c8 «Строим стратегию найма» — уменьшает качели массовый найм/массовые сокращения
{ source: 'c8', target: 'p18', weight: 1, notes: 'Сглаживает качели «массовый найм → массовые сокращения».' },
// p18 «18. Негативный новостной фон» ← c21 «Собираем обратную связь, поддерживаем и анализируем мотивацию» — работаем с тревогой, а не игнорируем её.
{ source: 'c21', target: 'p18', weight: 1, notes: 'Работаем с тревогами людей через обратную связь.' },
// p18 «18. Негативный новостной фон» ← c23 «Публичные выступления, технический бренд, репутация компании» — через внешнюю повестку создаём ощущение устойчивости.
{ source: 'c23', target: 'p18', weight: 1, notes: 'Сильный технический бренд поддерживает ощущение устойчивости.' },
// p19 «19. Отсутствие или несоблюдение стратегии» ← c1 «Синхронизируем видение рынка и стратегии» — формирует стратегическую рамку
{ source: 'c1', target: 'p19', weight: 3, notes: 'Формирует общую стратегическую рамку.' },
// p19 «19. Отсутствие или несоблюдение стратегии» ← c2 «Встраиваем технологическое видение в стратегию компании» — техкурс как часть стратегии
{ source: 'c2', target: 'p19', weight: 2, notes: 'Связывает технологический курс со стратегией.' },
// p19 «19. Отсутствие или несоблюдение стратегии» ← c3 «Прикидываем unit-экономику, чтобы тех-инвестиции соответствовали ожиданиям» — реалистичность стратегии по деньгам
{ source: 'c3', target: 'p19', weight: 2, notes: 'Учитывает экономику достижимости целей.' },
// p19 «19. Отсутствие или несоблюдение стратегии» ← c4 «Синхронизируем roadmap, цели и задачи» — разворачивает стратегию в планы
{ source: 'c4', target: 'p19', weight: 2, notes: 'Разворачивает стратегию в конкретный roadmap.' },
// p19 «19. Отсутствие или несоблюдение стратегии» ← c12 «Согласовываем общие OKR / KPI / дашборды / ключевые метрики» — делает стратегию измеримой
{ source: 'c12', target: 'p19', weight: 1, notes: 'Привязывает метрики к стратегическим целям.' },
// p20 «20. Отсутствие приоритетов» ← c5 «Приоритизируем продуктовый и технический бэклог» — главный инструмент явных приоритетов
{ source: 'c5', target: 'p20', weight: 3, notes: 'Делает явным порядок задач и техдолга.' },
// p20 «20. Отсутствие приоритетов» ← c4 «Синхронизируем roadmap, цели и задачи» — расставляет приоритеты по целям
{ source: 'c4', target: 'p20', weight: 2, notes: 'Уточняет, какие цели важнее в горизонте квартала.' },
// p20 «20. Отсутствие приоритетов» ← c7 «Расставляем веса и ищем компромиссы между «быстро / дорого / надёжно»» — решает, где скорость, а где надёжность
{ source: 'c7', target: 'p20', weight: 2, notes: 'Выбирает, где ускоряемся, а где бережём качество.' },
// p20 «20. Отсутствие приоритетов» ← c3 «Прикидываем unit-экономику, чтобы тех-инвестиции соответствовали ожиданиям» — экономическая сортировка задач
{ source: 'c3', target: 'p20', weight: 1, notes: 'Сравнивает приоритеты по экономической отдаче.' },
// p21 «21. Дискредитация стратегии» ← c1 «Синхронизируем видение рынка и стратегии» — одна общая стратегия вместо множества локальных
{ source: 'c1', target: 'p21', weight: 3, notes: 'Даёт единую стратегию вместо множества локальных.' },
// p21 «21. Дискредитация стратегии» ← c2 «Встраиваем технологическое видение в стратегию компании» — тех-инициативы не противоречат общей рамке
{ source: 'c2', target: 'p21', weight: 2, notes: 'Уточняет, какие тех-инициативы в неё вписываются.' },
// p21 «21. Дискредитация стратегии» ← c4 «Синхронизируем roadmap, цели и задачи» — планы команд под стратегию
{ source: 'c4', target: 'p21', weight: 2, notes: 'Подгоняет планы команд под общую стратегию.' },
// p21 «21. Дискредитация стратегии» ← c15 «Формируем культуру, ценности и архитектурные принципы» — культурное правило «локальное не ломает общее»
{ source: 'c15', target: 'p21', weight: 1, notes: 'Фиксирует правило «локальные решения не ломают курс».' },
// p21 «21. Дискредитация стратегии» ← c12 «Согласовываем общие OKR / KPI / дашборды / ключевые метрики» — связываем локальные планы с общей стратегией.
{ source: 'c12', target: 'p21', weight: 1, notes: 'Единые OKR/KPI связывают локальные планы с общей стратегией.' },
// p21 «21. Дискредитация стратегии» ← c25 «Определяем общую культуру и ценности» — культура не даёт превращать стратегию в ритуал.
{ source: 'c25', target: 'p21', weight: 1, notes: 'Культура поощряет честный разговор о стратегии.' },
// p22 «22. Постоянные отвлечения» ← c10 «Проверяем, соответствует ли «рабочая среда» ожиданиям» — главный рычаг: тихие часы и правила фокуса
{ source: 'c10', target: 'p22', weight: 3, notes: 'Вводит тихие часы и ограничения на митинги.' },
// p22 «22. Постоянные отвлечения» ← c4 «Синхронизируем roadmap, цели и задачи» — меньше «срочных» влетов задач
{ source: 'c4', target: 'p22', weight: 2, notes: 'Уменьшает число «срочных» влетов задач.' },
// p22 «22. Постоянные отвлечения» ← c5 «Приоритизируем продуктовый и технический бэклог» — снижает поток «срочных» в пользу действительно важных.
{ source: 'c5', target: 'p22', weight: 2, notes: 'Приоритизация уменьшает поток внезапных задач.' },
// p22 «22. Постоянные отвлечения» ← c11 «Выделяем и декомпозируем ключевые эпики и инициативы» — люди знают, что делать без постоянных уточнений
{ source: 'c11', target: 'p22', weight: 1, notes: 'Даёт ясный план работы без постоянных уточнений.' },
// p23 «23. Организационный долг» ← c14 «Определяем структуру команд» — главный рычаг: ликвидировать временные сочетания ролей
{ source: 'c14', target: 'p23', weight: 3, notes: 'Ликвидирует «временные» совмещения ролей.' },
// p23 «23. Организационный долг» ← c15 «Формируем культуру, ценности и архитектурные принципы» — не даём временным решениям становиться нормой.
{ source: 'c15', target: 'p23', weight: 2, notes: 'Ценности и принципы снижают орг-долг.' },
// p23 «23. Организационный долг» ← c8 «Строим стратегию найма» — добираем людей под устойчивую структуру
{ source: 'c8', target: 'p23', weight: 2, notes: 'Добирает людей под устойчивую структуру.' },
// p23 «23. Организационный долг» ← c25 «Определяем общую культуру и ценности» — поощряем починку системных багов, а не только тушение пожаров.
{ source: 'c25', target: 'p23', weight: 2, notes: 'Культура поощряет чинить системные баги.' },
// p23 «23. Организационный долг» ← c19 «Стандартизируем инженерные практики и подходы» — процессы становятся поддерживаемыми
{ source: 'c19', target: 'p23', weight: 1, notes: 'Описывает процессы так, чтобы их можно было поддерживать.' },
// p23 «23. Организационный долг» ← c22 «Разрабатываем грейды, систему оценки и ценностей» — кто чинит процессы
{ source: 'c22', target: 'p23', weight: 1, notes: 'Старшие грейды улучшают процессы.' },
// p23 «23. Организационный долг» ← c28 «Работа с ключевыми партнёрами» — перестраиваем внешние договорённости, вместо вечных костылей.
{ source: 'c28', target: 'p23', weight: 1, notes: 'Работа с партнёрами убирает внешние орг-костыли.' },
// p23 «23. Организационный долг» ← c29 «Прохождение внешних аудитов» — аудит подсвечивает и заставляет гасить орг-долг.
{ source: 'c29', target: 'p23', weight: 1, notes: 'Аудиты вскрывают накопленный орг-долг.' },
// p24 «24. Технический долг» ← c5 «Приоритизируем продуктовый и технический бэклог» — главный рычаг: техдолг в общем бэклоге
{ source: 'c5', target: 'p24', weight: 3, notes: 'Выводит техдолг в общий бэклог.' },
// p24 «24. Технический долг» ← c15 «Формируем культуру, ценности и архитектурные принципы» — архитектурные принципы снижают накопление долга.
{ source: 'c15', target: 'p24', weight: 3, notes: 'Архитектурные принципы уменьшают техдолг.' },
// p24 «24. Технический долг» ← c19 «Стандартизируем инженерные практики и подходы» — тесты, CI/CD, рефакторинг как норма
{ source: 'c19', target: 'p24', weight: 2, notes: 'Стандарты требуют тестов, рефакторинга и CI/CD.' },
// p24 «24. Технический долг» ← c7 «Расставляем веса и ищем компромиссы между «быстро / дорого / надёжно»» — не всегда жертвуем качеством ради скорости
{ source: 'c7', target: 'p24', weight: 2, notes: 'Не позволяет постоянно жертвовать качеством ради скорости.' },
// p24 «24. Технический долг» ← c29 «Прохождение внешних аудитов» — внешние техаудиты не дают забыть о долге.
{ source: 'c29', target: 'p24', weight: 1, notes: 'Внешние техаудиты подсвечивают и сокращают техдолг.' },
// p24 «24. Технический долг» ← c3 «Прикидываем unit-экономику, чтобы тех-инвестиции соответствовали ожиданиям» — сравнение цены долга и рефакторинга
{ source: 'c3', target: 'p24', weight: 1, notes: 'Сравнивает цену долга с ценой его погашения.' },
// p24 «24. Технический долг» ← c6 «Договариваемся про бюджеты» — выделяем деньги на качество и инфраструктуру.
{ source: 'c6', target: 'p24', weight: 1, notes: 'Закладываем бюджеты на качество и инфраструктуру.' },
// p25 «25. Хаос в процессах» ← c19 «Стандартизируем инженерные практики и подходы» — главный набор базовых процессов
{ source: 'c19', target: 'p25', weight: 3, notes: 'Задаёт базовый набор процессов и практик.' },
// p25 «25. Хаос в процессах» ← c14 «Определяем структуру команд» — понятно, кто где участвует в процессе
{ source: 'c14', target: 'p25', weight: 2, notes: 'Определяет, кто за какой кусок процесса отвечает.' },
// p25 «25. Хаос в процессах» ← c11 «Выделяем и декомпозируем ключевые эпики и инициативы» — процессы строятся вокруг потока ценности
{ source: 'c11', target: 'p25', weight: 2, notes: 'Строит процессы вокруг потоков ценности, а не хаоса.' },
// p25 «25. Хаос в процессах» ← c12 «Согласовываем общие OKR / KPI / дашборды / ключевые метрики» — измеряется поток, а не случайные действия
{ source: 'c12', target: 'p25', weight: 1, notes: 'Помогает измерять и улучшать поток, а не случайные метрики.' },
// p26 «26. Борьба с конфликтами» ← c25 «Определяем общую культуру и ценности» — главный рычаг: конфликт как сигнал, а не зло
{ source: 'c25', target: 'p26', weight: 3, notes: 'Закрепляет конфликты как нормальный сигнал.' },
// p26 «26. Борьба с конфликтами» ← c20 «Отслеживаем сигналы от тех, кто ежедневно создаёт ценность» — используем конфликты как сигналы о проблемах.
{ source: 'c20', target: 'p26', weight: 2, notes: 'Сигналы «с поля» помогают не терять важные конфликты.' },
// p26 «26. Борьба с конфликтами» ← c15 «Формируем культуру, ценности и архитектурные принципы» — правила, как мы спорим
{ source: 'c15', target: 'p26', weight: 2, notes: 'Определяет правила споров и принятия решений.' },
// p26 «26. Борьба с конфликтами» ← c21 «Собираем обратную связь, поддерживаем и анализируем мотивацию» — конфликты поднимаются раньше, чем взрываются
{ source: 'c21', target: 'p26', weight: 1, notes: 'Даёт канал, где можно поднять проблему до конфликта.' },
// p27 «27. Фасилитация проблем» ← c25 «Определяем общую культуру и ценности» — главный фокус: лечить причины, а не фасилитировать симптомы
{ source: 'c25', target: 'p27', weight: 3, notes: 'Смещает фокус с фасилитации на решение причины.' },
// p27 «27. Фасилитация проблем» ← c15 «Формируем культуру, ценности и архитектурные принципы» — договариваемся о том, что нормально, а что нет
{ source: 'c15', target: 'p27', weight: 2, notes: 'Помогает договориться о том, что считаем нормой.' },
// p27 «27. Фасилитация проблем» ← c21 «Собираем обратную связь, поддерживаем и анализируем мотивацию» — системно подсвечивает накопившиеся боли
{ source: 'c21', target: 'p27', weight: 2, notes: 'Регулярно поднимает накопившиеся боли.' },
// p27 «27. Фасилитация проблем» ← c19 «Стандартизируем инженерные практики и подходы» — закрепляет решения в новых практиках
{ source: 'c19', target: 'p27', weight: 2, notes: 'Фиксирует изменения в устойчивых практиках.' },
// p27 «27. Фасилитация проблем» ← c14 «Определяем структуру команд» — меняем роли и структуру, если проблема в них
{ source: 'c14', target: 'p27', weight: 1, notes: 'Поддерживает изменения корректировкой ролей и структуры.' },
// p27 «27. Фасилитация проблем» ← c1 «Синхронизируем видение рынка и стратегии» — обсуждаем проблемы на уровне системы, а не симптомов.
{ source: 'c1', target: 'p27', weight: 1, notes: 'Стратегический контекст помогает обсуждать проблемы на уровне системы.' },
// p28 «28. Агенты изменений не видят всей картины» ← c1 «Синхронизируем видение рынка и стратегии» — делимся с ними общей картой рынка
{ source: 'c1', target: 'p28', weight: 3, notes: 'Делится с агентами общей картой рынка.' },
// p28 «28. Агенты изменений не видят всей картины» ← c2 «Встраиваем технологическое видение в стратегию компании» — дают понимание техкурса
{ source: 'c2', target: 'p28', weight: 2, notes: 'Объясняет, куда движется тех-курс.' },
// p28 «28. Агенты изменений не видят всей картины» ← c4 «Синхронизируем roadmap, цели и задачи» — их инициативы привязаны к roadmap и целям
{ source: 'c4', target: 'p28', weight: 2, notes: 'Привязывает их инициативы к roadmap и целям.' },
// p28 «28. Агенты изменений не видят всей картины» ← c12 «Согласовываем общие OKR / KPI / дашборды / ключевые метрики» — дают прозрачную систему целей
{ source: 'c12', target: 'p28', weight: 2, notes: 'Даёт прозрачные цели и ключевые результаты.' },
// p28 «28. Агенты изменений не видят всей картины» ← c20 «Отслеживаем сигналы от тех, кто ежедневно создаёт ценность» — замыкает контур «сигналы → стратегия»
{ source: 'c20', target: 'p28', weight: 1, notes: 'Замыкает обратную связь от людей к стратегии.' },
// p29 «29. Простои и панические релизы» ← c7 «Расставляем веса и ищем компромиссы между «быстро / дорого / надёжно»» — главный рычаг: нельзя всегда «быстро»
{ source: 'c7', target: 'p29', weight: 3, notes: 'Определяет, где нельзя экономить на надёжности.' },
// p29 «29. Простои и панические релизы» ← c19 «Стандартизируем инженерные практики и подходы» — тесты, rollback, мониторинг
{ source: 'c19', target: 'p29', weight: 2, notes: 'Внедряет тестирование, мониторинг и rollback-стратегии.' },
// p29 «29. Простои и панические релизы» ← c5 «Приоритизируем продуктовый и технический бэклог» — reliability-задачи в бэклоге
{ source: 'c5', target: 'p29', weight: 2, notes: 'Заводит задачи по надёжности в план.' },
// p29 «29. Простои и панические релизы» ← c27 «Поддерживаем сложные сделки и коммитменты» — делаем обещания выполнимыми без паники.
{ source: 'c27', target: 'p29', weight: 1, notes: 'Реалистичные коммитменты уменьшают выкаты «в последний момент».' },
// p29 «29. Простои и панические релизы» ← c6 «Договариваемся про бюджеты» — бюджет на SRE/инфру
{ source: 'c6', target: 'p29', weight: 1, notes: 'Выделяет бюджет на SRE и инфраструктуру.' },
// p29 «29. Простои и панические релизы» ← c24 «Выстраиваем канал получения обратной связи с «передовой» от клиентов» — быстро понимаем реальное влияние инцидентов.
{ source: 'c24', target: 'p29', weight: 1, notes: 'Канал от клиентов помогает быстро ловить и чинить сбои.' },
// p29 «29. Простои и панические релизы» ← c29 «Прохождение внешних аудитов» — дисциплинируем практики релизов и инцидентов.
{ source: 'c29', target: 'p29', weight: 1, notes: 'Процессы под аудиты дисциплинируют релизы и инциденты.' },
// p30 «30. Отсутствие резервов» ← c6 «Договариваемся про бюджеты» — главный рычаг: финансовые резервы и буферы
{ source: 'c6', target: 'p30', weight: 3, notes: 'Закладывает финансовые резервы и буферы.' },
// p30 «30. Отсутствие резервов» ← c3 «Прикидываем unit-экономику, чтобы тех-инвестиции соответствовали ожиданиям» — показывает выгоду резервов
{ source: 'c3', target: 'p30', weight: 2, notes: 'Показывает выгоду резервов против простоев.' },
// p30 «30. Отсутствие резервов» ← c8 «Строим стратегию найма» — кадровые резервы
{ source: 'c8', target: 'p30', weight: 2, notes: 'Планирует небольшой запас по людям.' },
// p30 «30. Отсутствие резервов» ← c7 «Расставляем веса и ищем компромиссы между «быстро / дорого / надёжно»» — ставим надёжность и устойчивость как ценность.
{ source: 'c7', target: 'p30', weight: 2, notes: 'Компромисс «стоимость/надёжность» включает резервирование.' },
// p30 «30. Отсутствие резервов» ← c9 «Балансируем «скорость найма / стоимость поиска / риски культурных расхождений»» — сглаживание найма/размеров команды
{ source: 'c9', target: 'p30', weight: 2, notes: 'Не разгоняет найм до неконтролируемых качелей.' },
// p30 «30. Отсутствие резервов» ← c10 «Проверяем, соответствует ли «рабочая среда» ожиданиям» — лимит загрузки
{ source: 'c10', target: 'p30', weight: 1, notes: 'Следит, чтобы загрузка не была 100%+.' },
// p30 «30. Отсутствие резервов» ← c27 «Поддерживаем сложные сделки и коммитменты» — не берём обязательств, убивающих все резервы.
{ source: 'c27', target: 'p30', weight: 1, notes: 'Сложные сделки согласуются с учётом резервов и рисков.' },
// p31 «31. Митинги по утрам, работа — вечером» ← c10 «Проверяем, соответствует ли «рабочая среда» ожиданиям» — главный рычаг: рабочие окна и тихие часы
{ source: 'c10', target: 'p31', weight: 3, notes: 'Фиксирует рабочие окна и тихие часы.' },
// p31 «31. Митинги по утрам, работа — вечером» ← c4 «Синхронизируем roadmap, цели и задачи» — решения в планировании, а не в endless-митингах
{ source: 'c4', target: 'p31', weight: 2, notes: 'Переносит часть решений из митингов в планирование.' },
// p31 «31. Митинги по утрам, работа — вечером» ← c12 «Согласовываем общие OKR / KPI / дашборды / ключевые метрики» — статус из систем, а не из созвонов
{ source: 'c12', target: 'p31', weight: 1, notes: 'Делает статус видимым в системах, а не в созвонах.' },
// p32 «32. Отсутствие онбординга (особенно для тимлидов)» ← c8 «Строим стратегию найма» — главный рычаг: онбординг как часть стратегии найма
{ source: 'c8', target: 'p32', weight: 3, notes: 'Закладывает онбординг в стратегию найма.' },
// p32 «32. Отсутствие онбординга (особенно для тимлидов)» ← c22 «Разрабатываем грейды, систему оценки и ценностей» — ожидания по роли известны заранее
{ source: 'c22', target: 'p32', weight: 2, notes: 'Описывает ожидания от ролей ещё на входе.' },
// p32 «32. Отсутствие онбординга (особенно для тимлидов)» ← c21 «Собираем обратную связь, поддерживаем и анализируем мотивацию» — корректируем онбординг по фидбеку
{ source: 'c21', target: 'p32', weight: 2, notes: 'Собирает фидбек новых людей по входу в роль.' },
// p32 «32. Отсутствие онбординга (особенно для тимлидов)» ← c14 «Определяем структуру команд» — новый человек понимает своё место
{ source: 'c14', target: 'p32', weight: 2, notes: 'Показывает, где именно новый человек в системе.' },
// p33 «33. Раздавать задачи сверху» ← c21 «Собираем обратную связь, поддерживаем и анализируем мотивацию» — главный рычаг: учитывать мотивацию и сильные стороны
{ source: 'c21', target: 'p33', weight: 2, notes: 'Помогает учитывать мотивацию и сильные стороны.' },
// p33 «33. Раздавать задачи сверху» ← c12 «Согласовываем общие OKR / KPI / дашборды / ключевые метрики» — смещаем фокус с задач на результаты.
{ source: 'c12', target: 'p33', weight: 2, notes: 'OKR смещают фокус с задач на результаты.' },
// p33 «33. Раздавать задачи сверху» ← c20 «Отслеживаем сигналы от тех, кто ежедневно создаёт ценность» — кто реально тянет какую работу
{ source: 'c20', target: 'p33', weight: 2, notes: 'Подсвечивает, кто реально тянет какую работу.' },
// p33 «33. Раздавать задачи сверху» ← c11 «Выделяем и декомпозируем ключевые эпики и инициативы» — даём командам эпики, а не микроменеджмент
{ source: 'c11', target: 'p33', weight: 2, notes: 'Дает командам крупные эпики вместо мелких поручений.' },
// p33 «33. Раздавать задачи сверху» ← c25 «Определяем общую культуру и ценности» — ценность ownership vs «делай как скажут»
{ source: 'c25', target: 'p33', weight: 1, notes: 'Поддерживает культуру ownership вместо «делай как сказали».' },
// p34 «34. Использовать метрики, ревью и прочие практики для критики» ← c12 «Согласовываем общие OKR / KPI / дашборды / ключевые метрики» — главный рычаг: метрики как инструмент улучшения
{ source: 'c12', target: 'p34', weight: 3, notes: 'Определяет метрики как инструмент улучшения, а не наказания.' },
// p34 «34. Использовать метрики, ревью и прочие практики для критики» ← c22 «Разрабатываем грейды, систему оценки и ценностей» — делаем оценку инструментом развития.
{ source: 'c22', target: 'p34', weight: 3, notes: 'Система оценки ориентирована на развитие, а не наказания.' },
// p34 «34. Использовать метрики, ревью и прочие практики для критики» ← c25 «Определяем общую культуру и ценности» — уважение к ошибкам и росту
{ source: 'c25', target: 'p34', weight: 2, notes: 'Закрепляет уважительное отношение к ошибкам и росту.' },
// p34 «34. Использовать метрики, ревью и прочие практики для критики» ← c15 «Формируем культуру, ценности и архитектурные принципы» — формируем ценности, как работать с ошибками и ростом.
{ source: 'c15', target: 'p34', weight: 1, notes: 'Ценности задают стиль ревью и работы с ошибками.' },
// p34 «34. Использовать метрики, ревью и прочие практики для критики» ← c21 «Собираем обратную связь, поддерживаем и анализируем мотивацию» — ревью как диалог
{ source: 'c21', target: 'p34', weight: 1, notes: 'Строит ревью как диалог, а не разнос.' },
// p35 «35. Неправильный выбор стека технологий и подходов» ← c2 «Встраиваем технологическое видение в стратегию компании» — главный рычаг выбора стека
{ source: 'c2', target: 'p35', weight: 3, notes: 'Делает выбор стека частью техстратегии.' },
// p35 «35. Неправильный выбор стека технологий и подходов» ← c1 «Синхронизируем видение рынка и стратегии» — стек под рынок и продукт
{ source: 'c1', target: 'p35', weight: 2, notes: 'Фильтрует технологии через контекст рынка.' },
// p35 «35. Неправильный выбор стека технологий и подходов» ← c3 «Прикидываем unit-экономику, чтобы тех-инвестиции соответствовали ожиданиям» — TCO, окупаемость
{ source: 'c3', target: 'p35', weight: 2, notes: 'Считает TCO и окупаемость стека.' },
// p35 «35. Неправильный выбор стека технологий и подходов» ← c15 «Формируем культуру, ценности и архитектурные принципы» — принципы выбора технологий
{ source: 'c15', target: 'p35', weight: 1, notes: 'Фиксирует принципы выбора технологий в культуре.' },
// p35 «35. Неправильный выбор стека технологий и подходов» ← c26 «Формируем внешнее позиционирование технологий» — вынуждает выбирать стек, который можно убедительно защищать.
{ source: 'c26', target: 'p35', weight: 1, notes: 'Внешнее позиционирование требует осознанного выбора технологий.' },
// p36 «36. Массовые сокращения» ← c1 «Синхронизируем видение рынка и стратегии» — главный рычаг: стратегия вместо «резать людей»
{ source: 'c1', target: 'p36', weight: 3, notes: 'Помогает не доводить стратегию до «режем людей».' },
// p36 «36. Массовые сокращения» ← c2 «Встраиваем технологическое видение в стратегию компании» — не раздувать техпортфель
{ source: 'c2', target: 'p36', weight: 2, notes: 'Не раздувает техпортфель поверх реальных целей.' },
// p36 «36. Массовые сокращения» ← c3 «Прикидываем unit-экономику, чтобы тех-инвестиции соответствовали ожиданиям» — управлять расходами заранее
{ source: 'c3', target: 'p36', weight: 2, notes: 'Управляет расходами до кризиса сокращений.' },
// p36 «36. Массовые сокращения» ← c8 «Строим стратегию найма» — нанимать под реальные долгосрочные задачи
{ source: 'c8', target: 'p36', weight: 2, notes: 'Нанимает под реальные долгосрочные задачи.' },
// p36 «36. Массовые сокращения» ← c6 «Договариваемся про бюджеты» — управляем расходами заранее, а не через массовые сокращения.
{ source: 'c6', target: 'p36', weight: 2, notes: 'Планирование бюджетов уменьшает потребность в «рубильнике».' },
// p36 «36. Массовые сокращения» ← c9 «Балансируем «скорость найма / стоимость поиска / риски культурных расхождений»» — не уходить в оверхайринг
{ source: 'c9', target: 'p36', weight: 2, notes: 'Не гонит агрессивный найм ценой культуры.' },
// p36 «36. Массовые сокращения» ← c25 «Определяем общую культуру и ценности» — культура задаёт ограничения на практики «оптимизации» людей.
{ source: 'c25', target: 'p36', weight: 2, notes: 'Культура и ценности ограничивают практику массовых сокращений.' },
// p36 «36. Массовые сокращения» ← c23 «Публичные выступления, технический бренд, репутация компании» — как объяснить изменения и сохранить доверие
{ source: 'c23', target: 'p36', weight: 1, notes: 'Объясняет изменения так, чтобы сохранить доверие.' },
];
// --- D3-часть ----------------------------------------------------
const svg = d3.select("svg");
const width = +svg.attr("width");
const height = +svg.attr("height");
const nodes = [
...checklistItems.map(d => ({ ...d, type: 'checklist' })),
...pains.map(d => ({ ...d, type: 'pain' }))
];
// режим: 'force' или 'bipartite'
let layoutMode = 'force';
const simulation = d3.forceSimulation(nodes);
const link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr("class", "link")
.attr("stroke-width", d => 0.5 + d.weight);
const node = svg.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(nodes)
.enter().append("g")
.attr("class", d => "node " + d.type)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
)
.on("click", nodeClicked);
node.append("circle").attr("r", 10);
node.append("text")
.attr("class", "node-label")
.attr("x", 12)
.attr("y", 4)
.text(d => d.label);
node.append("title")
.text(d => {
if (d.notes && Array.isArray(d.notes)) {
return d.label + '\n- ' + d.notes.join('\n- ');
}
return d.label;
});
// --- силы --------------------------------------------------------
function applyForceLayout() {
simulation
.force("link", d3.forceLink(links).id(d => d.id).distance(80).strength(0.7))
.force("charge", d3.forceManyBody().strength(-250))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(40))
.force("x", null)
.force("y", null);
simulation.alpha(1).restart();
}
function applyBipartiteLayout() {
focusedId = null;
selectedBipartiteId = null;
node.classed("selected", false);
link.classed("highlight", false).classed("dimmed", false);
clearNotes();
const marginTop = 40;
const marginBottom = 40;
const colLeftX = width * 0.20;
const colRightX = width * 0.60;
const checklistNodes = nodes.filter(n => n.type === 'checklist');
const painNodes = nodes.filter(n => n.type === 'pain');
const availableHeight = height - marginTop - marginBottom;
const minStep = 28; // <-- вот тут задаёшь минимальное расстояние, поиграйся значением
// левая колонка
let stepLeft;
if (checklistNodes.length > 1) {
const baseStep = availableHeight / (checklistNodes.length - 1);
stepLeft = Math.max(baseStep, minStep);
} else {
stepLeft = minStep;
}
checklistNodes.forEach((n, i) => {
n.fx = colLeftX;
n.fy = marginTop + stepLeft * i;
});
// правая колонка
let stepRight;
if (painNodes.length > 1) {
const baseStep = availableHeight / (painNodes.length - 1);
stepRight = Math.max(baseStep, minStep);
} else {
stepRight = minStep;
}
painNodes.forEach((n, i) => {
n.fx = colRightX;
n.fy = marginTop + stepRight * i;
});
simulation
.force("link", d3.forceLink(links).id(d => d.id).distance(80).strength(0.8))
.force("charge", d3.forceManyBody().strength(0))
.force("center", null)
.force("collision", null)
.force("x", null)
.force("y", null);
simulation.alpha(0.5).restart();
}
// стартуем в силовом режиме
applyForceLayout();
simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node.attr("transform", d => `translate(${d.x},${d.y})`);
});
// --- adjacency + BFS (как у тебя) --------------------------------
function buildAdjacency(links) {
const adj = {};
links.forEach(l => {
const s = typeof l.source === 'object' ? l.source.id : l.source;
const t = typeof l.target === 'object' ? l.target.id : l.target;
if (!adj[s]) adj[s] = [];
if (!adj[t]) adj[t] = [];
adj[s].push(t);
adj[t].push(s);
});
return adj;
}
const adjacency = buildAdjacency(links);
function bfs(startId) {
const dist = {};
nodes.forEach(n => dist[n.id] = Infinity);
dist[startId] = 0;
const queue = [startId];
while (queue.length > 0) {
const v = queue.shift();
const dv = dist[v];
const neighbors = adjacency[v] || [];
neighbors.forEach(w => {
if (dist[w] === Infinity) {
dist[w] = dv + 1;
queue.push(w);
}
});
}
return dist;
}
// --- Фокус для силового режима -----------------------------------
// для силового режима — ID узла в фокусе
let focusedId = null;
// для двудольного режима — выбранный узел
let selectedBipartiteId = null;
// убрать все комментарии notes с узлов
function clearNotes() {
node.selectAll(".note-text").remove();
}
// простая разбивка текста на строки по длине
function wrapTextLines(text, maxChars) {
const words = text.split(/\s+/);
const lines = [];
let line = '';
words.forEach(word => {
const testLine = line ? line + ' ' + word : word;
if (testLine.length <= maxChars) {
line = testLine;
} else {
if (line) lines.push(line);
line = word;
}
});
if (line) lines.push(line);
return lines;
}
// показать notes для соседей выбранного узла (в двудольном режиме)
function showNotesForNode(clickedNode) {
clearNotes();
const clickedId = clickedNode.id;
links.forEach(l => {
const sId = typeof l.source === 'object' ? l.source.id : l.source;
const tId = typeof l.target === 'object' ? l.target.id : l.target;
if (sId !== clickedId && tId !== clickedId) return;
const neighborId = sId === clickedId ? tId : sId;
const noteText = l.notes;
if (!noteText) return;
// находим группу соответствующего узла
const neighborSelection = node.filter(nd => nd.id === neighborId);
neighborSelection.each(function(nd) {
const g = d3.select(this);
const baseX = 12;
const baseY = 16; // ниже основной подписи (label y=4)
const lines = [noteText] // wrapTextLines(noteText, 42); // ~42 символа в строке
const textEl = g.append("text")
.attr("class", "note-text")
.attr("x", baseX)
.attr("y", baseY);
lines.forEach((lineStr, idx) => {
textEl.append("tspan")
.attr("x", baseX)
.attr("dy", idx === 0 ? 0 : 10)
.text(lineStr);
});
});
});
}
function nodeClicked(event, d) {
event.stopPropagation();
if (layoutMode === 'force') {
// при переходе из двудольного режима убираем подсветки/комментарии
clearNotes();
link.classed("highlight", false).classed("dimmed", false);
selectedBipartiteId = null;
// старая логика фокуса
if (focusedId === d.id) {
focusedId = null;
clearFocus();
return;
}
focusedId = d.id;
applyFocus(d);
} else {
// двудольный режим: подсветка связей + текстовые комментарии на соседях
if (selectedBipartiteId === d.id) {
// повторный клик — сброс
selectedBipartiteId = null;
node.classed("selected", false);
link.classed("highlight", false).classed("dimmed", false);
clearNotes();
return;
}
selectedBipartiteId = d.id;
node.classed("selected", n => n.id === d.id);
link
.classed("highlight", l => {
const sId = typeof l.source === 'object' ? l.source.id : l.source;
const tId = typeof l.target === 'object' ? l.target.id : l.target;
return sId === d.id || tId === d.id;
})
.classed("dimmed", l => {
const sId = typeof l.source === 'object' ? l.source.id : l.source;
const tId = typeof l.target === 'object' ? l.target.id : l.target;
return sId !== d.id && tId !== d.id;
});
showNotesForNode(d);
}
}
function clearFocus() {
nodes.forEach(n => {
n.fx = null;
n.fy = null;
});
node.classed("selected", false);
simulation.alpha(1).restart();
}
function applyFocus(centerNode) {
const distances = bfs(centerNode.id);
const levels = new Map();
let maxFiniteDist = 0;
nodes.forEach(n => {
const d = distances[n.id];
if (Number.isFinite(d)) {
if (!levels.has(d)) levels.set(d, []);
levels.get(d).push(n);
if (d > maxFiniteDist) maxFiniteDist = d;
}
});
const infNodes = nodes.filter(n => !Number.isFinite(distances[n.id]));
if (infNodes.length > 0) {
const infLevel = maxFiniteDist + 1;
levels.set(infLevel, (levels.get(infLevel) || []).concat(infNodes));
}
const centerX = width / 2;
const centerY = height / 2;
const baseRadius = 0;
const stepRadius = 90;
const maxRadius = Math.min(width, height) / 2 - 40;
const rotationStep = Math.PI / 24;
levels.forEach((nodesOnLevel, dist) => {
if (dist === 0) {
nodesOnLevel.forEach(n => {
n.fx = centerX;
n.fy = centerY;
});
return;
}
const radius = Math.min(baseRadius + dist * stepRadius, maxRadius);
const count = nodesOnLevel.length;
const levelRotation = rotationStep * dist;
nodesOnLevel.forEach((n, i) => {
const angle = (2 * Math.PI * i) / count + levelRotation;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
n.fx = x;
n.fy = y;
});
});
node.classed("selected", n => n.id === centerNode.id);
simulation.alpha(0.7).restart();
}
// --- Drag --------------------------------------------------------
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
if (!focusedId && layoutMode === 'force') {
// в силовом режиме, без фокуса — отпускаем
d.fx = null;
d.fy = null;
}
// в двудольном режиме оставляем fx/fy, чтобы можно было «расправить» колонки руками
}
// --- Переключатель режимов --------------------------------------
const modeForceBtn = document.getElementById('mode-force');
const modeBipBtn = document.getElementById('mode-bipartite');
modeForceBtn.addEventListener('click', () => {
if (layoutMode === 'force') return;
layoutMode = 'force';
modeForceBtn.classList.add('active');
modeBipBtn.classList.remove('active');
selectedBipartiteId = null;
link.classed("highlight", false).classed("dimmed", false);
clearNotes();
clearFocus();
applyForceLayout();
});
modeBipBtn.addEventListener('click', () => {
if (layoutMode === 'bipartite') return;
layoutMode = 'bipartite';
modeBipBtn.classList.add('active');
modeForceBtn.classList.remove('active');
focusedId = null;
link.classed("highlight", false).classed("dimmed", false);
node.classed("selected", false);
clearNotes();
applyBipartiteLayout();
});
modeBipBtn.click();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment