diff --git a/stats/src/components/trends/StackedTrendChart.test.ts b/stats/src/components/trends/StackedTrendChart.test.ts new file mode 100644 index 00000000..7902389e --- /dev/null +++ b/stats/src/components/trends/StackedTrendChart.test.ts @@ -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']); +}); diff --git a/stats/src/components/trends/StackedTrendChart.tsx b/stats/src/components/trends/StackedTrendChart.tsx index c196728d..5b3b634e 100644 --- a/stats/src/components/trends/StackedTrendChart.tsx +++ b/stats/src/components/trends/StackedTrendChart.tsx @@ -33,19 +33,18 @@ const DEFAULT_LINE_COLORS = [ '#f4dbd6', ]; -function buildLineData(raw: PerAnimeDataPoint[]) { +export function buildLineData(raw: PerAnimeDataPoint[]) { const totalByAnime = new Map(); 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>(); 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) {