diff --git a/package.json b/package.json
index 791fa4e..5223df0 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",
+ "version": "1.2.0",
+ "description": "Library for gh-top-languages — chart generation, SVG output, and parameter parsing",
"keywords": [
"svg",
"charts",
diff --git a/src/charts/format.ts b/src/charts/format.ts
deleted file mode 100644
index 105fbe1..0000000
--- a/src/charts/format.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import type { GapType, Language } from "./types.js";
-
-export function displayPct(pct: number, totalPct: number, gapType: GapType): number {
- return gapType === "adapt" && totalPct > 0 ? pct * (100 / totalPct) : pct;
-}
-
-export function formatLegendEntry(lang: Language, totalPct: number, gapType: GapType): string {
- return `${lang.lang} ${displayPct(lang.pct, totalPct, gapType).toFixed(1)}%`;
-}
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/helpers.ts b/src/charts/helpers.ts
new file mode 100644
index 0000000..a5c6fd1
--- /dev/null
+++ b/src/charts/helpers.ts
@@ -0,0 +1,38 @@
+import type { GapType, Language } from "./types.js";
+
+export function displayPct(pct: number, totalPct: number, gapType: GapType): number {
+ return gapType === "adapt" && totalPct > 0 ? pct * (100 / totalPct) : pct;
+}
+
+export function formatLegendEntry(lang: Language, totalPct: number, gapType: GapType): string {
+ 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 {
+ 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)}%)`;
+}
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"`
: ``;