From 2f8cf3871486a4ef52a9138b33f2ad524fad5ea0 Mon Sep 17 00:00:00 2001 From: Ewan Croft Date: Thu, 13 Aug 2026 15:16:46 +0100 Subject: [PATCH] feat(tourmaline): add starting-letter distribution chart for artists Buckets every artist by first character (A-Z, 0-9, Other) and renders as a bar chart via the existing Chart.js BaseChart component. Ported from lastfm-stats-web's letter-chart. --- .../src/lib/analysis/letter-distribution.ts | 32 +++++++++++++++++ .../src/routes/profile/[did]/+page.svelte | 8 +++++ .../routes/profile/[did]/LetterChart.svelte | 35 +++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 packages/tourmaline/src/lib/analysis/letter-distribution.ts create mode 100644 packages/tourmaline/src/routes/profile/[did]/LetterChart.svelte diff --git a/packages/tourmaline/src/lib/analysis/letter-distribution.ts b/packages/tourmaline/src/lib/analysis/letter-distribution.ts new file mode 100644 index 0000000..2c76ef0 --- /dev/null +++ b/packages/tourmaline/src/lib/analysis/letter-distribution.ts @@ -0,0 +1,32 @@ +export interface LetterBucket { + letter: string; + count: number; +} + +/** + * Bucket names by their first alphanumeric character: A-Z, "0-9" for + * leading digits, "Other" for anything else (punctuation, emoji, non-Latin + * scripts that don't normalise to A-Z). Ported from lastfm-stats-web's + * letter-chart. + */ +export function buildLetterDistribution(names: string[]): LetterBucket[] { + const counts = new Map(); + for (const letter of "ABCDEFGHIJKLMNOPQRSTUVWXYZ") counts.set(letter, 0); + counts.set("0-9", 0); + counts.set("Other", 0); + + for (const raw of names) { + const trimmed = raw.trim(); + if (!trimmed) continue; + const first = trimmed[0].toUpperCase(); + if (first >= "0" && first <= "9") { + counts.set("0-9", (counts.get("0-9") ?? 0) + 1); + } else if (first >= "A" && first <= "Z") { + counts.set(first, (counts.get(first) ?? 0) + 1); + } else { + counts.set("Other", (counts.get("Other") ?? 0) + 1); + } + } + + return [...counts.entries()].map(([letter, count]) => ({ letter, count })); +} diff --git a/packages/tourmaline/src/routes/profile/[did]/+page.svelte b/packages/tourmaline/src/routes/profile/[did]/+page.svelte index 4b5c273..a634e74 100644 --- a/packages/tourmaline/src/routes/profile/[did]/+page.svelte +++ b/packages/tourmaline/src/routes/profile/[did]/+page.svelte @@ -37,6 +37,7 @@ import BiggestGaps from './BiggestGaps.svelte'; import CatalogueDepth from './CatalogueDepth.svelte'; import TopPeriods from './TopPeriods.svelte'; + import LetterChart from './LetterChart.svelte'; import DateRangePicker from './DateRangePicker.svelte'; import ListeningPhases from './ListeningPhases.svelte'; import StoryRecap from './StoryRecap.svelte'; @@ -541,6 +542,13 @@ + {#if profile.allArtists.length > 0} +
+

Artists by Starting Letter

+ +
+ {/if} +
diff --git a/packages/tourmaline/src/routes/profile/[did]/LetterChart.svelte b/packages/tourmaline/src/routes/profile/[did]/LetterChart.svelte new file mode 100644 index 0000000..5fef496 --- /dev/null +++ b/packages/tourmaline/src/routes/profile/[did]/LetterChart.svelte @@ -0,0 +1,35 @@ + + +
+ +
-- 2.51.2