@xrn07/figure-renderer 0.1.0

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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -0
  3. package/dist/parse.d.ts +11 -0
  4. package/dist/parse.d.ts.map +1 -0
  5. package/dist/parse.js +39 -0
  6. package/dist/react.d.ts +14 -0
  7. package/dist/react.d.ts.map +1 -0
  8. package/dist/react.js +21 -0
  9. package/dist/render.d.ts +10 -0
  10. package/dist/render.d.ts.map +1 -0
  11. package/dist/render.js +41 -0
  12. package/dist/renderers/bioTable.d.ts +3 -0
  13. package/dist/renderers/bioTable.d.ts.map +1 -0
  14. package/dist/renderers/bioTable.js +28 -0
  15. package/dist/renderers/circuit.d.ts +3 -0
  16. package/dist/renderers/circuit.d.ts.map +1 -0
  17. package/dist/renderers/circuit.js +237 -0
  18. package/dist/renderers/coordinatePlane.d.ts +3 -0
  19. package/dist/renderers/coordinatePlane.d.ts.map +1 -0
  20. package/dist/renderers/coordinatePlane.js +142 -0
  21. package/dist/renderers/forceDiagram.d.ts +3 -0
  22. package/dist/renderers/forceDiagram.d.ts.map +1 -0
  23. package/dist/renderers/forceDiagram.js +130 -0
  24. package/dist/renderers/geometry.d.ts +3 -0
  25. package/dist/renderers/geometry.d.ts.map +1 -0
  26. package/dist/renderers/geometry.js +173 -0
  27. package/dist/renderers/graph.d.ts +3 -0
  28. package/dist/renderers/graph.d.ts.map +1 -0
  29. package/dist/renderers/graph.js +137 -0
  30. package/dist/renderers/numberLine.d.ts +3 -0
  31. package/dist/renderers/numberLine.d.ts.map +1 -0
  32. package/dist/renderers/numberLine.js +98 -0
  33. package/dist/renderers/rayDiagram.d.ts +3 -0
  34. package/dist/renderers/rayDiagram.d.ts.map +1 -0
  35. package/dist/renderers/rayDiagram.js +181 -0
  36. package/dist/renderers/venn.d.ts +3 -0
  37. package/dist/renderers/venn.d.ts.map +1 -0
  38. package/dist/renderers/venn.js +60 -0
  39. package/dist/types.d.ts +225 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +3 -0
  42. package/dist/utils/arrowMarker.d.ts +13 -0
  43. package/dist/utils/arrowMarker.d.ts.map +1 -0
  44. package/dist/utils/arrowMarker.js +48 -0
  45. package/dist/utils/coordTransform.d.ts +29 -0
  46. package/dist/utils/coordTransform.d.ts.map +1 -0
  47. package/dist/utils/coordTransform.js +38 -0
  48. package/dist/utils/gridLines.d.ts +23 -0
  49. package/dist/utils/gridLines.d.ts.map +1 -0
  50. package/dist/utils/gridLines.js +124 -0
  51. package/dist/utils/label.d.ts +30 -0
  52. package/dist/utils/label.d.ts.map +1 -0
  53. package/dist/utils/label.js +82 -0
  54. package/dist/utils/path.d.ts +21 -0
  55. package/dist/utils/path.d.ts.map +1 -0
  56. package/dist/utils/path.js +72 -0
  57. package/dist/utils/shapes.d.ts +29 -0
  58. package/dist/utils/shapes.d.ts.map +1 -0
  59. package/dist/utils/shapes.js +78 -0
  60. package/dist/utils/svgTag.d.ts +11 -0
  61. package/dist/utils/svgTag.d.ts.map +1 -0
  62. package/dist/utils/svgTag.js +29 -0
  63. package/package.json +70 -0
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Transform data coordinates to SVG coordinates
3
+ * Maps data space to SVG pixel space
4
+ */
5
+ export interface TransformOptions {
6
+ xRange: [number, number];
7
+ yRange: [number, number];
8
+ width: number;
9
+ height: number;
10
+ padding?: number;
11
+ }
12
+ /**
13
+ * Create coordinate transformation function
14
+ */
15
+ export declare function createCoordTransform(options: TransformOptions): {
16
+ /**
17
+ * Transform data x to SVG x
18
+ */
19
+ x: (dataX: number) => number;
20
+ /**
21
+ * Transform data y to SVG y (inverted, since SVG y goes down)
22
+ */
23
+ y: (dataY: number) => number;
24
+ };
25
+ /**
26
+ * Quick transform for single coordinate
27
+ */
28
+ export declare function transformCoord(value: number, range: [number, number], size: number, padding?: number, invert?: boolean): number;
29
+ //# sourceMappingURL=coordTransform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordTransform.d.ts","sourceRoot":"","sources":["../../src/utils/coordTransform.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,gBAAgB;IAY1D;;OAEG;eACQ,MAAM;IAIjB;;OAEG;eACQ,MAAM;EAIpB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EACvB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,MAAW,EACpB,MAAM,GAAE,OAAe,GACtB,MAAM,CAQR"}
@@ -0,0 +1,38 @@
1
+ // Coordinate transformation utilities
2
+ /**
3
+ * Create coordinate transformation function
4
+ */
5
+ export function createCoordTransform(options) {
6
+ const { xRange, yRange, width, height, padding = 40 } = options;
7
+ const xMin = xRange[0];
8
+ const xMax = xRange[1];
9
+ const yMin = yRange[0];
10
+ const yMax = yRange[1];
11
+ const usableWidth = width - 2 * padding;
12
+ const usableHeight = height - 2 * padding;
13
+ return {
14
+ /**
15
+ * Transform data x to SVG x
16
+ */
17
+ x: (dataX) => {
18
+ return padding + ((dataX - xMin) / (xMax - xMin)) * usableWidth;
19
+ },
20
+ /**
21
+ * Transform data y to SVG y (inverted, since SVG y goes down)
22
+ */
23
+ y: (dataY) => {
24
+ return height - padding - ((dataY - yMin) / (yMax - yMin)) * usableHeight;
25
+ },
26
+ };
27
+ }
28
+ /**
29
+ * Quick transform for single coordinate
30
+ */
31
+ export function transformCoord(value, range, size, padding = 40, invert = false) {
32
+ const [min, max] = range;
33
+ const usableSize = size - 2 * padding;
34
+ if (invert) {
35
+ return size - padding - ((value - min) / (max - min)) * usableSize;
36
+ }
37
+ return padding + ((value - min) / (max - min)) * usableSize;
38
+ }
@@ -0,0 +1,23 @@
1
+ interface GridOptions {
2
+ xRange: [number, number];
3
+ yRange: [number, number];
4
+ width: number;
5
+ height: number;
6
+ xSteps?: number;
7
+ ySteps?: number;
8
+ stroke?: string;
9
+ strokeWidth?: number;
10
+ }
11
+ /**
12
+ * Generate grid lines for coordinate systems
13
+ */
14
+ export declare function gridLines(options: GridOptions): string;
15
+ /**
16
+ * Generate axes with labels
17
+ */
18
+ export declare function axes(options: GridOptions & {
19
+ showLabels?: boolean;
20
+ labelStep?: number;
21
+ }): string;
22
+ export {};
23
+ //# sourceMappingURL=gridLines.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gridLines.d.ts","sourceRoot":"","sources":["../../src/utils/gridLines.ts"],"names":[],"mappings":"AAKA,UAAU,WAAW;IACnB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAgDtD;AAED;;GAEG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAkEhG"}
@@ -0,0 +1,124 @@
1
+ // Grid line generation utility
2
+ import { svgTag } from './svgTag';
3
+ import { createCoordTransform } from './coordTransform';
4
+ /**
5
+ * Generate grid lines for coordinate systems
6
+ */
7
+ export function gridLines(options) {
8
+ const { xRange, yRange, width, height, xSteps = 10, ySteps = 10, stroke = '#e0e0e0', strokeWidth = 1, } = options;
9
+ const transform = createCoordTransform({ xRange, yRange, width, height });
10
+ const xDelta = (xRange[1] - xRange[0]) / xSteps;
11
+ const yDelta = (yRange[1] - yRange[0]) / ySteps;
12
+ const lines = [];
13
+ // Vertical lines
14
+ for (let i = 0; i <= xSteps; i++) {
15
+ const x = xRange[0] + i * xDelta;
16
+ const svgX = transform.x(x);
17
+ lines.push(svgTag('line', {
18
+ x1: svgX,
19
+ y1: transform.y(yRange[0]),
20
+ x2: svgX,
21
+ y2: transform.y(yRange[1]),
22
+ stroke,
23
+ 'stroke-width': strokeWidth,
24
+ }, ''));
25
+ }
26
+ // Horizontal lines
27
+ for (let i = 0; i <= ySteps; i++) {
28
+ const y = yRange[0] + i * yDelta;
29
+ const svgY = transform.y(y);
30
+ lines.push(svgTag('line', {
31
+ x1: transform.x(xRange[0]),
32
+ y1: svgY,
33
+ x2: transform.x(xRange[1]),
34
+ y2: svgY,
35
+ stroke,
36
+ 'stroke-width': strokeWidth,
37
+ }, ''));
38
+ }
39
+ return svgTag('g', { class: 'grid' }, lines.join(''));
40
+ }
41
+ /**
42
+ * Generate axes with labels
43
+ */
44
+ export function axes(options) {
45
+ const { xRange, yRange, width, height, showLabels = true, labelStep } = options;
46
+ const transform = createCoordTransform({ xRange, yRange, width, height });
47
+ const xZero = transform.x(0);
48
+ const yZero = transform.y(0);
49
+ const elements = [];
50
+ // X axis (draw at y=0 if it's in range, otherwise at bottom)
51
+ const xAxisY = yRange[0] <= 0 && yRange[1] >= 0 ? yZero : height - 40;
52
+ elements.push(svgTag('line', {
53
+ x1: 40,
54
+ y1: xAxisY,
55
+ x2: width - 40,
56
+ y2: xAxisY,
57
+ stroke: '#000',
58
+ 'stroke-width': 2,
59
+ 'marker-end': 'url(#arrow-default)',
60
+ }, ''));
61
+ // Y axis (draw at x=0 if it's in range, otherwise at left)
62
+ const yAxisX = xRange[0] <= 0 && xRange[1] >= 0 ? xZero : 40;
63
+ elements.push(svgTag('line', {
64
+ x1: yAxisX,
65
+ y1: height - 40,
66
+ x2: yAxisX,
67
+ y2: 40,
68
+ stroke: '#000',
69
+ 'stroke-width': 2,
70
+ 'marker-end': 'url(#arrow-default)',
71
+ }, ''));
72
+ // Add axis labels
73
+ if (showLabels) {
74
+ const xLabelStep = labelStep || calculateNiceStep(xRange[0], xRange[1]);
75
+ const yLabelStep = labelStep || calculateNiceStep(yRange[0], yRange[1]);
76
+ // X-axis labels
77
+ for (let x = Math.ceil(xRange[0] / xLabelStep) * xLabelStep; x <= xRange[1]; x += xLabelStep) {
78
+ if (Math.abs(x) < 0.001)
79
+ continue; // Skip origin
80
+ const svgX = transform.x(x);
81
+ elements.push(svgTag('text', {
82
+ x: svgX,
83
+ y: xAxisY + 15,
84
+ 'text-anchor': 'middle',
85
+ 'font-size': 12,
86
+ fill: '#333',
87
+ }, String(x)));
88
+ }
89
+ // Y-axis labels
90
+ for (let y = Math.ceil(yRange[0] / yLabelStep) * yLabelStep; y <= yRange[1]; y += yLabelStep) {
91
+ if (Math.abs(y) < 0.001)
92
+ continue; // Skip origin
93
+ const svgY = transform.y(y);
94
+ elements.push(svgTag('text', {
95
+ x: yAxisX - 10,
96
+ y: svgY + 4,
97
+ 'text-anchor': 'end',
98
+ 'font-size': 12,
99
+ fill: '#333',
100
+ }, String(y)));
101
+ }
102
+ }
103
+ return svgTag('g', { class: 'axes' }, elements.join(''));
104
+ }
105
+ /**
106
+ * Calculate a nice step size for axis labels
107
+ */
108
+ function calculateNiceStep(min, max) {
109
+ const range = max - min;
110
+ const roughStep = range / 5;
111
+ // Nice steps: 1, 2, 5, 10, 0.5, 0.2, 0.1, etc.
112
+ const magnitude = Math.pow(10, Math.floor(Math.log10(roughStep)));
113
+ const normalized = roughStep / magnitude;
114
+ let niceNormalized;
115
+ if (normalized < 1.5)
116
+ niceNormalized = 1;
117
+ else if (normalized < 3)
118
+ niceNormalized = 2;
119
+ else if (normalized < 7)
120
+ niceNormalized = 5;
121
+ else
122
+ niceNormalized = 10;
123
+ return niceNormalized * magnitude;
124
+ }
@@ -0,0 +1,30 @@
1
+ interface LabelOptions {
2
+ x: number;
3
+ y: number;
4
+ text: string;
5
+ align?: 'left' | 'center' | 'right';
6
+ baseline?: 'top' | 'middle' | 'bottom' | 'auto';
7
+ color?: string;
8
+ fontSize?: number;
9
+ fontWeight?: 'normal' | 'bold';
10
+ rotation?: number;
11
+ }
12
+ /**
13
+ * Generate positioned text element for labels
14
+ */
15
+ export declare function label(options: LabelOptions): string;
16
+ /**
17
+ * Create a text label with background for better visibility
18
+ */
19
+ export declare function labeledBox(options: LabelOptions & {
20
+ backgroundColor?: string;
21
+ padding?: number;
22
+ }): string;
23
+ /**
24
+ * Generate multi-line label
25
+ */
26
+ export declare function multilineLabel(options: Omit<LabelOptions, 'text'> & {
27
+ lines: string[];
28
+ }): string;
29
+ export {};
30
+ //# sourceMappingURL=label.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../../src/utils/label.ts"],"names":[],"mappings":"AAIA,UAAU,YAAY;IACpB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CA4BnD;AAYD;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG;IAAE,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAoDzG;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,MAAM,CAOhG"}
@@ -0,0 +1,82 @@
1
+ // Label positioning utility
2
+ import { svgTag } from './svgTag';
3
+ /**
4
+ * Generate positioned text element for labels
5
+ */
6
+ export function label(options) {
7
+ const { x, y, text, align = 'center', baseline = 'middle', color = '#000', fontSize = 14, fontWeight = 'normal', rotation = 0, } = options;
8
+ const attrs = {
9
+ x: String(x),
10
+ y: String(y),
11
+ 'text-anchor': align,
12
+ 'dominant-baseline': baseline,
13
+ fill: color,
14
+ 'font-size': String(fontSize),
15
+ 'font-weight': fontWeight,
16
+ transform: rotation ? `rotate(${rotation} ${x} ${y})` : undefined,
17
+ };
18
+ return `<text ${Object.entries(attrs)
19
+ .filter(([_, v]) => v !== undefined)
20
+ .map(([k, v]) => `${k}="${v}"`)
21
+ .join(' ')}>${escapeText(text)}</text>`;
22
+ }
23
+ /**
24
+ * Escape text content for SVG
25
+ */
26
+ function escapeText(text) {
27
+ return text
28
+ .replace(/&/g, '&amp;')
29
+ .replace(/</g, '&lt;')
30
+ .replace(/>/g, '&gt;');
31
+ }
32
+ /**
33
+ * Create a text label with background for better visibility
34
+ */
35
+ export function labeledBox(options) {
36
+ const { x, y, text, backgroundColor = 'white', padding = 4, fontSize = 14, align = 'center', ...rest } = options;
37
+ // Estimate text width (rough approximation)
38
+ const textWidth = text.length * fontSize * 0.6;
39
+ const textHeight = fontSize;
40
+ // Calculate box dimensions
41
+ let boxX, boxY, boxWidth, boxHeight;
42
+ switch (align) {
43
+ case 'left':
44
+ boxX = x - padding;
45
+ boxY = y - textHeight / 2 - padding;
46
+ boxWidth = textWidth + padding * 2;
47
+ boxHeight = textHeight + padding * 2;
48
+ break;
49
+ case 'right':
50
+ boxX = x - textWidth - padding;
51
+ boxY = y - textHeight / 2 - padding;
52
+ boxWidth = textWidth + padding * 2;
53
+ boxHeight = textHeight + padding * 2;
54
+ break;
55
+ default: // center
56
+ boxX = x - textWidth / 2 - padding;
57
+ boxY = y - textHeight / 2 - padding;
58
+ boxWidth = textWidth + padding * 2;
59
+ boxHeight = textHeight + padding * 2;
60
+ }
61
+ const boxSvg = svgTag('rect', {
62
+ x: boxX,
63
+ y: boxY,
64
+ width: boxWidth,
65
+ height: boxHeight,
66
+ fill: backgroundColor,
67
+ stroke: 'none',
68
+ rx: 2,
69
+ }, '');
70
+ const textSvg = label({ x, y, text, fontSize, align, ...rest });
71
+ return boxSvg + textSvg;
72
+ }
73
+ /**
74
+ * Generate multi-line label
75
+ */
76
+ export function multilineLabel(options) {
77
+ const { x, y, lines, fontSize = 14, ...rest } = options;
78
+ const lineHeight = fontSize * 1.2;
79
+ return lines
80
+ .map((line, i) => label({ x, y: y + i * lineHeight, text: line, fontSize, ...rest }))
81
+ .join('');
82
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Generate path data string from array of points
3
+ */
4
+ export declare function pathFromPoints(points: [number, number][], closed?: boolean): string;
5
+ /**
6
+ * Generate path data for a circle
7
+ */
8
+ export declare function circlePath(cx: number, cy: number, r: number): string;
9
+ /**
10
+ * Generate path data for an arc
11
+ */
12
+ export declare function arcPath(cx: number, cy: number, r: number, startAngle: number, endAngle: number): string;
13
+ /**
14
+ * Generate path data for an arrow line
15
+ */
16
+ export declare function arrowLine(x1: number, y1: number, x2: number, y2: number, arrowSize?: number): string;
17
+ /**
18
+ * Generate path data for dashed line
19
+ */
20
+ export declare function dashedLine(x1: number, y1: number, x2: number, y2: number, dashLength?: number, gapLength?: number): string;
21
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAE,OAAe,GAAG,MAAM,CAgB1F;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAIpE;AAED;;GAEG;AACH,wBAAgB,OAAO,CACrB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,CAAC,EAAE,MAAM,EACT,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,MAAM,CAYR;AAED;;GAEG;AACH,wBAAgB,SAAS,CACvB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,SAAS,GAAE,MAAW,GACrB,MAAM,CAUR;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,UAAU,GAAE,MAAW,EACvB,SAAS,GAAE,MAAU,GACpB,MAAM,CAuBR"}
@@ -0,0 +1,72 @@
1
+ // SVG path generation utilities
2
+ /**
3
+ * Generate path data string from array of points
4
+ */
5
+ export function pathFromPoints(points, closed = false) {
6
+ if (points.length === 0)
7
+ return '';
8
+ const [startX, startY] = points[0];
9
+ let d = `M ${startX} ${startY}`;
10
+ for (let i = 1; i < points.length; i++) {
11
+ const [x, y] = points[i];
12
+ d += ` L ${x} ${y}`;
13
+ }
14
+ if (closed) {
15
+ d += ' Z';
16
+ }
17
+ return d;
18
+ }
19
+ /**
20
+ * Generate path data for a circle
21
+ */
22
+ export function circlePath(cx, cy, r) {
23
+ return `M ${cx - r} ${cy}
24
+ a ${r} ${r} 0 1 0 ${r * 2} 0
25
+ a ${r} ${r} 0 1 0 -${r * 2} 0`;
26
+ }
27
+ /**
28
+ * Generate path data for an arc
29
+ */
30
+ export function arcPath(cx, cy, r, startAngle, endAngle) {
31
+ const startRad = (startAngle * Math.PI) / 180;
32
+ const endRad = (endAngle * Math.PI) / 180;
33
+ const x1 = cx + r * Math.cos(startRad);
34
+ const y1 = cy - r * Math.sin(startRad);
35
+ const x2 = cx + r * Math.cos(endRad);
36
+ const y2 = cy - r * Math.sin(endRad);
37
+ const largeArc = endAngle - startAngle > 180 ? 1 : 0;
38
+ return `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${largeArc} 1 ${x2} ${y2} Z`;
39
+ }
40
+ /**
41
+ * Generate path data for an arrow line
42
+ */
43
+ export function arrowLine(x1, y1, x2, y2, arrowSize = 10) {
44
+ const angle = Math.atan2(y2 - y1, x2 - x1);
45
+ // Arrow head points
46
+ const ax1 = x2 - arrowSize * Math.cos(angle - Math.PI / 6);
47
+ const ay1 = y2 - arrowSize * Math.sin(angle - Math.PI / 6);
48
+ const ax2 = x2 - arrowSize * Math.cos(angle + Math.PI / 6);
49
+ const ay2 = y2 - arrowSize * Math.sin(angle + Math.PI / 6);
50
+ return `M ${x1} ${y1} L ${x2} ${y2} L ${ax1} ${ay1} M ${x2} ${y2} L ${ax2} ${ay2}`;
51
+ }
52
+ /**
53
+ * Generate path data for dashed line
54
+ */
55
+ export function dashedLine(x1, y1, x2, y2, dashLength = 10, gapLength = 5) {
56
+ const dx = x2 - x1;
57
+ const dy = y2 - y1;
58
+ const length = Math.sqrt(dx * dx + dy * dy);
59
+ const angle = Math.atan2(dy, dx);
60
+ let d = '';
61
+ let currentDist = 0;
62
+ while (currentDist < length) {
63
+ const startX = x1 + currentDist * Math.cos(angle);
64
+ const startY = y1 + currentDist * Math.sin(angle);
65
+ const endDist = Math.min(currentDist + dashLength, length);
66
+ const endX = x1 + endDist * Math.cos(angle);
67
+ const endY = y1 + endDist * Math.sin(angle);
68
+ d += `M ${startX} ${startY} L ${endX} ${endY} `;
69
+ currentDist = endDist + gapLength;
70
+ }
71
+ return d.trim();
72
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Draw a circle
3
+ */
4
+ export declare function circle(cx: number, cy: number, r: number, attrs?: Record<string, string | number>): string;
5
+ /**
6
+ * Draw a rectangle
7
+ */
8
+ export declare function rect(x: number, y: number, width: number, height: number, attrs?: Record<string, string | number>): string;
9
+ /**
10
+ * Draw a line
11
+ */
12
+ export declare function line(x1: number, y1: number, x2: number, y2: number, attrs?: Record<string, string | number>): string;
13
+ /**
14
+ * Draw a polygon from points
15
+ */
16
+ export declare function polygon(points: [number, number][], attrs?: Record<string, string | number>): string;
17
+ /**
18
+ * Draw a path
19
+ */
20
+ export declare function path(d: string, attrs?: Record<string, string | number>): string;
21
+ /**
22
+ * Draw text
23
+ */
24
+ export declare function text(x: number, y: number, content: string, attrs?: Record<string, string | number>): string;
25
+ /**
26
+ * Draw an ellipse
27
+ */
28
+ export declare function ellipse(cx: number, cy: number, rx: number, ry: number, attrs?: Record<string, string | number>): string;
29
+ //# sourceMappingURL=shapes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shapes.d.ts","sourceRoot":"","sources":["../../src/utils/shapes.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,wBAAgB,MAAM,CACpB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,CAAC,EAAE,MAAM,EACT,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAC1C,MAAM,CAOR;AAED;;GAEG;AACH,wBAAgB,IAAI,CAClB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAC1C,MAAM,CAQR;AAED;;GAEG;AACH,wBAAgB,IAAI,CAClB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAC1C,MAAM,CAQR;AAED;;GAEG;AACH,wBAAgB,OAAO,CACrB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAC1B,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAC1C,MAAM,CAMR;AAED;;GAEG;AACH,wBAAgB,IAAI,CAClB,CAAC,EAAE,MAAM,EACT,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAC1C,MAAM,CAKR;AAED;;GAEG;AACH,wBAAgB,IAAI,CAClB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAC1C,MAAM,CAMR;AAED;;GAEG;AACH,wBAAgB,OAAO,CACrB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAC1C,MAAM,CAQR"}
@@ -0,0 +1,78 @@
1
+ // Common geometric shapes utilities
2
+ import { svgTag } from './svgTag';
3
+ /**
4
+ * Draw a circle
5
+ */
6
+ export function circle(cx, cy, r, attrs = {}) {
7
+ return svgTag('circle', {
8
+ cx,
9
+ cy,
10
+ r,
11
+ ...attrs,
12
+ }, '');
13
+ }
14
+ /**
15
+ * Draw a rectangle
16
+ */
17
+ export function rect(x, y, width, height, attrs = {}) {
18
+ return svgTag('rect', {
19
+ x,
20
+ y,
21
+ width,
22
+ height,
23
+ ...attrs,
24
+ }, '');
25
+ }
26
+ /**
27
+ * Draw a line
28
+ */
29
+ export function line(x1, y1, x2, y2, attrs = {}) {
30
+ return svgTag('line', {
31
+ x1,
32
+ y1,
33
+ x2,
34
+ y2,
35
+ ...attrs,
36
+ }, '');
37
+ }
38
+ /**
39
+ * Draw a polygon from points
40
+ */
41
+ export function polygon(points, attrs = {}) {
42
+ const pointsStr = points.map(([x, y]) => `${x},${y}`).join(' ');
43
+ return svgTag('polygon', {
44
+ points: pointsStr,
45
+ ...attrs,
46
+ }, '');
47
+ }
48
+ /**
49
+ * Draw a path
50
+ */
51
+ export function path(d, attrs = {}) {
52
+ return svgTag('path', {
53
+ d,
54
+ ...attrs,
55
+ }, '');
56
+ }
57
+ /**
58
+ * Draw text
59
+ */
60
+ export function text(x, y, content, attrs = {}) {
61
+ return svgTag('text', {
62
+ x,
63
+ y,
64
+ ...attrs,
65
+ }, content);
66
+ }
67
+ /**
68
+ * Draw an ellipse
69
+ */
70
+ export function ellipse(cx, cy, rx, ry, attrs = {}) {
71
+ return svgTag('ellipse', {
72
+ cx,
73
+ cy,
74
+ rx,
75
+ ry,
76
+ ...attrs,
77
+ }, '');
78
+ }
@@ -0,0 +1,11 @@
1
+ type SVGAttributes = Record<string, string | number | boolean | undefined>;
2
+ /**
3
+ * Type-safe SVG element generator
4
+ * @param tag - SVG element name (e.g., 'rect', 'circle', 'path')
5
+ * @param attrs - Object containing element attributes
6
+ * @param content - Inner content (string or nested elements)
7
+ * @returns SVG element string
8
+ */
9
+ export declare function svgTag(tag: string, attrs: SVGAttributes, content?: string): string;
10
+ export {};
11
+ //# sourceMappingURL=svgTag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svgTag.d.ts","sourceRoot":"","sources":["../../src/utils/svgTag.ts"],"names":[],"mappings":"AAEA,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;AAE3E;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,GAAE,MAAW,GAAG,MAAM,CAUtF"}
@@ -0,0 +1,29 @@
1
+ // SVG element builder utility
2
+ /**
3
+ * Type-safe SVG element generator
4
+ * @param tag - SVG element name (e.g., 'rect', 'circle', 'path')
5
+ * @param attrs - Object containing element attributes
6
+ * @param content - Inner content (string or nested elements)
7
+ * @returns SVG element string
8
+ */
9
+ export function svgTag(tag, attrs, content = '') {
10
+ const attributes = Object.entries(attrs)
11
+ .filter(([_, value]) => value !== undefined)
12
+ .map(([key, value]) => `${key}="${escapeAttribute(String(value))}"`)
13
+ .join(' ');
14
+ if (content) {
15
+ return `<${tag} ${attributes}>${content}</${tag}>`;
16
+ }
17
+ return `<${tag} ${attributes} />`;
18
+ }
19
+ /**
20
+ * Escape HTML attribute values
21
+ */
22
+ function escapeAttribute(value) {
23
+ return value
24
+ .replace(/&/g, '&amp;')
25
+ .replace(/</g, '&lt;')
26
+ .replace(/>/g, '&gt;')
27
+ .replace(/"/g, '&quot;')
28
+ .replace(/'/g, '&#039;');
29
+ }
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@xrn07/figure-renderer",
3
+ "version": "0.1.0",
4
+ "description": "Zero-dependency pure TypeScript figure renderer - pure SVG string generation",
5
+ "main": "./dist/render.js",
6
+ "module": "./dist/render.js",
7
+ "types": "./dist/render.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/render.d.ts",
11
+ "import": "./dist/render.js"
12
+ },
13
+ "./react": {
14
+ "types": "./dist/react.d.ts",
15
+ "import": "./dist/react.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "test": "vitest",
26
+ "test:coverage": "vitest --coverage",
27
+ "dev": "tsc --watch",
28
+ "prepublishOnly": "npm run build && npm test",
29
+ "version": "npm run build",
30
+ "postversion": "git push --follow-tags"
31
+ },
32
+ "keywords": [
33
+ "svg",
34
+ "renderer",
35
+ "figure",
36
+ "diagram",
37
+ "visualization",
38
+ "typescript",
39
+ "education",
40
+ "circuit",
41
+ "graph",
42
+ "geometry",
43
+ "zero-dependency"
44
+ ],
45
+ "author": "Haki",
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "https://github.com/haki/figure-renderer.git"
50
+ },
51
+ "homepage": "https://github.com/haki/figure-renderer#readme",
52
+ "bugs": {
53
+ "url": "https://github.com/haki/figure-renderer/issues"
54
+ },
55
+ "peerDependencies": {
56
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "react": {
60
+ "optional": true
61
+ }
62
+ },
63
+ "devDependencies": {
64
+ "@types/node": "^20.0.0",
65
+ "@types/react": "^18.3.31",
66
+ "typescript": "^5.0.0",
67
+ "vitest": "^1.0.0",
68
+ "zod": "^3.0.0"
69
+ }
70
+ }