diff --git a/generate_report.py b/generate_report.py
index 839a4b3..cb63956 100644
--- a/generate_report.py
+++ b/generate_report.py
@@ -225,17 +225,41 @@ HTML_TEMPLATE = """
margin-top: 4px;
}
- /* Chart container */
+ /* Chart containers */
+ .charts-wrapper {
+ display: flex;
+ gap: 20px;
+ justify-content: center;
+ flex-wrap: wrap;
+ }
.chart-container {
position: relative;
- max-width: 500px;
- margin: 0 auto;
+ flex: 1;
+ min-width: 280px;
+ max-width: 450px;
padding: 10px;
background: #FFFFFF;
border: 1px solid;
border-color: var(--mac-border-dark) var(--mac-border-light) var(--mac-border-light) var(--mac-border-dark);
box-shadow: inset 1px 1px 0 var(--mac-border-dark);
}
+ .chart-title {
+ font-family: 'Charcoal', 'Chicago', Geneva, sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+ text-align: center;
+ margin-bottom: 8px;
+ color: var(--mac-text);
+ }
+ @media (max-width: 700px) {
+ .charts-wrapper {
+ flex-direction: column;
+ align-items: center;
+ }
+ .chart-container {
+ max-width: 100%;
+ }
+ }
/* Tables styled like Mac OS 9 lists */
table {
@@ -499,8 +523,15 @@ HTML_TEMPLATE = """
@@ -598,7 +629,9 @@ HTML_TEMPLATE = """
});
let chart = null;
+ let categoriesChart = null;
const ctx = document.getElementById('playtime-chart').getContext('2d');
+ const ctxCategories = document.getElementById('categories-chart').getContext('2d');
function getSelectedServices() {
const checkboxes = filtersDiv.querySelectorAll('input[type="checkbox"]');
@@ -671,6 +704,9 @@ HTML_TEMPLATE = """
if (chart) {
chart.destroy();
}
+ if (categoriesChart) {
+ categoriesChart.destroy();
+ }
if (chartData.length === 0) {
document.getElementById('games-table').innerHTML =
@@ -718,6 +754,61 @@ HTML_TEMPLATE = """
}
});
+ // Categories chart data
+ const topCategoriesChart = categoriesData.slice(0, topN);
+ const otherCategoriesChart = categoriesData.slice(topN);
+ const categoriesChartData = topCategoriesChart.map(c => ({
+ name: c.name,
+ playtime: c.playtime
+ }));
+ if (otherCategoriesChart.length > 0) {
+ const othersPlaytime = otherCategoriesChart.reduce((sum, c) => sum + c.playtime, 0);
+ categoriesChartData.push({
+ name: `Others (${otherCategoriesChart.length} categories)`,
+ playtime: othersPlaytime
+ });
+ }
+
+ if (categoriesChartData.length > 0) {
+ categoriesChart = new Chart(ctxCategories, {
+ type: 'doughnut',
+ data: {
+ labels: categoriesChartData.map(c => c.name),
+ datasets: [{
+ data: categoriesChartData.map(c => c.playtime),
+ backgroundColor: colors.slice(0, categoriesChartData.length),
+ borderColor: '#FFFFFF',
+ borderWidth: 1
+ }]
+ },
+ options: {
+ responsive: true,
+ plugins: {
+ legend: {
+ position: 'bottom',
+ labels: {
+ color: '#000000',
+ font: {
+ family: "'Charcoal', 'Chicago', Geneva, sans-serif",
+ size: 10
+ },
+ padding: 10
+ }
+ },
+ tooltip: {
+ callbacks: {
+ label: function(context) {
+ const value = context.raw;
+ const percent = ((value / totalPlaytime) * 100).toFixed(1);
+ return ' ' + formatTime(value) + ' (' + percent + '%)';
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+
const tbody = document.getElementById('games-table');
tbody.innerHTML = '';
chartData.forEach((game, index) => {
@@ -772,17 +863,64 @@ HTML_TEMPLATE = """
if (categoriesData.length === 0) {
catTbody.innerHTML = '| No categories found |
';
} else {
- categoriesData.forEach((cat, index) => {
+ const topCategories = categoriesData.slice(0, topN);
+ const otherCategories = categoriesData.slice(topN);
+
+ topCategories.forEach((cat, index) => {
const percent = ((cat.playtime / totalPlaytime) * 100).toFixed(1);
const row = document.createElement('tr');
row.innerHTML = `
${index + 1} |
- ${cat.name} ${cat.gameCount} games |
+
+
+ ${cat.name} ${cat.gameCount} games
+ |
${formatTime(cat.playtime)} |
${percent}% |
`;
catTbody.appendChild(row);
});
+
+ if (otherCategories.length > 0) {
+ const othersPlaytime = otherCategories.reduce((sum, c) => sum + c.playtime, 0);
+ const othersPercent = ((othersPlaytime / totalPlaytime) * 100).toFixed(1);
+ const othersIndex = topCategories.length;
+
+ const othersRow = document.createElement('tr');
+ othersRow.className = 'others-row';
+ othersRow.innerHTML = `
+ ${othersIndex + 1} |
+
+
+ Others (${otherCategories.length} categories)
+ |
+ ${formatTime(othersPlaytime)} |
+ ${othersPercent}% |
+ `;
+ catTbody.appendChild(othersRow);
+
+ const detailRows = [];
+ otherCategories.forEach((otherCat, otherIndex) => {
+ const otherPercent = ((otherCat.playtime / totalPlaytime) * 100).toFixed(1);
+ const detailRow = document.createElement('tr');
+ detailRow.className = 'others-detail';
+ detailRow.innerHTML = `
+ ${othersIndex + 1}.${otherIndex + 1} |
+
+ ${otherCat.name} ${otherCat.gameCount} games
+ |
+ ${formatTime(otherCat.playtime)} |
+ ${otherPercent}% |
+ `;
+ catTbody.appendChild(detailRow);
+ detailRows.push(detailRow);
+ });
+
+ othersRow.addEventListener('click', () => {
+ othersRow.classList.toggle('expanded');
+ detailRows.forEach(dr => dr.classList.toggle('visible'));
+ });
+ }
}
}