fix(stats): show all titles in trend progress charts instead of top 7

The stacked Library-Cumulative charts silently dropped every title
outside the top 7 by total value, while the Title Visibility filter
listed all of them and promised 'Default: show everything'. Render
every series and rely on the visibility chips to declutter.
This commit is contained in:
2026-07-06 00:21:29 -07:00
parent b14f977e33
commit d618de9d2f
2 changed files with 39 additions and 7 deletions
@@ -0,0 +1,33 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { buildLineData, type PerAnimeDataPoint } from './StackedTrendChart';
function makePoints(titleCount: number): PerAnimeDataPoint[] {
const points: PerAnimeDataPoint[] = [];
for (let i = 0; i < titleCount; i += 1) {
points.push({ epochDay: 20_000 + i, animeTitle: `Title ${i}`, value: titleCount - i });
}
return points;
}
test('buildLineData keeps every title as a series instead of capping at the top 7', () => {
const { points, seriesKeys } = buildLineData(makePoints(17));
assert.equal(seriesKeys.length, 17);
assert.ok(seriesKeys.includes('Title 16'));
for (const row of points) {
for (const key of seriesKeys) {
assert.ok(key in row);
}
}
});
test('buildLineData orders series by total value descending', () => {
const { seriesKeys } = buildLineData([
{ epochDay: 20_000, animeTitle: 'Small', value: 1 },
{ epochDay: 20_000, animeTitle: 'Big', value: 10 },
{ epochDay: 20_001, animeTitle: 'Medium', value: 5 },
]);
assert.deepEqual(seriesKeys, ['Big', 'Medium', 'Small']);
});
@@ -33,19 +33,18 @@ const DEFAULT_LINE_COLORS = [
'#f4dbd6',
];
function buildLineData(raw: PerAnimeDataPoint[]) {
export function buildLineData(raw: PerAnimeDataPoint[]) {
const totalByAnime = new Map<string, number>();
for (const entry of raw) {
totalByAnime.set(entry.animeTitle, (totalByAnime.get(entry.animeTitle) ?? 0) + entry.value);
}
const sorted = [...totalByAnime.entries()].sort((a, b) => b[1] - a[1]);
const topTitles = sorted.slice(0, 7).map(([title]) => title);
const topSet = new Set(topTitles);
const seriesKeys = [...totalByAnime.entries()]
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
.map(([title]) => title);
const byDay = new Map<number, Record<string, number>>();
for (const entry of raw) {
if (!topSet.has(entry.animeTitle)) continue;
const row = byDay.get(entry.epochDay) ?? {};
row[entry.animeTitle] = (row[entry.animeTitle] ?? 0) + Math.round(entry.value * 10) / 10;
byDay.set(entry.epochDay, row);
@@ -60,13 +59,13 @@ function buildLineData(raw: PerAnimeDataPoint[]) {
day: 'numeric',
}),
};
for (const title of topTitles) {
for (const title of seriesKeys) {
row[title] = values[title] ?? 0;
}
return row;
});
return { points, seriesKeys: topTitles };
return { points, seriesKeys };
}
export function StackedTrendChart({ title, data, colorPalette }: StackedTrendChartProps) {