@gh-top-languages/lib 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +36 -33
  3. package/dist/charts/donut.d.ts +1 -1
  4. package/dist/charts/donut.d.ts.map +1 -1
  5. package/dist/charts/donut.js +5 -6
  6. package/dist/charts/donut.js.map +1 -1
  7. package/dist/charts/geometry.js +11 -11
  8. package/dist/charts/layout.d.ts +13 -2
  9. package/dist/charts/layout.d.ts.map +1 -1
  10. package/dist/charts/layout.js +27 -8
  11. package/dist/charts/layout.js.map +1 -1
  12. package/dist/charts/legend.d.ts +1 -1
  13. package/dist/charts/legend.d.ts.map +1 -1
  14. package/dist/charts/legend.js +20 -20
  15. package/dist/charts/legend.js.map +1 -1
  16. package/dist/charts/pie.d.ts +1 -1
  17. package/dist/charts/pie.d.ts.map +1 -1
  18. package/dist/charts/pie.js +5 -6
  19. package/dist/charts/pie.js.map +1 -1
  20. package/dist/constants/styles.d.ts +4 -2
  21. package/dist/constants/styles.d.ts.map +1 -1
  22. package/dist/constants/styles.js +18 -2
  23. package/dist/constants/styles.js.map +1 -1
  24. package/dist/render/chart.d.ts +1 -1
  25. package/dist/render/chart.d.ts.map +1 -1
  26. package/dist/render/chart.js +2 -2
  27. package/dist/render/chart.js.map +1 -1
  28. package/dist/render/error.js +7 -7
  29. package/dist/render/svg.d.ts +2 -1
  30. package/dist/render/svg.d.ts.map +1 -1
  31. package/dist/render/svg.js +21 -18
  32. package/dist/render/svg.js.map +1 -1
  33. package/dist/types.d.ts +2 -0
  34. package/dist/types.d.ts.map +1 -1
  35. package/package.json +48 -48
  36. package/src/charts/donut.ts +38 -22
  37. package/src/charts/geometry.ts +88 -88
  38. package/src/charts/layout.ts +50 -19
  39. package/src/charts/legend.ts +54 -53
  40. package/src/charts/pie.ts +41 -22
  41. package/src/constants/config.ts +11 -11
  42. package/src/constants/geometry.ts +9 -9
  43. package/src/constants/styles.ts +40 -23
  44. package/src/constants/themes.ts +68 -68
  45. package/src/constants/types.ts +4 -4
  46. package/src/render/chart.ts +22 -24
  47. package/src/render/error.ts +25 -25
  48. package/src/render/svg.ts +30 -31
  49. package/src/types.ts +30 -28
  50. package/src/utils/params.ts +47 -47
  51. package/src/utils/sanitize.ts +14 -14
