function initHotspotColorToggles() {
const bar = document.getElementById('hotspot-color-toggles');
if (!bar) return;
const buttons = bar.querySelectorAll('button[role="tab"], button.toggle');
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function (evt) {
const mode = evt.currentTarget.getAttribute('data-mode');
for (var j = 0; j < buttons.length; j++) {
const isCurrent = (buttons[j] === evt.currentTarget);
buttons[j].classList.toggle('tab-active', isCurrent);
buttons[j].classList.toggle('active', isCurrent);
buttons[j].setAttribute('aria-selected', isCurrent ? 'true' : 'false');
}
startViewTransition(function () {
currentHotspotColorMode = mode;
if (mode !== 'bivariate') {
const bs = window.Alpine && window.Alpine.store && window.Alpine.store('brush');
if (bs && bs.cell) bs.clear();
}
renderHotspotCirclePack(data.hotspots || [], mode);
});
});
}
}
function buildFsHierarchy(rows) {
const root = { name: 'root', children: [] };
for (var i = 0; i < rows.length; i++) {
const row = rows[i];
const parts = (row.path || '').split('/').filter(Boolean);
var node = root;
var acc = '';
for (var j = 0; j < parts.length; j++) {
const segment = parts[j];
acc = acc ? (acc + '/' + segment) : segment;
var child = node.children && node.children.find(function (c) { return c.name === segment; });
if (!child) {
child = { name: segment, fullPath: acc };
if (j === parts.length - 1) {
child.metrics = {
revisions: row.revisions,
cognitive: row.cognitive,
cognitive_health: row.cognitive_health,
hotspot_score: row.hotspot_score,
};
} else {
child.children = [];
}
if (!node.children) node.children = [];
node.children.push(child);
}
node = child;
}
}
return root;
}
function heatmapColor(ratio) {
const r = 255;
const g = Math.round(255 * (1 - ratio * 0.85));
const b = Math.round(50 * (1 - ratio));
return 'rgb(' + r + ',' + g + ',' + b + ')';
}
function computePrimaryAuthorByPath(rows) {
const byPath = {};
for (var i = 0; i < rows.length; i++) {
const r = rows[i];
const cur = byPath[r.entity];
if (!cur || r.added > cur.added) {
byPath[r.entity] = { author: r.author, added: r.added };
}
}
const out = {};
Object.keys(byPath).forEach(function (p) { out[p] = byPath[p].author; });
return out;
}
function computeUniqueAuthors(entityOwnership) {
const set = new Set();
for (var i = 0; i < entityOwnership.length; i++) {
const author = entityOwnership[i].author;
if (author) set.add(author);
}
return Array.from(set).sort();
}
function arcPath(x1, y1, x2, y2, curveness) {
const mx = (x1 + x2) / 2;
const my = (y1 + y2) / 2;
const dx = x2 - x1;
const dy = y2 - y1;
const cx = mx + (-dy * curveness);
const cy = my + (dx * curveness);
return 'M ' + x1 + ',' + y1 + ' Q ' + cx + ',' + cy + ' ' + x2 + ',' + y2;
}
function buildCouplingArcs(filePath, nodePositions, couplingRows) {
if (!filePath || !nodePositions || !couplingRows || !couplingRows.length) {
return [];
}
const selfPos = nodePositions.get(filePath);
if (!selfPos) return [];
const matching = [];
for (var i = 0; i < couplingRows.length; i++) {
const r = couplingRows[i];
if (r.entity_a === filePath || r.entity_b === filePath) matching.push(r);
}
matching.sort(function (a, b) {
const pa = (typeof a.fisher_p === 'number') ? a.fisher_p : 1;
const pb = (typeof b.fisher_p === 'number') ? b.fisher_p : 1;
return pa - pb;
});
const top = matching.slice(0, 5);
const arcs = [];
for (var k = 0; k < top.length; k++) {
const row = top[k];
const peer = (row.entity_a === filePath) ? row.entity_b : row.entity_a;
const peerPos = nodePositions.get(peer);
if (!peerPos) continue;
const opacity = Math.max(0.4, 1.0 - (row.fisher_p || 0));
const lineWidth = 1 + Math.min(6, (row.degree || 0) / 8);
arcs.push({
x1: selfPos.x, y1: selfPos.y,
x2: peerPos.x, y2: peerPos.y,
opacity: opacity,
lineWidth: lineWidth,
peer: peer,
degree: row.degree,
shared: row.shared,
});
}
return arcs;
}
function updateCouplingArcs() {
if (!lastHotspotChart || !lastHotspotNodePositions) return;
if (lastHotspotChart.isDisposed && lastHotspotChart.isDisposed()) return;
const arcs = buildCouplingArcs(
selectedCouplingFile,
lastHotspotNodePositions,
data.coupling || []
);
arcData.length = 0;
for (var ai = 0; ai < arcs.length; ai++) {
const a = arcs[ai];
arcData.push({ value: [a.x1, a.y1], _arc: a });
}
const peers = new Set();
for (var pi = 0; pi < arcs.length; pi++) peers.add(arcs[pi].peer);
if (lastCirclePackData) {
for (var ci = 0; ci < lastCirclePackData.length; ci++) {
const it = lastCirclePackData[ci];
if (!it || !it._raw || !it.metrics) continue;
it._raw.couplingSelected =
!!selectedCouplingFile && it.fullPath === selectedCouplingFile;
it._raw.couplingPeer = peers.has(it.fullPath);
}
}
lastHotspotChart.setOption({
series: [
lastCirclePackData ? { data: lastCirclePackData } : {},
{ data: arcData },
],
});
}
function updateHotspotBrush() {
if (!lastHotspotChart || !lastCirclePackData) return;
if (lastHotspotChart.isDisposed && lastHotspotChart.isDisposed()) return;
for (var i = 0; i < lastCirclePackData.length; i++) {
const item = lastCirclePackData[i];
if (!item || !item._raw || !item.metrics) continue; item._raw.opacity = !brushedPaths
? 0.85
: (brushedPaths.has(item.fullPath) ? 0.95 : 0.12);
}
lastHotspotChart.setOption({ series: [{ data: lastCirclePackData }, {}] });
}
function makeAuthorPalette(authors) {
const palette = [
token('--chart-palette-1'), token('--chart-palette-2'), token('--chart-palette-3'),
token('--chart-palette-4'), token('--chart-palette-5'), token('--chart-palette-6'),
token('--chart-palette-7'), token('--chart-palette-8'), token('--chart-palette-9'),
token('--chart-palette-10'), token('--chart-palette-11'), token('--chart-palette-12'),
token('--chart-palette-13'), token('--chart-palette-14'), token('--chart-palette-15'),
];
const sorted = authors.slice().sort();
const out = {};
for (var i = 0; i < sorted.length; i++) {
out[sorted[i]] = palette[i % palette.length];
}
return out;
}
})();