From 37e8e5c9f685d91383c720272d47c1c353eb2989 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Thu, 9 Jul 2026 15:53:27 -0400 Subject: [PATCH 1/4] chore: rename github -> gh --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 791fa4e..39ced2b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@gh-top-languages/lib", "version": "1.1.3", - "description": "Library for github-top-languages — chart generation, SVG output, and parameter parsing", + "description": "Library for gh-top-languages — chart generation, SVG output, and parameter parsing", "keywords": [ "svg", "charts", From b3076e8ffaf1c8662644c46c817089f8cb6b45be Mon Sep 17 00:00:00 2001 From: Masonlet Date: Thu, 9 Jul 2026 15:56:52 -0400 Subject: [PATCH 2/4] fix(legend): cycle theme colours to match segment rendering --- src/charts/geometry.ts | 7 ++++--- src/charts/{format.ts => helpers.ts} | 4 ++++ src/charts/layout.ts | 2 +- src/charts/legend.ts | 4 ++-- 4 files changed, 11 insertions(+), 6 deletions(-) rename src/charts/{format.ts => helpers.ts} (75%) diff --git a/src/charts/geometry.ts b/src/charts/geometry.ts index 1ae9fe3..336e299 100644 --- a/src/charts/geometry.ts +++ b/src/charts/geometry.ts @@ -1,5 +1,6 @@ import { FULL_CIRCLE_ANGLE } from "../constants/geometry.js"; import type { Point, Language, Geometry, GapType } from "./types.js" +import { resolveColour } from "./helpers.js"; export const polarToCartesian = ( cx: number, @@ -83,11 +84,11 @@ export const createDonutSegments = ( ); currentAngle += angle; - const fillColour = colours[i % colours.length]; + const fill = resolveColour(colours, i); const strokeAttr = stroke ? ` stroke="#000" stroke-width="0.5" stroke-linejoin="round"` - : ` stroke="${fillColour}" stroke-width="0.2"`; - return ``; + : ` stroke="${fill}" stroke-width="0.2"`; + return ``; }).join(''); if (gapType === "gap" && totalPct > 0 && totalPct < 100) { diff --git a/src/charts/format.ts b/src/charts/helpers.ts similarity index 75% rename from src/charts/format.ts rename to src/charts/helpers.ts index 105fbe1..181aab1 100644 --- a/src/charts/format.ts +++ b/src/charts/helpers.ts @@ -7,3 +7,7 @@ export function displayPct(pct: number, totalPct: number, gapType: GapType): num export function formatLegendEntry(lang: Language, totalPct: number, gapType: GapType): string { return `${lang.lang} ${displayPct(lang.pct, totalPct, gapType).toFixed(1)}%`; } + +export function resolveColour(colours: readonly string[], index: number): string { + return colours[index % colours.length]!; +} diff --git a/src/charts/layout.ts b/src/charts/layout.ts index 22643bb..956cba4 100644 --- a/src/charts/layout.ts +++ b/src/charts/layout.ts @@ -6,7 +6,7 @@ import { LEGEND_SHIFT_THRESHOLD } from "../constants/styles.js"; import type { GapType, Geometry, Language } from "./types.js"; -import { formatLegendEntry } from "./format.js"; +import { formatLegendEntry } from "./helpers.js"; const measureText = (text: string, fontSize: number): number => [...text] .reduce((sum, ch) => sum + (ARIAL_CHAR_WIDTHS[ch] ?? DEFAULT_CHAR_WIDTH), 0) * fontSize / 1000; diff --git a/src/charts/legend.ts b/src/charts/legend.ts index 610a77d..426e112 100644 --- a/src/charts/legend.ts +++ b/src/charts/legend.ts @@ -1,6 +1,6 @@ import { LEGEND_SHIFT_THRESHOLD, LEGEND_STYLES } from "../constants/styles.js"; import type { Theme, Language, GapType } from "./types.js"; -import { formatLegendEntry } from "./format.js"; +import { formatLegendEntry, resolveColour } from "./helpers.js"; export function createLegend( languages: Language[], @@ -28,7 +28,7 @@ export function createLegend( y = LEGEND_STYLES.START_Y + row * LEGEND_STYLES.ROW_HEIGHT; } - const fill = selectedTheme.colours[i]; + const fill = resolveColour(selectedTheme.colours, i); const strokeAttr = stroke ? ` stroke="#000" stroke-width="0.5" stroke-linejoin="round"` : ``; From 87b6dd7980c47723656a987ab41697c98774aeda Mon Sep 17 00:00:00 2001 From: Masonlet Date: Thu, 9 Jul 2026 16:24:37 -0400 Subject: [PATCH 3/4] feat: generate palette-matched fallback colours on overflow --- src/charts/helpers.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/charts/helpers.ts b/src/charts/helpers.ts index 181aab1..a5c6fd1 100644 --- a/src/charts/helpers.ts +++ b/src/charts/helpers.ts @@ -8,6 +8,31 @@ export function formatLegendEntry(lang: Language, totalPct: number, gapType: Gap return `${lang.lang} ${displayPct(lang.pct, totalPct, gapType).toFixed(1)}%`; } +function hexToHsl(hex: string): { s: number; l: number } | null { + const match = /^#?([0-9a-f]{6})$/i.exec(hex); + if (!match) return null; + + const r = parseInt(match[1]!.slice(0, 2), 16) / 255; + const g = parseInt(match[1]!.slice(2, 4), 16) / 255; + const b = parseInt(match[1]!.slice(4, 6), 16) / 255; + + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + const l = (max + min) / 2; + const d = max - min; + const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1)); + + return { s: s * 100, l: l * 100 }; +} + export function resolveColour(colours: readonly string[], index: number): string { - return colours[index % colours.length]!; + const base = colours[index]; + if (base) return base; + + const parsed = colours.map(hexToHsl).filter((c): c is { s: number; l: number } => c !== null); + const avg = (key: "s" | "l") => + parsed.length > 0 ? parsed.reduce((sum, c) => sum + c[key], 0) / parsed.length : 65; + + const hue = (index * 137.508) % 360; + return `hsl(${hue.toFixed(1)}, ${avg("s").toFixed(1)}%, ${avg("l").toFixed(1)}%)`; } From c93243ccf0d6d88ec5d17dc3a57744a54523e9b4 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Thu, 9 Jul 2026 16:27:15 -0400 Subject: [PATCH 4/4] chore: bump v1.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 39ced2b..5223df0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gh-top-languages/lib", - "version": "1.1.3", + "version": "1.2.0", "description": "Library for gh-top-languages — chart generation, SVG output, and parameter parsing", "keywords": [ "svg",