@@ -1,23 +1,40 @@
1
- export const TITLE_STYLES = {
2
- TEXT_Y: 30,
3
- FONT_SIZE: 24
4
- } as const;
5
-
6
- export const LEGEND_STYLES = {
7
- START_Y: 80,
8
- ROW_HEIGHT: 25,
9
- SQUARE_SIZE: 12,
10
- SQUARE_RADIUS: 2,
11
- FONT_SIZE: 11,
12
- WIDTH: 130,
13
- COLUMN_WIDTH: 105
14
- } as const;
15
-
16
- export const ERROR_STYLES = {
17
- TEXT_Y: 100,
18
- FONT_SIZE: 18,
19
- COLOUR: "#ff6b6b"
20
- } as const;
21
-
22
- export const LEGEND_SHIFT_THRESHOLD = 8;
23
- export const CHART_MARGIN_RIGHT = 20;
1
+ export const TITLE_STYLES = {
2
+ TEXT_Y: 30,
3
+ FONT_SIZE: 24
4
+ } as const;
5
+
6
+ export const LEGEND_STYLES = {
7
+ START_Y: 80,
8
+ ROW_HEIGHT: 25,
9
+ SQUARE_SIZE: 12,
10
+ SQUARE_RADIUS: 2,
11
+ FONT_SIZE: 11,
12
+ TEXT_GAP: 5,
13
+ COLUMN_GAP: 15,
14
+ } as const;
15
+
16
+ export const ERROR_STYLES = {
17
+ TEXT_Y: 100,
18
+ FONT_SIZE: 18,
19
+ COLOUR: "#ff6b6b"
20
+ } as const;
21
+
22
+ export const LEGEND_SHIFT_THRESHOLD = 8;
23
+ export const CHART_MARGIN_RIGHT = 20;
24
+
25
+ export const ARIAL_CHAR_WIDTHS: Record<string, number> = {
26
+ " ": 278, "!": 278, "#": 556, "%": 889, "+": 584, ".": 278, "-": 333,
27
+ "0": 556, "1": 556, "2": 556, "3": 556, "4": 556,
28
+ "5": 556, "6": 556, "7": 556, "8": 556, "9": 556,
29
+ A: 667, B: 667, C: 722, D: 722, E: 667, F: 611,
30
+ G: 778, H: 722, I: 278, J: 500, K: 667, L: 556,
31
+ M: 833, N: 722, O: 778, P: 667, Q: 778, R: 722,
32
+ S: 667, T: 611, U: 722, V: 667, W: 944, X: 667,
33
+ Y: 667, Z: 611,
34
+ a: 556, b: 556, c: 500, d: 556, e: 556, f: 278,
35
+ g: 556, h: 556, i: 222, j: 222, k: 500, l: 222,
36
+ m: 833, n: 556, o: 556, p: 556, q: 556, r: 333,
37
+ s: 500, t: 278, u: 556, v: 500, w: 722, x: 500,
38
+ y: 500, z: 500
39
+ } as const;
40
+ export const DEFAULT_CHAR_WIDTH = 556;
@@ -1,68 +1,68 @@
1
- export const THEMES = {
2
- default: {
3
- bg: "#0d1117",
4
- text: "#ffffff",
5
- colours: [
6
- "#A8D5Ba",
7
- "#FFD6A5",
8
- "#FFAAA6",
9
- "#D0CFCF",
10
- "#CBAACB",
11
- "#FFE156",
12
- "#96D5E9",
13
- "#F3B0C3",
14
- "#B4A7D6",
15
- "#FFB6B9",
16
- "#A3E4D7",
17
- "#F8B88B",
18
- "#C9E4CA",
19
- "#FAD7A0",
20
- "#AED6F1",
21
- "#D7BDE2"
22
- ]
23
- },
24
- light: {
25
- bg: "#ffffff",
26
- text: "#2f2f2f",
27
- colours: [
28
- "#2ecc71",
29
- "#3498db",
30
- "#e74c3c",
31
- "#f39c12",
32
- "#9b59b6",
33
- "#1abc9c",
34
- "#e67e22",
35
- "#34495e",
36
- "#16a085",
37
- "#c0392b",
38
- "#8e44ad",
39
- "#27ae60",
40
- "#d35400",
41
- "#2980b9",
42
- "#7f8c8d",
43
- "#f1c40f"
44
- ]
45
- },
46
- dark: {
47
- bg: "#1a1a1a",
48
- text: "#ccd6f6",
49
- colours: [
50
- "#ff6b6b",
51
- "#4ecdc3",
52
- "#45b7d1",
53
- "#ffa07a",
54
- "#98d8c8",
55
- "#f7dc6f",
56
- "#bb8fce",
57
- "#64ffda",
58
- "#85c1e2",
59
- "#ff8a80",
60
- "#a7ffeb",
61
- "#ffd54f",
62
- "#ea80fc",
63
- "#80d8ff",
64
- "#ffab91",
65
- "#b9f6ca"
66
- ]
67
- }
68
- } as const;
1
+ export const THEMES = {
2
+ default: {
3
+ bg: "#0d1117",
4
+ text: "#ffffff",
5
+ colours: [
6
+ "#A8D5Ba",
7
+ "#FFD6A5",
8
+ "#FFAAA6",
9
+ "#D0CFCF",
10
+ "#CBAACB",
11
+ "#FFE156",
12
+ "#96D5E9",
13
+ "#F3B0C3",
14
+ "#B4A7D6",
15
+ "#FFB6B9",
16
+ "#A3E4D7",
17
+ "#F8B88B",
18
+ "#C9E4CA",
19
+ "#FAD7A0",
20
+ "#AED6F1",
21
+ "#D7BDE2"
22
+ ]
23
+ },
24
+ light: {
25
+ bg: "#ffffff",
26
+ text: "#2f2f2f",
27
+ colours: [
28
+ "#2ecc71",
29
+ "#3498db",
30
+ "#e74c3c",
31
+ "#f39c12",
32
+ "#9b59b6",
33
+ "#1abc9c",
34
+ "#e67e22",
35
+ "#34495e",
36
+ "#16a085",
37
+ "#c0392b",
38
+ "#8e44ad",
39
+ "#27ae60",
40
+ "#d35400",
41
+ "#2980b9",
42
+ "#7f8c8d",
43
+ "#f1c40f"
44
+ ]
45
+ },
46
+ dark: {
47
+ bg: "#1a1a1a",
48
+ text: "#ccd6f6",
49
+ colours: [
50
+ "#ff6b6b",
51
+ "#4ecdc3",
52
+ "#45b7d1",
53
+ "#ffa07a",
54
+ "#98d8c8",
55
+ "#f7dc6f",
56
+ "#bb8fce",
57
+ "#64ffda",
58
+ "#85c1e2",
59
+ "#ff8a80",
60
+ "#a7ffeb",
61
+ "#ffd54f",
62
+ "#ea80fc",
63
+ "#80d8ff",
64
+ "#ffab91",
65
+ "#b9f6ca"
66
+ ]
67
+ }
68
+ } as const;
@@ -1,4 +1,4 @@
1
- export const VALID_TYPES = [
2
- "donut",
3
- "pie"
4
- ] as const;
1
+ export const VALID_TYPES = [
2
+ "donut",
3
+ "pie"
4
+ ] as const;
@@ -1,24 +1,22 @@
1
- import type { Language, Theme, ChartType, ChartResult } from "../types.js";
2
- import { generateDonutChart } from "../charts/donut.js";
3
- import { generatePieChart } from "../charts/pie.js";
4
-
5
- const CHART_GENERATORS: Record<ChartType, (
6
- data: Language[],
7
- theme: Theme,
8
- width: number,
9
- stroke: boolean
10
- ) => ChartResult> = {
11
- donut: generateDonutChart,
12
- pie: generatePieChart,
13
- }
14
-
15
- export function generateChartData(
16
- data: Language[],
17
- theme: Theme,
18
- chartType: ChartType,
19
- width: number,
20
- stroke: boolean
21
- ): ChartResult {
22
- const generator = CHART_GENERATORS[chartType] || CHART_GENERATORS.donut;
23
- return generator(data, theme, width, stroke);
24
- }
1
+ import type { Language, Theme, ChartType, ChartResult } from "../types.js";
2
+ import { generateDonutChart } from "../charts/donut.js";
3
+ import { generatePieChart } from "../charts/pie.js";
4
+
5
+ const CHART_GENERATORS: Record<ChartType, (
6
+ data: Language[],
7
+ theme: Theme,
8
+ stroke: boolean
9
+ ) => ChartResult> = {
10
+ donut: generateDonutChart,
11
+ pie: generatePieChart,
12
+ }
13
+
14
+ export function generateChartData(
15
+ data: Language[],
16
+ theme: Theme,
17
+ chartType: ChartType,
18
+ stroke: boolean
19
+ ): ChartResult {
20
+ const generator = CHART_GENERATORS[chartType] || CHART_GENERATORS.donut;
21
+ return generator(data, theme, stroke);
22
+ }
@@ -1,25 +1,25 @@
1
- import type { Theme } from "../types.js";
2
- import { THEMES } from "../constants/themes.js";
3
- import { ERROR_STYLES } from "../constants/styles.js"
4
- import { sanitize } from "../utils/sanitize.js";
5
-
6
- export function renderError(
7
- message: string,
8
- width: number,
9
- height: number,
10
- selectedTheme?: Theme
11
- ): string {
12
- const background = selectedTheme?.bg || THEMES.default.bg;
13
- const maxLen = 40;
14
- const truncated = message.length > maxLen
15
- ? sanitize(message.slice(0, maxLen)) + "..."
16
- : sanitize(message);
17
- return `
18
- <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
19
- <rect width="${width}" height="${height}" fill="${background}" rx="10"/>
20
- <text x="${width/2}" y="${ERROR_STYLES.TEXT_Y}" text-anchor="middle" fill="${ERROR_STYLES.COLOUR}" font-family="Arial" font-size="${ERROR_STYLES.FONT_SIZE}">
21
- Error: ${truncated}
22
- </text>
23
- </svg>
24
- `.trim();
25
- }
1
+ import type { Theme } from "../types.js";
2
+ import { THEMES } from "../constants/themes.js";
3
+ import { ERROR_STYLES } from "../constants/styles.js"
4
+ import { sanitize } from "../utils/sanitize.js";
5
+
6
+ export function renderError(
7
+ message: string,
8
+ width: number,
9
+ height: number,
10
+ selectedTheme?: Theme
11
+ ): string {
12
+ const background = selectedTheme?.bg || THEMES.default.bg;
13
+ const maxLen = 40;
14
+ const truncated = message.length > maxLen
15
+ ? sanitize(message.slice(0, maxLen)) + "..."
16
+ : sanitize(message);
17
+ return `
18
+ <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
19
+ <rect width="${width}" height="${height}" fill="${background}" rx="10"/>
20
+ <text x="${width/2}" y="${ERROR_STYLES.TEXT_Y}" text-anchor="middle" fill="${ERROR_STYLES.COLOUR}" font-family="Arial" font-size="${ERROR_STYLES.FONT_SIZE}">
21
+ Error: ${truncated}
22
+ </text>
23
+ </svg>
24
+ `.trim();
25
+ }
package/src/render/svg.ts CHANGED
@@ -1,31 +1,30 @@
1
- import { TITLE_STYLES } from "../constants/styles.js"
2
-
3
- export function renderSvg(
4
- width: number,
5
- height: number,
6
- background: string,
7
- segments: string,
8
- legend: string,
9
- title: string | null,
10
- textColour: string
11
- ): string {
12
- const titleElement = title ? `
13
- <text
14
- x="${width/2}"
15
- y="${TITLE_STYLES.TEXT_Y}"
16
- text-anchor="middle" fill="${textColour}"
17
- font-family="Arial" font-size="${TITLE_STYLES.FONT_SIZE}"
18
- >
19
- ${title}
20
- </text>
21
- ` : '';
22
-
23
- return `
24
- <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
25
- <rect width="${width}" height="${height}" fill="${background}" rx="10"/>
26
- ${titleElement}
27
- ${segments}
28
- ${legend}
29
- </svg>
30
- `.trim();
31
- }
1
+ import { TITLE_STYLES } from "../constants/styles.js"
2
+ import type { ChartResult } from "../types.js";
3
+
4
+ export function renderSvg(
5
+ width: number, height: number, background: string,
6
+ chart: ChartResult, title: string | null, textColour: string
7
+ ): string {
8
+ const titleElement = title ? `
9
+ <text
10
+ x="${chart.contentWidth / 2}"
11
+ y="${TITLE_STYLES.TEXT_Y}"
12
+ text-anchor="middle" fill="${textColour}"
13
+ font-family="Arial" font-size="${TITLE_STYLES.FONT_SIZE}"
14
+ >
15
+ ${title}
16
+ </text>
17
+ ` : '';
18
+
19
+ return `
20
+ <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
21
+ <rect width="${width}" height="${height}" fill="${background}" rx="10"/>
22
+ <svg width="${width}" height="${height}"
23
+ viewBox="0 0 ${chart.contentWidth} ${chart.contentHeight}"
24
+ preserveAspectRatio="xMidYMid meet">
25
+ ${titleElement}
26
+ ${chart.segments}
27
+ ${chart.legend}
28
+ </svg>
29
+ </svg>`.trim();
30
+ }
package/src/types.ts CHANGED
@@ -1,28 +1,30 @@
1
- export type Point = {
2
- x: number;
3
- y: number;
4
- };
5
-
6
- export type Language = {
7
- lang: string;
8
- pct: number;
9
- };
10
-
11
- export type Geometry = {
12
- CENTER_Y: number;
13
- INNER_RADIUS: number;
14
- OUTER_RADIUS: number;
15
- };
16
-
17
- export type ChartType = "donut" | "pie";
18
-
19
- export type ChartResult = {
20
- segments: string;
21
- legend: string;
22
- };
23
-
24
- export type Theme = {
25
- readonly colours: readonly string[];
26
- readonly text: string;
27
- readonly bg: string;
28
- };
1
+ export type Point = {
2
+ x: number;
3
+ y: number;
4
+ };
5
+
6
+ export type Language = {
7
+ lang: string;
8
+ pct: number;
9
+ };
10
+
11
+ export type Geometry = {
12
+ CENTER_Y: number;
13
+ INNER_RADIUS: number;
14
+ OUTER_RADIUS: number;
15
+ };
16
+
17
+ export type ChartType = "donut" | "pie";
18
+
19
+ export type ChartResult = {
20
+ segments: string;
21
+ legend: string;
22
+ contentWidth: number;
23
+ contentHeight: number;
24
+ };
25
+
26
+ export type Theme = {
27
+ readonly colours: readonly string[];
28
+ readonly text: string;
29
+ readonly bg: string;
30
+ };
@@ -1,47 +1,47 @@
1
- import type { ChartType } from "../types.js";
2
- import { sanitize } from "./sanitize.js";
3
- import { VALID_TYPES } from "../constants/types.js";
4
- import { DEFAULT_CONFIG } from "../constants/config.js";
5
- import { THEMES } from "../constants/themes.js";
6
-
7
- export type QueryParams = Record<string, string | undefined>;
8
-
9
- const parseIntSafe = (
10
- val: string | undefined,
11
- fallback: number
12
- ): number => {
13
- const parsed = Number.parseInt(val ?? '', 10);
14
- return Number.isNaN(parsed) ? fallback : parsed;
15
- }
16
-
17
- const normalizeHex = (val: string) => `#${val.replace(/^#/, '')}`;
18
-
19
- export function parseQueryParams(query: QueryParams) {
20
- const baseTheme = THEMES[query["theme"] as keyof typeof THEMES] ?? THEMES.default;
21
- const count = parseIntSafe(query["count"], DEFAULT_CONFIG.COUNT);
22
-
23
- const customColours: string[] = [...baseTheme.colours];
24
- for (let i = 1; i <= DEFAULT_CONFIG.MAX_COUNT; i++) {
25
- const colourVal = query[`c${i}`];
26
- if(colourVal) customColours[i - 1] = normalizeHex(colourVal);
27
- }
28
-
29
- const typeParam = query["type"] as ChartType | undefined;
30
- const chartType: ChartType = VALID_TYPES.some(t => t === typeParam) ? typeParam! : "donut";
31
-
32
- return {
33
- chartType,
34
- chartTitle: query["hide_title"] === "true" ? '' : sanitize(query["title"] ?? DEFAULT_CONFIG.TITLE),
35
- width: Math.max(parseIntSafe(query["width"], DEFAULT_CONFIG.WIDTH), DEFAULT_CONFIG.MIN_WIDTH ),
36
- height: Math.max(parseIntSafe(query["height"], DEFAULT_CONFIG.HEIGHT), DEFAULT_CONFIG.MIN_HEIGHT),
37
- count: Math.min(Math.max(count, 1), DEFAULT_CONFIG.MAX_COUNT),
38
- selectedTheme: {
39
- bg: THEMES[query["bg"] as keyof typeof THEMES]?.bg ?? (query["bg"] ? normalizeHex(query["bg"]) : baseTheme.bg),
40
- text: query["text"] ? normalizeHex(query["text"]) : baseTheme.text,
41
- colours: customColours
42
- },
43
- stroke: query["stroke"] === "true",
44
- useTestData: query["test"] === "true",
45
- errorTest: sanitize(query["error"] ?? '')
46
- }
47
- }
1
+ import type { ChartType } from "../types.js";
2
+ import { sanitize } from "./sanitize.js";
3
+ import { VALID_TYPES } from "../constants/types.js";
4
+ import { DEFAULT_CONFIG } from "../constants/config.js";
5
+ import { THEMES } from "../constants/themes.js";
6
+
7
+ export type QueryParams = Record<string, string | undefined>;
8
+
9
+ const parseIntSafe = (
10
+ val: string | undefined,
11
+ fallback: number
12
+ ): number => {
13
+ const parsed = Number.parseInt(val ?? '', 10);
14
+ return Number.isNaN(parsed) ? fallback : parsed;
15
+ }
16
+
17
+ const normalizeHex = (val: string) => `#${val.replace(/^#/, '')}`;
18
+
19
+ export function parseQueryParams(query: QueryParams) {
20
+ const baseTheme = THEMES[query["theme"] as keyof typeof THEMES] ?? THEMES.default;
21
+ const count = parseIntSafe(query["count"], DEFAULT_CONFIG.COUNT);
22
+
23
+ const customColours: string[] = [...baseTheme.colours];
24
+ for (let i = 1; i <= DEFAULT_CONFIG.MAX_COUNT; i++) {
25
+ const colourVal = query[`c${i}`];
26
+ if(colourVal) customColours[i - 1] = normalizeHex(colourVal);
27
+ }
28
+
29
+ const typeParam = query["type"] as ChartType | undefined;
30
+ const chartType: ChartType = VALID_TYPES.some(t => t === typeParam) ? typeParam! : "donut";
31
+
32
+ return {
33
+ chartType,
34
+ chartTitle: query["hide_title"] === "true" ? '' : sanitize(query["title"] ?? DEFAULT_CONFIG.TITLE),
35
+ width: Math.max(parseIntSafe(query["width"], DEFAULT_CONFIG.WIDTH), DEFAULT_CONFIG.MIN_WIDTH ),
36
+ height: Math.max(parseIntSafe(query["height"], DEFAULT_CONFIG.HEIGHT), DEFAULT_CONFIG.MIN_HEIGHT),
37
+ count: Math.min(Math.max(count, 1), DEFAULT_CONFIG.MAX_COUNT),
38
+ selectedTheme: {
39
+ bg: THEMES[query["bg"] as keyof typeof THEMES]?.bg ?? (query["bg"] ? normalizeHex(query["bg"]) : baseTheme.bg),
40
+ text: query["text"] ? normalizeHex(query["text"]) : baseTheme.text,
41
+ colours: customColours
42
+ },
43
+ stroke: query["stroke"] === "true",
44
+ useTestData: query["test"] === "true",
45
+ errorTest: sanitize(query["error"] ?? '')
46
+ }
47
+ }
@@ -1,14 +1,14 @@
1
- const ESCAPE_MAP = {
2
- '<': '&lt;',
3
- '>': '&gt;',
4
- '&': '&amp;',
5
- '"': '&quot;',
6
- "'": '&#39;',
7
- } as const;
8
-
9
- export const sanitize = (str: unknown): string => {
10
- if (typeof str !== "string") return '';
11
- return str.replace(/[<>&"']/g, (m: string): string =>
12
- ESCAPE_MAP[m as keyof typeof ESCAPE_MAP]
13
- );
14
- };
1
+ const ESCAPE_MAP = {
2
+ '<': '&lt;',
3
+ '>': '&gt;',
4
+ '&': '&amp;',
5
+ '"': '&quot;',
6
+ "'": '&#39;',
7
+ } as const;
8
+
9
+ export const sanitize = (str: unknown): string => {
10
+ if (typeof str !== "string") return '';
11
+ return str.replace(/[<>&"']/g, (m: string): string =>
12
+ ESCAPE_MAP[m as keyof typeof ESCAPE_MAP]
13
+ );
14
+ };