function renderCalendarHeatmap(rows) {
const container = document.getElementById('widget-calendar-heatmap-body');
if (!container) return;
if (!rows.length) {
container.innerHTML = '<div class="empty">No commit-activity data.</div>';
return;
}
const data = rows.map(function (r) { return [r.date, r.count]; });
const counts = rows.map(function (r) { return r.count; });
let minVal = counts[0];
let maxVal = counts[0];
for (var ci = 1; ci < counts.length; ci++) {
if (counts[ci] < minVal) minVal = counts[ci];
if (counts[ci] > maxVal) maxVal = counts[ci];
}
const years = Array.from(new Set(rows.map(function (r) { return r.date.slice(0, 4); }))).sort();
const calendars = years.map(function (y, idx) {
return {
range: y,
top: 20 + idx * 110,
cellSize: ['auto', 13],
left: 70,
right: 130,
splitLine: { lineStyle: { color: getCssVar('--border') } },
itemStyle: { color: 'transparent', borderColor: getCssVar('--border') },
yearLabel: { color: getCssVar('--fg-dim'), fontSize: 11 },
monthLabel: { color: getCssVar('--fg-dim'), fontSize: 11 },
dayLabel: { color: getCssVar('--fg-dim'), fontSize: 10 },
};
});
container.style.height = (30 + years.length * 110 + 20) + 'px';
const series = years.map(function (y, idx) {
return {
type: 'heatmap',
coordinateSystem: 'calendar',
calendarIndex: idx,
data: data.filter(function (d) { return d[0].startsWith(y); }),
};
});
setChartAriaLabel(container,
'Commit-activity calendar heatmap over ' + years.length + ' year(s), ' +
data.length + ' active days');
const chart = mountEcharts(container);
chart.setOption({
tooltip: {
formatter: function (params) {
const date = params.value[0];
const n = params.value[1];
return '<b>' + escapeHtml(date) + '</b><br/>' +
n + ' commit' + (n === 1 ? '' : 's');
},
},
visualMap: {
min: minVal === maxVal ? 0 : minVal,
max: maxVal,
type: 'piecewise',
orient: 'vertical',
right: 12,
top: 'middle',
textStyle: { color: getCssVar('--fg-dim'), fontSize: 10 },
itemWidth: 12,
itemHeight: 12,
itemGap: 6,
inRange: {
color: [
token('--heatmap-1'),
token('--heatmap-2'),
token('--heatmap-3'),
token('--heatmap-4'),
token('--heatmap-5'),
],
},
},
calendar: calendars,
series: series,
});
}
function renderXRaySunburst(rows) {
const container = document.getElementById('widget-xray-sunburst-body');
if (!container) return;
if (!rows.length) {
container.innerHTML = '<div class="empty">No function-level data. ' +
'Add Tier-1 source files to the repo or enable the `spa` feature ' +
'on a build that has tree-sitter language support.</div>';
return;
}
const root = { name: 'all', children: [] };
let maxCognitive = 0;
for (var i = 0; i < rows.length; i++) {
const r = rows[i];
const segs = (r.path || '').split('/').filter(Boolean);
const top = segs[0] || '<root>';
const file = segs.slice(1).join('/') || r.path;
let topNode = root.children.find(function (c) { return c.name === top; });
if (!topNode) {
topNode = { name: top, children: [] };
root.children.push(topNode);
}
let fileNode = topNode.children.find(function (c) { return c.name === file; });
if (!fileNode) {
fileNode = { name: file, children: [], fullPath: r.path };
topNode.children.push(fileNode);
}
const cog = typeof r.cognitive === 'number' ? r.cognitive : 0;
if (cog > maxCognitive) maxCognitive = cog;
fileNode.children.push({
name: r.function || '<anonymous>',
value: cog,
cognitive: cog,
startLine: r.start_line,
endLine: r.end_line,
fullPath: r.path,
});
}
const cogScale = maxCognitive || 1;
for (var ti = 0; ti < root.children.length; ti++) {
const topNode = root.children[ti];
for (var fi = 0; fi < topNode.children.length; fi++) {
const fileNode = topNode.children[fi];
for (var fni = 0; fni < fileNode.children.length; fni++) {
const fn = fileNode.children[fni];
fn.itemStyle = { color: heatmapColor(fn.cognitive / cogScale) };
}
}
}
setChartAriaLabel(container,
'X-Ray complexity sunburst of ' + rows.length + ' functions across ' +
root.children.length + ' top-level paths, coloured by cognitive complexity');
const chart = mountEcharts(container);
chart.setOption({
tooltip: {
formatter: function (params) {
const d = params.data || {};
if (d.cognitive != null) {
return '<b>' + escapeHtml(d.fullPath) + '</b><br/>' +
'function <code>' + escapeHtml(d.name) + '</code><br/>' +
'cognitive: ' + d.cognitive.toFixed(0) + '<br/>' +
'lines ' + d.startLine + '–' + d.endLine;
}
return '<b>' + escapeHtml(d.name) + '</b>';
},
},
series: [{
type: 'sunburst',
data: root.children,
radius: ['0%', '90%'],
nodeClick: 'rootToNode',
emphasis: { focus: 'ancestor' },
levels: [
{},
{
itemStyle: { color: getCssVar('--xray-ring-1') },
label: { color: getCssVar('--xray-ring-label'), fontSize: 11 },
},
{
itemStyle: { color: getCssVar('--xray-ring-2') },
label: { color: getCssVar('--xray-ring-label'), fontSize: 10 },
},
{ label: { color: getCssVar('--xray-leaf-label'), fontSize: 9 } },
],
}],
});
chart.on('click', function (params) {
const d = params && params.data;
if (d && d.fullPath && d.cognitive != null) {
if (window._codeloreShowDetail) {
window._codeloreShowDetail(d.fullPath);
} else {
showFileDetailDrawer(d.fullPath, data);
}
}
});
}