Skip to content

Commit ce7ca49

Browse files
authored
refactor: consolidate donut/pie chart generators (#5)
* refactor: consolidate chart generators * refactor: convert tests to single generate file, remove missed render chart test * docs: update file descriptions * chore: remove donut/pie exports * chore: bump v1.1.1
1 parent 74abc7a commit ce7ca49

9 files changed

Lines changed: 126 additions & 240 deletions

File tree

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ Core library for github-top-languages projects — chart generation, SVG output,
1919
| `charts/geometry.js` | SVG arc path math and segment helpers |
2020
| `charts/legend.js` | Legend element generation |
2121
| `charts/layout.js` | Shared layout calculations |
22-
| `charts/generate.js` | Chart type dispatcher |
23-
| `charts/donut.js` | Donut chart segment and legend generation |
24-
| `charts/pie.js` | Pie chart segment and legend generation |
22+
| `charts/generate.js` | Chart geometry dispatch, segment and legend generation |
2523
| `render/svg.js` | SVG document rendering |
2624
| `render/error.js` | Error SVG rendering |
2725

package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@gh-top-languages/lib",
33
"description": "Library for github-top-languages — chart generation, SVG output, and parameter parsing",
44
"author": "Mason L'Etoile",
5-
"version": "1.1.0",
5+
"version": "1.1.1",
66
"license": "MIT",
77
"repository": {
88
"type": "git",
@@ -29,8 +29,6 @@
2929
"files": ["dist", "src"],
3030
"exports": {
3131
"./charts/types.js": { "types": "./dist/charts/types.d.ts", "import": "./dist/charts/types.js" },
32-
"./charts/donut.js": { "types": "./dist/charts/donut.d.ts", "import": "./dist/charts/donut.js" },
33-
"./charts/pie.js": { "types": "./dist/charts/pie.d.ts", "import": "./dist/charts/pie.js" },
3432
"./charts/geometry.js": { "types": "./dist/charts/geometry.d.ts", "import": "./dist/charts/geometry.js" },
3533
"./charts/legend.js": { "types": "./dist/charts/legend.d.ts", "import": "./dist/charts/legend.js" },
3634
"./charts/layout.js": { "types": "./dist/charts/layout.d.ts", "import": "./dist/charts/layout.js" },

src/charts/donut.ts

Lines changed: 0 additions & 40 deletions
This file was deleted.

src/charts/generate.ts

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,45 @@
1-
import type { ChartResult, Language, Theme, ChartType, GapType } from "./types.js";
2-
import { generateDonutChart } from "./donut.js";
3-
import { generatePieChart } from "./pie.js";
1+
import { DONUT_GEOMETRY, PIE_GEOMETRY } from "../constants/geometry.js";
2+
import type { ChartResult, Language, Theme, ChartType, GapType, Geometry } from "./types.js";
3+
import { computeLayout } from "./layout.js";
4+
import { createDonutSegments } from "./geometry.js";
5+
import { createLegend } from "./legend.js";
46

5-
const CHART_GENERATORS: Record<ChartType, (
6-
data: Language[],
7-
theme: Theme,
8-
gapType: GapType,
9-
stroke: boolean
10-
) => ChartResult> = {
11-
donut: generateDonutChart,
12-
pie: generatePieChart,
13-
}
7+
const GEOMETRY: Record<ChartType, Geometry> = { donut: DONUT_GEOMETRY, pie: PIE_GEOMETRY };
148

159
export function generateChartData(
16-
data: Language[],
17-
theme: Theme,
10+
data: Language[],
11+
theme: Theme,
1812
chartType: ChartType,
19-
gapType: GapType,
20-
stroke: boolean
13+
gapType: GapType,
14+
stroke: boolean
2115
): ChartResult {
22-
const generator = CHART_GENERATORS[chartType] || CHART_GENERATORS.donut;
23-
return generator(data, theme, gapType, stroke);
16+
const geometry = GEOMETRY[chartType] ?? GEOMETRY.donut;
17+
18+
const {
19+
chartX,
20+
legendStartX,
21+
columnWidth,
22+
contentWidth,
23+
contentHeight
24+
} = computeLayout(data, geometry, gapType);
25+
26+
const segments = createDonutSegments(
27+
data,
28+
chartX,
29+
geometry,
30+
[...theme.colours],
31+
stroke,
32+
gapType,
33+
theme.gap
34+
);
35+
const legend = createLegend(
36+
data,
37+
theme,
38+
legendStartX,
39+
stroke,
40+
columnWidth,
41+
gapType
42+
);
43+
44+
return { segments, legend, contentWidth, contentHeight };
2445
}

src/charts/pie.ts

Lines changed: 0 additions & 40 deletions
This file was deleted.

tests/charts/donut.test.ts

Lines changed: 0 additions & 50 deletions
This file was deleted.

tests/charts/generate.test.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { describe, it, expect, vi, afterEach } from "vitest";
2+
import type { ChartType, Theme } from "../../src/charts/types.js";
3+
import { generateChartData } from "../../src/charts/generate.js";
4+
import { createDonutSegments } from "../../src/charts/geometry.js";
5+
import { createLegend } from "../../src/charts/legend.js";
6+
7+
vi.mock("../../src/charts/geometry.js", () => ({
8+
createDonutSegments: vi.fn(() => `<path d="mockSegment"/>`)
9+
}));
10+
11+
vi.mock("../../src/charts/legend.js", () => ({
12+
createLegend: vi.fn(() => "<rect/><text>mockLegend</text>")
13+
}));
14+
15+
const mockCreateDonutSegments = vi.mocked(createDonutSegments);
16+
const mockCreateLegend = vi.mocked(createLegend);
17+
18+
const theme: Theme = { colours: ["#f00", "#0f0"], text: "#333", bg: "#fff", gap: "#000" };
19+
20+
describe("generateChartData", () => {
21+
afterEach(() => {
22+
vi.clearAllMocks();
23+
});
24+
25+
["donut" as ChartType, "pie" as ChartType].forEach((type) => {
26+
describe(`${type} context`, () => {
27+
it("returns segments, legend, and content dimensions", () => {
28+
const langs = [{ lang: "JS", pct: 100 }];
29+
const result = generateChartData(langs, theme, type, "gap", false);
30+
expect(result).toHaveProperty("segments");
31+
expect(result).toHaveProperty("legend");
32+
expect(result).toHaveProperty("contentWidth");
33+
expect(result).toHaveProperty("contentHeight");
34+
expect(mockCreateDonutSegments).toHaveBeenCalled();
35+
expect(mockCreateLegend).toHaveBeenCalled();
36+
});
37+
38+
it("computes chartX and legendStartX as numbers", () => {
39+
const langs = [{ lang: "Python", pct: 100 }];
40+
generateChartData(langs, theme, type, "gap", false);
41+
const segmentCall = mockCreateDonutSegments.mock.calls.at(-1);
42+
const legendCall = mockCreateLegend.mock.calls.at(-1) ?? [];
43+
expect(typeof segmentCall![1]).toBe("number");
44+
expect(typeof legendCall[2]).toBe("number");
45+
expect(typeof legendCall[4]).toBe("number");
46+
});
47+
48+
it("passes theme to both segments and legend", () => {
49+
const langs = [{ lang: "HTML", pct: 100 }];
50+
generateChartData(langs, theme, type, "gap", false);
51+
const segmentsCall = mockCreateDonutSegments.mock.calls.at(-1)!;
52+
const legendCall = mockCreateLegend.mock.calls.at(-1) ?? [];
53+
expect(segmentsCall[3]).toEqual(theme.colours);
54+
expect(legendCall[1]).toBe(theme);
55+
});
56+
});
57+
});
58+
59+
describe("donut", () => {
60+
it("falls back to donut geometry for unrecognized chartType", () => {
61+
const langs = [{ lang: "JS", pct: 100 }];
62+
generateChartData(langs, theme, "bigbadwolf" as ChartType, "gap", false);
63+
const call = mockCreateDonutSegments.mock.calls.at(-1)!;
64+
expect(call[2].INNER_RADIUS).not.toBe(0);
65+
});
66+
67+
it("passes non-zero INNER_RADIUS for donut", () => {
68+
const langs = [{ lang: "HTML", pct: 100 }];
69+
generateChartData(langs, theme, "donut", "gap", false);
70+
const call = mockCreateDonutSegments.mock.calls.at(-1)!;
71+
const geometry = call[2];
72+
expect(geometry.INNER_RADIUS).not.toBe(0);
73+
});
74+
});
75+
76+
describe("pie", () => {
77+
it("passes INNER_RADIUS: 0 for filled pie", () => {
78+
const langs = [{ lang: "Python", pct: 100 }];
79+
generateChartData(langs, theme, "pie", "gap", false);
80+
const call = mockCreateDonutSegments.mock.calls.at(-1)!;
81+
const geometry = call[2];
82+
expect(geometry.INNER_RADIUS).toBe(0);
83+
});
84+
});
85+
});

tests/charts/pie.test.ts

Lines changed: 0 additions & 47 deletions
This file was deleted.

tests/render/chart.test.ts

Lines changed: 0 additions & 39 deletions
This file was deleted.

0 commit comments

Comments
 (0)