feat(graph): iter6 — кнопки «По использованию» / «Дубли» + режим viewMode
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -76,6 +76,20 @@
|
||||
/* ── Паспорт узла (iter6) ── */
|
||||
#passport-section p { font-size: 12px; color: #eee8d5; line-height: 1.6; }
|
||||
#passport-section p .pp-k { color: #839496; }
|
||||
|
||||
/* ── Кнопки режимов в футере (iter6) ── */
|
||||
.cat-ctl-sep { width: 1px; align-self: stretch; background: #586e75; margin: 0 4px; }
|
||||
.cat-ctl {
|
||||
background: #002b36; border: 1px solid #586e75; color: #93a1a1;
|
||||
border-radius: 4px; padding: 2px 8px; font-size: 11px; cursor: pointer;
|
||||
transition: background 0.12s, box-shadow 0.12s; user-select: none;
|
||||
}
|
||||
.cat-ctl:hover { background: #0d4a5a; color: #fdf6e3; }
|
||||
.cat-ctl.active {
|
||||
background: rgba(253,246,227,0.12);
|
||||
box-shadow: inset 0 0 0 1px rgba(253,246,227,0.4);
|
||||
color: #fdf6e3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -145,6 +159,9 @@
|
||||
<div class="cat-item" data-filter-key="conflict:RED"><div class="cat-dot" style="background:#ff5f57; border:1px dashed #ff5f57"></div>🔴 Не закрыт правилом</div>
|
||||
<div class="cat-item" data-filter-key="conflict:BLACK"><div class="cat-dot" style="background:#888888; border:1px dashed #888888"></div>⚫ Возник на практике</div>
|
||||
<div class="cat-item" data-filter-key="conflict:GREEN"><div class="cat-dot" style="background:#859900; border:1px dashed #859900"></div>🟢 Закрыт правилом</div>
|
||||
<span class="cat-ctl-sep"></span>
|
||||
<button class="cat-ctl" id="cat-ctl-heat" title="Подсветить узлы по числу вызовов за 7 дней">🔥 По использованию</button>
|
||||
<button class="cat-ctl" id="cat-ctl-dup" title="Подсветить явные пары дублей (D1–D5, D7)">⧉ Дубли</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -1675,15 +1692,20 @@ function showEdgeLegend(edgeId) {
|
||||
network.on('click', params => {
|
||||
if (params.nodes.length === 1) {
|
||||
const id = params.nodes[0];
|
||||
HIGHLIGHT.setSelectedNode(id);
|
||||
HIGHLIGHT.applyHighlight();
|
||||
// iter6 — в режиме heat/dup клик открывает паспорт, но не трогает подсветку режима
|
||||
if (HIGHLIGHT.state.viewMode === null) {
|
||||
HIGHLIGHT.setSelectedNode(id);
|
||||
HIGHLIGHT.applyHighlight();
|
||||
}
|
||||
// Right panel still shows details of the clicked node (last-clicked, even after toggle-off)
|
||||
showNodeLegend(id);
|
||||
} else if (params.edges.length === 1) {
|
||||
showEdgeLegend(params.edges[0]);
|
||||
} else if (params.nodes.length === 0 && params.edges.length === 0) {
|
||||
HIGHLIGHT.setSelectedNode(null);
|
||||
HIGHLIGHT.applyHighlight();
|
||||
if (HIGHLIGHT.state.viewMode === null) {
|
||||
HIGHLIGHT.setSelectedNode(null);
|
||||
HIGHLIGHT.applyHighlight();
|
||||
}
|
||||
document.getElementById('legend-panel').classList.remove('visible');
|
||||
}
|
||||
});
|
||||
@@ -1830,8 +1852,35 @@ const HIGHLIGHT = (function setupHighlight() {
|
||||
const state = {
|
||||
selectedNode: null,
|
||||
legendFilter: new Set(),
|
||||
viewMode: null, // null | 'heat' | 'dup' — взаимоисключающие режимы (iter6)
|
||||
};
|
||||
|
||||
// ── Теплокарта использования (iter6) — 4 яруса по NODE_META[id].uses ──
|
||||
function heatOpacity(nodeId) {
|
||||
const m = NODE_META[nodeId];
|
||||
const u = m ? m.uses : null;
|
||||
if (u === null || u === undefined) return 0.5; // нет данных — нейтрально
|
||||
if (u >= 21) return 1.0; // часто
|
||||
if (u >= 6) return 0.65; // иногда
|
||||
if (u >= 1) return 0.35; // редко
|
||||
return 0.12; // простаивает (uses === 0)
|
||||
}
|
||||
// Узлы верхнего яруса теплокарты получают акцентную рамку.
|
||||
function heatBorderWidth(nodeId) {
|
||||
if (state.viewMode !== 'heat') return 2;
|
||||
const m = NODE_META[nodeId];
|
||||
const u = m ? m.uses : null;
|
||||
return (typeof u === 'number' && u >= 21) ? 4 : 2;
|
||||
}
|
||||
// Переключатель режима — toggle; включение режима гасит пофильтровую подсветку.
|
||||
function setViewMode(mode) {
|
||||
state.viewMode = (state.viewMode === mode) ? null : mode;
|
||||
if (state.viewMode !== null) {
|
||||
state.legendFilter.clear();
|
||||
state.selectedNode = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Pre-computed indices ──────────────────────────
|
||||
const NODES_BY_ID = new Map();
|
||||
const NEIGHBOURS = new Map();
|
||||
@@ -1861,6 +1910,9 @@ const HIGHLIGHT = (function setupHighlight() {
|
||||
|
||||
// ── Opacity computations ──────────────────────────
|
||||
function computeNodeOpacity(nodeId) {
|
||||
// Row 0: view-mode (iter6) — глобальная картина, поверх focus/filter
|
||||
if (state.viewMode === 'heat') return heatOpacity(nodeId);
|
||||
if (state.viewMode === 'dup') return DUP_NODE_SET.has(nodeId) ? OPACITY_FOCUS : OPACITY_DIM;
|
||||
// Row 1: focus
|
||||
if (state.selectedNode !== null) {
|
||||
if (state.selectedNode === nodeId) return OPACITY_FOCUS;
|
||||
@@ -1902,6 +1954,7 @@ const HIGHLIGHT = (function setupHighlight() {
|
||||
const nodeUpdates = NODES.map(n => ({
|
||||
id: n.id,
|
||||
opacity: computeNodeOpacity(n.id),
|
||||
borderWidth: heatBorderWidth(n.id), // 4 для верхнего яруса теплокарты, иначе 2 (iter6)
|
||||
}));
|
||||
const edgeUpdates = edgesDS.get().map(e => ({
|
||||
id: e.id,
|
||||
@@ -1925,6 +1978,7 @@ const HIGHLIGHT = (function setupHighlight() {
|
||||
function clearAll() {
|
||||
state.selectedNode = null;
|
||||
state.legendFilter.clear();
|
||||
state.viewMode = null;
|
||||
}
|
||||
|
||||
function updateLegendVisuals() {
|
||||
@@ -1934,10 +1988,22 @@ const HIGHLIGHT = (function setupHighlight() {
|
||||
if (state.legendFilter.has(key)) item.classList.add('active');
|
||||
else item.classList.remove('active');
|
||||
});
|
||||
const heatBtn = document.getElementById('cat-ctl-heat');
|
||||
const dupBtn = document.getElementById('cat-ctl-dup');
|
||||
if (heatBtn) heatBtn.classList.toggle('active', state.viewMode === 'heat');
|
||||
if (dupBtn) dupBtn.classList.toggle('active', state.viewMode === 'dup');
|
||||
}
|
||||
|
||||
// ── Legend click delegation ───────────────────────
|
||||
document.getElementById('cat-legend').addEventListener('click', e => {
|
||||
// iter6 — клик по кнопке режима heat/dup
|
||||
const ctl = e.target.closest('.cat-ctl');
|
||||
if (ctl) {
|
||||
setViewMode(ctl.id === 'cat-ctl-heat' ? 'heat' : 'dup');
|
||||
applyHighlight();
|
||||
updateLegendVisuals();
|
||||
return;
|
||||
}
|
||||
const item = e.target.closest('.cat-item');
|
||||
if (!item || !item.dataset.filterKey) return;
|
||||
toggleFilter(item.dataset.filterKey);
|
||||
|
||||
Reference in New Issue
Block a user