@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,142 @@
1
+ // Coordinate plane renderer
2
+ import { svgTag } from '../utils/svgTag';
3
+ import { gridLines, axes } from '../utils/gridLines';
4
+ import { createCoordTransform } from '../utils/coordTransform';
5
+ import { circle, line, path, text } from '../utils/shapes';
6
+ export function renderCoordinatePlane(dsl) {
7
+ const { width, height, xRange, yRange, showGrid = true, showLabels = true, elements, title } = dsl;
8
+ const svgElements = [];
9
+ // Add arrow markers
10
+ svgElements.push(createAxisMarkers());
11
+ // Draw grid
12
+ if (showGrid) {
13
+ svgElements.push(gridLines({
14
+ xRange,
15
+ yRange,
16
+ width,
17
+ height,
18
+ xSteps: 10,
19
+ ySteps: 10,
20
+ stroke: '#e0e0e0',
21
+ strokeWidth: 1,
22
+ }));
23
+ }
24
+ // Draw axes
25
+ svgElements.push(axes({
26
+ xRange,
27
+ yRange,
28
+ width,
29
+ height,
30
+ showLabels,
31
+ }));
32
+ // Create coordinate transform
33
+ const transform = createCoordTransform({ xRange, yRange, width, height });
34
+ // Render elements
35
+ elements.forEach(element => {
36
+ svgElements.push(renderElement(element, transform));
37
+ });
38
+ const svg = svgTag('svg', {
39
+ viewBox: `0 0 ${width} ${height}`,
40
+ xmlns: 'http://www.w3.org/2000/svg',
41
+ }, svgElements.join(''));
42
+ return svg;
43
+ }
44
+ function renderElement(element, transform) {
45
+ switch (element.type) {
46
+ case 'point':
47
+ return renderPoint(element, transform);
48
+ case 'line':
49
+ return renderPlaneLine(element, transform);
50
+ case 'curve':
51
+ return renderCurve(element, transform);
52
+ case 'label':
53
+ return renderPlaneLabel(element, transform);
54
+ default:
55
+ return '';
56
+ }
57
+ }
58
+ function renderPoint(element, transform) {
59
+ const data = element.data;
60
+ const { x, y, label, color = '#e53e3e' } = data;
61
+ const svgX = transform.x(x);
62
+ const svgY = transform.y(y);
63
+ const elements = [];
64
+ // Draw point
65
+ elements.push(circle(svgX, svgY, 5, {
66
+ fill: color,
67
+ stroke: '#fff',
68
+ 'stroke-width': 2,
69
+ }));
70
+ // Add label
71
+ if (label) {
72
+ elements.push(text(svgX + 8, svgY - 8, label, {
73
+ 'text-anchor': 'start',
74
+ 'font-size': 12,
75
+ 'font-weight': 'bold',
76
+ fill: color,
77
+ }));
78
+ }
79
+ return svgTag('g', { class: 'point' }, elements.join(''));
80
+ }
81
+ function renderPlaneLine(element, transform) {
82
+ const data = element.data;
83
+ const { x1, y1, x2, y2, color = '#333', style = 'solid' } = data;
84
+ const svgX1 = transform.x(x1);
85
+ const svgY1 = transform.y(y1);
86
+ const svgX2 = transform.x(x2);
87
+ const svgY2 = transform.y(y2);
88
+ const strokeDasharray = style === 'dashed' ? '8,4' : style === 'dotted' ? '2,2' : 'none';
89
+ return svgTag('g', { class: 'line' }, line(svgX1, svgY1, svgX2, svgY2, {
90
+ stroke: color,
91
+ 'stroke-width': 2,
92
+ 'stroke-dasharray': strokeDasharray,
93
+ }));
94
+ }
95
+ function renderCurve(element, transform) {
96
+ const data = element.data;
97
+ const { points, color = '#3182ce', style = 'solid' } = data;
98
+ // Transform all points
99
+ const svgPoints = points.map(([x, y]) => {
100
+ const sx = transform.x(x);
101
+ const sy = transform.y(y);
102
+ return [sx, sy];
103
+ });
104
+ // Create smooth path through points
105
+ let d = '';
106
+ if (svgPoints.length > 0) {
107
+ d = `M ${svgPoints[0][0]} ${svgPoints[0][1]}`;
108
+ for (let i = 1; i < svgPoints.length; i++) {
109
+ d += ` L ${svgPoints[i][0]} ${svgPoints[i][1]}`;
110
+ }
111
+ }
112
+ const strokeDasharray = style === 'dashed' ? '8,4' : style === 'dotted' ? '2,2' : 'none';
113
+ return svgTag('g', { class: 'curve' }, path(d, {
114
+ fill: 'none',
115
+ stroke: color,
116
+ 'stroke-width': 2.5,
117
+ 'stroke-linecap': 'round',
118
+ 'stroke-linejoin': 'round',
119
+ 'stroke-dasharray': strokeDasharray,
120
+ }));
121
+ }
122
+ function renderPlaneLabel(element, transform) {
123
+ const data = element.data;
124
+ const { x, y, text: labelText, align = 'center' } = data;
125
+ const svgX = transform.x(x);
126
+ const svgY = transform.y(y);
127
+ return text(svgX, svgY, labelText, {
128
+ 'text-anchor': align,
129
+ 'dominant-baseline': 'middle',
130
+ 'font-size': 14,
131
+ fill: '#333',
132
+ });
133
+ }
134
+ function createAxisMarkers() {
135
+ return `
136
+ <defs>
137
+ <marker id="axis-arrow" markerWidth="10" markerHeight="10" refX="9" refY="5" orient="auto">
138
+ <path d="M0,0 L10,5 L0,10" fill="#333" />
139
+ </marker>
140
+ </defs>
141
+ `;
142
+ }
@@ -0,0 +1,3 @@
1
+ import type { ForceDiagramDSL } from '../types';
2
+ export declare function renderForceDiagram(dsl: ForceDiagramDSL): string;
3
+ //# sourceMappingURL=forceDiagram.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forceDiagram.d.ts","sourceRoot":"","sources":["../../src/renderers/forceDiagram.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAyB,MAAM,UAAU,CAAC;AAKvE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAsB/D"}
@@ -0,0 +1,130 @@
1
+ // Force diagram renderer
2
+ import { svgTag } from '../utils/svgTag';
3
+ import { rect, circle, text, line } from '../utils/shapes';
4
+ export function renderForceDiagram(dsl) {
5
+ const { width, height, object, forces, title } = dsl;
6
+ const elements = [];
7
+ // Add arrow markers
8
+ elements.push(createForceMarkers());
9
+ // Render object
10
+ elements.push(renderObject(object));
11
+ // Render forces
12
+ forces.forEach(force => {
13
+ elements.push(renderForce(force));
14
+ });
15
+ const svg = svgTag('svg', {
16
+ viewBox: `0 0 ${width} ${height}`,
17
+ xmlns: 'http://www.w3.org/2000/svg',
18
+ }, elements.join(''));
19
+ return svg;
20
+ }
21
+ function renderObject(obj) {
22
+ const elements = [];
23
+ switch (obj.type) {
24
+ case 'box': {
25
+ const { x, y, width: w = 60, height: h = 60, label } = obj;
26
+ elements.push(rect(x - w / 2, y - h / 2, w, h, {
27
+ fill: '#f0f0f0',
28
+ stroke: '#333',
29
+ 'stroke-width': 2,
30
+ }));
31
+ if (label) {
32
+ elements.push(text(x, y, label, {
33
+ 'text-anchor': 'middle',
34
+ 'dominant-baseline': 'middle',
35
+ 'font-size': 14,
36
+ fill: '#333',
37
+ }));
38
+ }
39
+ break;
40
+ }
41
+ case 'sphere': {
42
+ const { x, y, label } = obj;
43
+ const radius = 30;
44
+ elements.push(circle(x, y, radius, {
45
+ fill: '#e6f7ff',
46
+ stroke: '#333',
47
+ 'stroke-width': 2,
48
+ }));
49
+ if (label) {
50
+ elements.push(text(x, y, label, {
51
+ 'text-anchor': 'middle',
52
+ 'dominant-baseline': 'middle',
53
+ 'font-size': 12,
54
+ fill: '#333',
55
+ }));
56
+ }
57
+ break;
58
+ }
59
+ case 'point': {
60
+ const { x, y, label } = obj;
61
+ elements.push(circle(x, y, 5, {
62
+ fill: '#333',
63
+ }));
64
+ if (label) {
65
+ elements.push(text(x + 10, y, label, {
66
+ 'text-anchor': 'start',
67
+ 'dominant-baseline': 'middle',
68
+ 'font-size': 12,
69
+ fill: '#333',
70
+ }));
71
+ }
72
+ break;
73
+ }
74
+ }
75
+ return svgTag('g', { class: 'object' }, elements.join(''));
76
+ }
77
+ function renderForce(force) {
78
+ const { originX, originY, magnitude, angle, label, color = '#e53e3e' } = force;
79
+ const elements = [];
80
+ // Convert angle to radians and calculate endpoint
81
+ const angleRad = (angle * Math.PI) / 180;
82
+ const scale = 2; // Scale factor for visibility
83
+ const endX = originX + magnitude * Math.cos(angleRad) * scale;
84
+ const endY = originY - magnitude * Math.sin(angleRad) * scale; // SVG y is inverted
85
+ // Draw force vector with arrow
86
+ elements.push(line(originX, originY, endX, endY, {
87
+ stroke: color,
88
+ 'stroke-width': 3,
89
+ 'marker-end': `url(#arrow-${color})`,
90
+ }));
91
+ // Draw label
92
+ if (label) {
93
+ // Position label slightly offset from the arrow
94
+ const labelOffset = 15;
95
+ const labelX = endX + labelOffset * Math.cos(angleRad);
96
+ const labelY = endY - labelOffset * Math.sin(angleRad);
97
+ elements.push(text(labelX, labelY, label, {
98
+ 'text-anchor': 'start',
99
+ 'dominant-baseline': 'middle',
100
+ 'font-size': 14,
101
+ 'font-weight': 'bold',
102
+ fill: color,
103
+ }));
104
+ }
105
+ return svgTag('g', { class: 'force' }, elements.join(''));
106
+ }
107
+ function createForceMarkers() {
108
+ return `
109
+ <defs>
110
+ <marker id="arrow-#e53e3e" markerWidth="10" markerHeight="10" refX="9" refY="5" orient="auto">
111
+ <path d="M0,0 L10,5 L0,10" fill="#e53e3e" />
112
+ </marker>
113
+ <marker id="arrow-#3182ce" markerWidth="10" markerHeight="10" refX="9" refY="5" orient="auto">
114
+ <path d="M0,0 L10,5 L0,10" fill="#3182ce" />
115
+ </marker>
116
+ <marker id="arrow-#38a169" markerWidth="10" markerHeight="10" refX="9" refY="5" orient="auto">
117
+ <path d="M0,0 L10,5 L0,10" fill="#38a169" />
118
+ </marker>
119
+ <marker id="arrow-#d69e2e" markerWidth="10" markerHeight="10" refX="9" refY="5" orient="auto">
120
+ <path d="M0,0 L10,5 L0,10" fill="#d69e2e" />
121
+ </marker>
122
+ <marker id="arrow-#805ad5" markerWidth="10" markerHeight="10" refX="9" refY="5" orient="auto">
123
+ <path d="M0,0 L10,5 L0,10" fill="#805ad5" />
124
+ </marker>
125
+ <marker id="arrow-#333" markerWidth="10" markerHeight="10" refX="9" refY="5" orient="auto">
126
+ <path d="M0,0 L10,5 L0,10" fill="#333" />
127
+ </marker>
128
+ </defs>
129
+ `;
130
+ }
@@ -0,0 +1,3 @@
1
+ import type { GeometryDSL } from '../types';
2
+ export declare function renderGeometry(dsl: GeometryDSL): string;
3
+ //# sourceMappingURL=geometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry.d.ts","sourceRoot":"","sources":["../../src/renderers/geometry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,UAAU,CAAC;AAK3D,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAgBvD"}
@@ -0,0 +1,173 @@
1
+ // Geometry figure renderer
2
+ import { svgTag } from '../utils/svgTag';
3
+ import { line, circle, rect, polygon, path, text } from '../utils/shapes';
4
+ import { arcPath } from '../utils/path';
5
+ export function renderGeometry(dsl) {
6
+ const { width, height, shapes, title } = dsl;
7
+ const elements = [];
8
+ // Render shapes
9
+ shapes.forEach(shape => {
10
+ elements.push(renderShape(shape));
11
+ });
12
+ const svg = svgTag('svg', {
13
+ viewBox: `0 0 ${width} ${height}`,
14
+ xmlns: 'http://www.w3.org/2000/svg',
15
+ }, elements.join(''));
16
+ return svg;
17
+ }
18
+ function renderShape(shape) {
19
+ switch (shape.type) {
20
+ case 'line':
21
+ return renderLine(shape);
22
+ case 'circle':
23
+ return renderCircle(shape);
24
+ case 'rect':
25
+ return renderRect(shape);
26
+ case 'triangle':
27
+ return renderTriangle(shape);
28
+ case 'angle':
29
+ return renderAngle(shape);
30
+ default:
31
+ return '';
32
+ }
33
+ }
34
+ function renderLine(shape) {
35
+ const { x1, y1, x2, y2, label } = shape;
36
+ const elements = [];
37
+ // Draw line
38
+ elements.push(line(x1, y1, x2, y2, {
39
+ stroke: '#333',
40
+ 'stroke-width': 2,
41
+ }));
42
+ // Add endpoints
43
+ elements.push(circle(x1, y1, 3, { fill: '#333' }));
44
+ elements.push(circle(x2, y2, 3, { fill: '#333' }));
45
+ // Add label if provided
46
+ if (label) {
47
+ const midX = (x1 + x2) / 2;
48
+ const midY = (y1 + y2) / 2;
49
+ elements.push(text(midX + 5, midY - 5, label, {
50
+ 'text-anchor': 'start',
51
+ 'font-size': 12,
52
+ fill: '#333',
53
+ }));
54
+ }
55
+ return svgTag('g', { class: 'line' }, elements.join(''));
56
+ }
57
+ function renderCircle(shape) {
58
+ const { cx, cy, r, label } = shape;
59
+ const elements = [];
60
+ // Draw circle
61
+ elements.push(circle(cx, cy, r, {
62
+ fill: 'none',
63
+ stroke: '#333',
64
+ 'stroke-width': 2,
65
+ }));
66
+ // Draw center point
67
+ elements.push(circle(cx, cy, 3, { fill: '#333' }));
68
+ // Add label if provided
69
+ if (label) {
70
+ elements.push(text(cx, cy - r - 10, label, {
71
+ 'text-anchor': 'middle',
72
+ 'font-size': 12,
73
+ fill: '#333',
74
+ }));
75
+ }
76
+ return svgTag('g', { class: 'circle' }, elements.join(''));
77
+ }
78
+ function renderRect(shape) {
79
+ const { x, y, width, height, label } = shape;
80
+ const elements = [];
81
+ // Draw rectangle
82
+ elements.push(rect(x, y, width, height, {
83
+ fill: 'none',
84
+ stroke: '#333',
85
+ 'stroke-width': 2,
86
+ }));
87
+ // Add corner points
88
+ elements.push(circle(x, y, 3, { fill: '#333' }));
89
+ elements.push(circle(x + width, y, 3, { fill: '#333' }));
90
+ elements.push(circle(x + width, y + height, 3, { fill: '#333' }));
91
+ elements.push(circle(x, y + height, 3, { fill: '#333' }));
92
+ // Add label if provided
93
+ if (label) {
94
+ elements.push(text(x + width / 2, y - 10, label, {
95
+ 'text-anchor': 'middle',
96
+ 'font-size': 12,
97
+ fill: '#333',
98
+ }));
99
+ }
100
+ return svgTag('g', { class: 'rectangle' }, elements.join(''));
101
+ }
102
+ function renderTriangle(shape) {
103
+ const { points, label } = shape;
104
+ const elements = [];
105
+ // Draw triangle
106
+ elements.push(polygon(points, {
107
+ fill: 'none',
108
+ stroke: '#333',
109
+ 'stroke-width': 2,
110
+ }));
111
+ // Add vertices
112
+ points.forEach(([px, py]) => {
113
+ elements.push(circle(px, py, 3, { fill: '#333' }));
114
+ });
115
+ // Add label if provided
116
+ if (label) {
117
+ // Calculate centroid
118
+ const cx = points.reduce((sum, [x]) => sum + x, 0) / points.length;
119
+ const cy = points.reduce((sum, [, y]) => sum + y, 0) / points.length;
120
+ elements.push(text(cx, cy, label, {
121
+ 'text-anchor': 'middle',
122
+ 'dominant-baseline': 'middle',
123
+ 'font-size': 12,
124
+ fill: '#333',
125
+ }));
126
+ }
127
+ return svgTag('g', { class: 'triangle' }, elements.join(''));
128
+ }
129
+ function renderAngle(shape) {
130
+ const { vertex, rays, label } = shape;
131
+ const [vx, vy] = vertex;
132
+ const [[r1x, r1y], [r2x, r2y]] = rays;
133
+ const elements = [];
134
+ // Calculate angle
135
+ const angle1 = Math.atan2(vy - r1y, r1x - vx) * 180 / Math.PI;
136
+ const angle2 = Math.atan2(vy - r2y, r2x - vx) * 180 / Math.PI;
137
+ // Draw rays
138
+ const rayLength = 50;
139
+ elements.push(line(vx, vy, r1x, r1y, {
140
+ stroke: '#333',
141
+ 'stroke-width': 2,
142
+ }));
143
+ elements.push(line(vx, vy, r2x, r2y, {
144
+ stroke: '#333',
145
+ 'stroke-width': 2,
146
+ }));
147
+ // Draw angle arc
148
+ const arcRadius = 20;
149
+ const startAngle = Math.atan2(r1y - vy, r1x - vx) * 180 / Math.PI;
150
+ const endAngle = Math.atan2(r2y - vy, r2x - vx) * 180 / Math.PI;
151
+ elements.push(path(arcPath(vx, vy, arcRadius, startAngle, endAngle), {
152
+ fill: 'rgba(100, 149, 237, 0.2)',
153
+ stroke: '#6495ED',
154
+ 'stroke-width': 1.5,
155
+ }));
156
+ // Add vertex point
157
+ elements.push(circle(vx, vy, 4, { fill: '#333' }));
158
+ // Add label if provided
159
+ if (label) {
160
+ // Position label at middle of angle
161
+ const midAngle = ((startAngle + endAngle) / 2) * Math.PI / 180;
162
+ const labelRadius = arcRadius + 15;
163
+ const labelX = vx + labelRadius * Math.cos(midAngle);
164
+ const labelY = vy - labelRadius * Math.sin(midAngle);
165
+ elements.push(text(labelX, labelY, label, {
166
+ 'text-anchor': 'middle',
167
+ 'dominant-baseline': 'middle',
168
+ 'font-size': 14,
169
+ fill: '#333',
170
+ }));
171
+ }
172
+ return svgTag('g', { class: 'angle' }, elements.join(''));
173
+ }
@@ -0,0 +1,3 @@
1
+ import type { GraphDSL } from '../types';
2
+ export declare function renderGraph(dsl: GraphDSL): string;
3
+ //# sourceMappingURL=graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/renderers/graph.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,UAAU,CAAC;AAMxD,wBAAgB,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,CA2CjD"}
@@ -0,0 +1,137 @@
1
+ // Graph renderer
2
+ import { svgTag } from '../utils/svgTag';
3
+ import { gridLines, axes } from '../utils/gridLines';
4
+ import { createCoordTransform } from '../utils/coordTransform';
5
+ import { path } from '../utils/shapes';
6
+ export function renderGraph(dsl) {
7
+ const { width, height, xRange, yRange, functions, title } = dsl;
8
+ const elements = [];
9
+ // Add arrow markers
10
+ elements.push(createGraphMarkers());
11
+ // Draw grid
12
+ elements.push(gridLines({
13
+ xRange,
14
+ yRange,
15
+ width,
16
+ height,
17
+ xSteps: 10,
18
+ ySteps: 10,
19
+ stroke: '#e0e0e0',
20
+ strokeWidth: 1,
21
+ }));
22
+ // Draw axes
23
+ elements.push(axes({
24
+ xRange,
25
+ yRange,
26
+ width,
27
+ height,
28
+ showLabels: true,
29
+ }));
30
+ // Create coordinate transform
31
+ const transform = createCoordTransform({ xRange, yRange, width, height });
32
+ // Render functions
33
+ functions.forEach(fn => {
34
+ elements.push(renderFunction(fn, transform, xRange));
35
+ });
36
+ const svg = svgTag('svg', {
37
+ viewBox: `0 0 ${width} ${height}`,
38
+ xmlns: 'http://www.w3.org/2000/svg',
39
+ }, elements.join(''));
40
+ return svg;
41
+ }
42
+ function renderFunction(fn, transform, xRange) {
43
+ const { equation, color, style = 'solid' } = fn;
44
+ // Generate points along the curve
45
+ const step = (xRange[1] - xRange[0]) / 100;
46
+ const points = [];
47
+ for (let x = xRange[0]; x <= xRange[1]; x += step) {
48
+ try {
49
+ // Safely evaluate the function
50
+ const y = evaluateFunction(equation, x);
51
+ // Check if y is finite and within reasonable bounds
52
+ if (isFinite(y) && Math.abs(y) < 1e6) {
53
+ points.push([x, y]);
54
+ }
55
+ else {
56
+ // Break the path if we hit infinity or NaN
57
+ if (points.length > 1) {
58
+ // We'll handle discontinuities by creating separate paths
59
+ }
60
+ }
61
+ }
62
+ catch (e) {
63
+ // Skip invalid points
64
+ }
65
+ }
66
+ // Transform points to SVG coordinates
67
+ const svgPoints = points.map(([x, y]) => {
68
+ const sx = transform.x(x);
69
+ const sy = transform.y(y);
70
+ return [sx, sy];
71
+ });
72
+ // Create path data
73
+ let d = '';
74
+ if (svgPoints.length > 0) {
75
+ d = `M ${svgPoints[0][0]} ${svgPoints[0][1]}`;
76
+ for (let i = 1; i < svgPoints.length; i++) {
77
+ d += ` L ${svgPoints[i][0]} ${svgPoints[i][1]}`;
78
+ }
79
+ }
80
+ const strokeDasharray = style === 'dashed' ? '8,4' : style === 'dotted' ? '2,2' : 'none';
81
+ return path(d, {
82
+ fill: 'none',
83
+ stroke: color,
84
+ 'stroke-width': 2.5,
85
+ 'stroke-linecap': 'round',
86
+ 'stroke-linejoin': 'round',
87
+ 'stroke-dasharray': strokeDasharray,
88
+ });
89
+ }
90
+ /**
91
+ * Safely evaluate a mathematical function
92
+ * Supports: basic arithmetic, Math functions, powers
93
+ */
94
+ function evaluateFunction(equation, x) {
95
+ // Create a safe evaluation context
96
+ const safeMath = {
97
+ sin: Math.sin,
98
+ cos: Math.cos,
99
+ tan: Math.tan,
100
+ asin: Math.asin,
101
+ acos: Math.acos,
102
+ atan: Math.atan,
103
+ sqrt: Math.sqrt,
104
+ abs: Math.abs,
105
+ log: Math.log,
106
+ exp: Math.exp,
107
+ pow: Math.pow,
108
+ PI: Math.PI,
109
+ E: Math.E,
110
+ };
111
+ // Replace common notation with JavaScript equivalents
112
+ let expr = equation
113
+ .replace(/\^/g, '**') // x^2 -> x**2
114
+ .replace(/ln/g, 'log') // ln -> log
115
+ .replace(/(\d)([a-zA-Z])/g, '$1*$2'); // 2x -> 2*x
116
+ // Create a function with x as parameter
117
+ try {
118
+ const func = new Function('x', 'Math', `
119
+ with (Math) {
120
+ return ${expr};
121
+ }
122
+ `);
123
+ return func(x, safeMath);
124
+ }
125
+ catch (e) {
126
+ return NaN;
127
+ }
128
+ }
129
+ function createGraphMarkers() {
130
+ return `
131
+ <defs>
132
+ <marker id="graph-arrow" markerWidth="10" markerHeight="10" refX="9" refY="5" orient="auto">
133
+ <path d="M0,0 L10,5 L0,10" fill="#333" />
134
+ </marker>
135
+ </defs>
136
+ `;
137
+ }
@@ -0,0 +1,3 @@
1
+ import type { NumberLineDSL } from '../types';
2
+ export declare function renderNumberLine(dsl: NumberLineDSL): string;
3
+ //# sourceMappingURL=numberLine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"numberLine.d.ts","sourceRoot":"","sources":["../../src/renderers/numberLine.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAkB,MAAM,UAAU,CAAC;AAI9D,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA+F3D"}