@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.
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/dist/parse.d.ts +11 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +39 -0
- package/dist/react.d.ts +14 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +21 -0
- package/dist/render.d.ts +10 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +41 -0
- package/dist/renderers/bioTable.d.ts +3 -0
- package/dist/renderers/bioTable.d.ts.map +1 -0
- package/dist/renderers/bioTable.js +28 -0
- package/dist/renderers/circuit.d.ts +3 -0
- package/dist/renderers/circuit.d.ts.map +1 -0
- package/dist/renderers/circuit.js +237 -0
- package/dist/renderers/coordinatePlane.d.ts +3 -0
- package/dist/renderers/coordinatePlane.d.ts.map +1 -0
- package/dist/renderers/coordinatePlane.js +142 -0
- package/dist/renderers/forceDiagram.d.ts +3 -0
- package/dist/renderers/forceDiagram.d.ts.map +1 -0
- package/dist/renderers/forceDiagram.js +130 -0
- package/dist/renderers/geometry.d.ts +3 -0
- package/dist/renderers/geometry.d.ts.map +1 -0
- package/dist/renderers/geometry.js +173 -0
- package/dist/renderers/graph.d.ts +3 -0
- package/dist/renderers/graph.d.ts.map +1 -0
- package/dist/renderers/graph.js +137 -0
- package/dist/renderers/numberLine.d.ts +3 -0
- package/dist/renderers/numberLine.d.ts.map +1 -0
- package/dist/renderers/numberLine.js +98 -0
- package/dist/renderers/rayDiagram.d.ts +3 -0
- package/dist/renderers/rayDiagram.d.ts.map +1 -0
- package/dist/renderers/rayDiagram.js +181 -0
- package/dist/renderers/venn.d.ts +3 -0
- package/dist/renderers/venn.d.ts.map +1 -0
- package/dist/renderers/venn.js +60 -0
- package/dist/types.d.ts +225 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/utils/arrowMarker.d.ts +13 -0
- package/dist/utils/arrowMarker.d.ts.map +1 -0
- package/dist/utils/arrowMarker.js +48 -0
- package/dist/utils/coordTransform.d.ts +29 -0
- package/dist/utils/coordTransform.d.ts.map +1 -0
- package/dist/utils/coordTransform.js +38 -0
- package/dist/utils/gridLines.d.ts +23 -0
- package/dist/utils/gridLines.d.ts.map +1 -0
- package/dist/utils/gridLines.js +124 -0
- package/dist/utils/label.d.ts +30 -0
- package/dist/utils/label.d.ts.map +1 -0
- package/dist/utils/label.js +82 -0
- package/dist/utils/path.d.ts +21 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +72 -0
- package/dist/utils/shapes.d.ts +29 -0
- package/dist/utils/shapes.d.ts.map +1 -0
- package/dist/utils/shapes.js +78 -0
- package/dist/utils/svgTag.d.ts +11 -0
- package/dist/utils/svgTag.d.ts.map +1 -0
- package/dist/utils/svgTag.js +29 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Haki
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# @xrn07/figure-renderer
|
|
2
|
+
|
|
3
|
+
Zero-dependency pure TypeScript figure renderer - pure SVG string generation.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Zero runtime dependencies** - Only ~8KB minified
|
|
8
|
+
- ✅ **Universal** - Works in Node.js, browser, Next.js SSR, PDF export
|
|
9
|
+
- ✅ **Type-safe** - Full TypeScript support
|
|
10
|
+
- ✅ **Testable** - SVG strings are easily snapshot-able
|
|
11
|
+
- ✅ **Accessible** - React component with proper ARIA labels
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @xrn07/figure-renderer
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### Server-side (Node.js)
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { renderFigure, parseFigureDSL } from "@xrn07/figure-renderer";
|
|
25
|
+
|
|
26
|
+
const dsl = parseFigureDSL(jsonString);
|
|
27
|
+
const svg = renderFigure(dsl);
|
|
28
|
+
console.log(svg); // <svg>...</svg>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### React/Next.js
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { FigureView } from "@xrn07/figure-renderer/react";
|
|
35
|
+
import { parseFigureDSL } from "@xrn07/figure-renderer";
|
|
36
|
+
|
|
37
|
+
const dsl = parseFigureDSL(question.figure_dsl);
|
|
38
|
+
return <FigureView dsl={dsl} alt={question.figure_alt_bn} className='w-full' />;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## API
|
|
42
|
+
|
|
43
|
+
### `renderFigure(dsl: FigureDSL): string`
|
|
44
|
+
|
|
45
|
+
Main rendering function that converts DSL to SVG string.
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
const svg = renderFigure({
|
|
49
|
+
type: 'circuit',
|
|
50
|
+
width: 400,
|
|
51
|
+
height: 300,
|
|
52
|
+
components: [...]
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `parseFigureDSL(str: string): FigureDSL | null`
|
|
57
|
+
|
|
58
|
+
Safe JSON parser with Zod validation. Returns `null` for invalid input.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const dsl = parseFigureDSL(jsonString);
|
|
62
|
+
if (dsl) {
|
|
63
|
+
// Valid DSL
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### `<FigureView />` (React component)
|
|
68
|
+
|
|
69
|
+
React wrapper component for rendering figures.
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
<FigureView dsl={dsl} alt='Description of figure' className='w-full h-auto' />
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Supported Figure Types
|
|
76
|
+
|
|
77
|
+
- `circuit` - Circuit diagrams
|
|
78
|
+
- `rayDiagram` - Ray optics diagrams
|
|
79
|
+
- `forceDiagram` - Force vector diagrams
|
|
80
|
+
- `geometry` - Geometric shapes
|
|
81
|
+
- `coordinatePlane` - XY coordinate systems
|
|
82
|
+
- `numberLine` - Number lines
|
|
83
|
+
- `graph` - Function graphs
|
|
84
|
+
- `venn` - Venn diagrams
|
|
85
|
+
- `bioTable` - Biology comparison tables
|
|
86
|
+
|
|
87
|
+
## DSL Schema
|
|
88
|
+
|
|
89
|
+
Each figure type has its own DSL structure. All share common base properties:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
interface FigureDSL {
|
|
93
|
+
type: FigureType;
|
|
94
|
+
width: number;
|
|
95
|
+
height: number;
|
|
96
|
+
title?: string;
|
|
97
|
+
alt?: string;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
See `src/types.ts` for complete type definitions.
|
|
102
|
+
|
|
103
|
+
## Examples
|
|
104
|
+
|
|
105
|
+
### Circuit Diagram
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const circuitDSL = {
|
|
109
|
+
type: "circuit",
|
|
110
|
+
width: 400,
|
|
111
|
+
height: 300,
|
|
112
|
+
components: [
|
|
113
|
+
{ type: "battery", x: 100, y: 150, label: "12V" },
|
|
114
|
+
{ type: "resistor", x: 200, y: 100, label: "R1" },
|
|
115
|
+
// ... more components
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Force Diagram
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const forceDSL = {
|
|
124
|
+
type: "forceDiagram",
|
|
125
|
+
width: 400,
|
|
126
|
+
height: 300,
|
|
127
|
+
object: { type: "box", x: 200, y: 150, width: 60, height: 60 },
|
|
128
|
+
forces: [
|
|
129
|
+
{
|
|
130
|
+
originX: 200,
|
|
131
|
+
originY: 150,
|
|
132
|
+
magnitude: 50,
|
|
133
|
+
angle: -90,
|
|
134
|
+
label: "F₁",
|
|
135
|
+
color: "#e53e3e",
|
|
136
|
+
},
|
|
137
|
+
// ... more forces
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Testing
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { renderFigure } from '@xrn07/figure-renderer';
|
|
146
|
+
|
|
147
|
+
test('renders circuit diagram', () => {
|
|
148
|
+
const dsl = { type: 'circuit', ... };
|
|
149
|
+
const svg = renderFigure(dsl);
|
|
150
|
+
expect(svg).toMatchSnapshot();
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Bundle Size
|
|
155
|
+
|
|
156
|
+
- Minified: ~8KB
|
|
157
|
+
- Gzipped: ~3KB
|
|
158
|
+
- Tree-shakeable: Unused renderers can be excluded
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
MIT
|
package/dist/parse.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FigureDSL } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Safely parse FigureDSL from JSON string
|
|
4
|
+
* Returns null if parsing or validation fails
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseFigureDSL(jsonString: string): FigureDSL | null;
|
|
7
|
+
/**
|
|
8
|
+
* Validate a FigureDSL object
|
|
9
|
+
*/
|
|
10
|
+
export declare function validateFigureDSL(obj: unknown): obj is FigureDSL;
|
|
11
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAgBzC;;;GAGG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAQnE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,SAAS,CAOhE"}
|
package/dist/parse.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Safe DSL parser with Zod validation
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* Zod schema for FigureDSL validation
|
|
5
|
+
*/
|
|
6
|
+
const figureDSLSchema = z.lazy(() => z.object({
|
|
7
|
+
type: z.enum(['circuit', 'rayDiagram', 'forceDiagram', 'geometry', 'coordinatePlane', 'numberLine', 'graph', 'venn', 'bioTable']),
|
|
8
|
+
width: z.number(),
|
|
9
|
+
height: z.number(),
|
|
10
|
+
title: z.string().optional(),
|
|
11
|
+
alt: z.string().optional(),
|
|
12
|
+
// Allow additional properties for specific figure types
|
|
13
|
+
}).passthrough());
|
|
14
|
+
/**
|
|
15
|
+
* Safely parse FigureDSL from JSON string
|
|
16
|
+
* Returns null if parsing or validation fails
|
|
17
|
+
*/
|
|
18
|
+
export function parseFigureDSL(jsonString) {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = JSON.parse(jsonString);
|
|
21
|
+
return figureDSLSchema.parse(parsed);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error('Failed to parse FigureDSL:', error);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Validate a FigureDSL object
|
|
30
|
+
*/
|
|
31
|
+
export function validateFigureDSL(obj) {
|
|
32
|
+
try {
|
|
33
|
+
figureDSLSchema.parse(obj);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { FigureDSL } from './types';
|
|
3
|
+
export interface FigureViewProps {
|
|
4
|
+
dsl: FigureDSL | null;
|
|
5
|
+
alt?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
style?: React.CSSProperties;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* React component for rendering figures
|
|
11
|
+
* Uses dangerouslySetInnerHTML for efficient SVG rendering
|
|
12
|
+
*/
|
|
13
|
+
export declare const FigureView: React.FC<FigureViewProps>;
|
|
14
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,SAAS,GAAG,IAAI,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAED;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAchD,CAAC"}
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// React component wrapper
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { renderFigure } from './render';
|
|
4
|
+
/**
|
|
5
|
+
* React component for rendering figures
|
|
6
|
+
* Uses dangerouslySetInnerHTML for efficient SVG rendering
|
|
7
|
+
*/
|
|
8
|
+
export const FigureView = ({ dsl, alt, className, style }) => {
|
|
9
|
+
if (!dsl) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const svg = renderFigure(dsl);
|
|
13
|
+
return React.createElement('div', {
|
|
14
|
+
className: className || undefined,
|
|
15
|
+
style: style || undefined,
|
|
16
|
+
dangerouslySetInnerHTML: { __html: svg },
|
|
17
|
+
role: 'img',
|
|
18
|
+
'aria-label': alt || dsl.alt || 'Figure'
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
FigureView.displayName = 'FigureView';
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FigureDSL } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Main rendering function - dispatches to appropriate renderer based on DSL type
|
|
4
|
+
* @param dsl - Figure DSL object
|
|
5
|
+
* @returns SVG string
|
|
6
|
+
*/
|
|
7
|
+
export declare function renderFigure(dsl: FigureDSL): string;
|
|
8
|
+
export type * from './types';
|
|
9
|
+
export { parseFigureDSL, validateFigureDSL } from './parse';
|
|
10
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAWzC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAuBnD;AAGD,mBAAmB,SAAS,CAAC;AAG7B,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Main entry point for figure rendering
|
|
2
|
+
import { renderCircuit } from './renderers/circuit';
|
|
3
|
+
import { renderRayDiagram } from './renderers/rayDiagram';
|
|
4
|
+
import { renderForceDiagram } from './renderers/forceDiagram';
|
|
5
|
+
import { renderGeometry } from './renderers/geometry';
|
|
6
|
+
import { renderCoordinatePlane } from './renderers/coordinatePlane';
|
|
7
|
+
import { renderNumberLine } from './renderers/numberLine';
|
|
8
|
+
import { renderGraph } from './renderers/graph';
|
|
9
|
+
import { renderVenn } from './renderers/venn';
|
|
10
|
+
import { renderBioTable } from './renderers/bioTable';
|
|
11
|
+
/**
|
|
12
|
+
* Main rendering function - dispatches to appropriate renderer based on DSL type
|
|
13
|
+
* @param dsl - Figure DSL object
|
|
14
|
+
* @returns SVG string
|
|
15
|
+
*/
|
|
16
|
+
export function renderFigure(dsl) {
|
|
17
|
+
switch (dsl.type) {
|
|
18
|
+
case 'circuit':
|
|
19
|
+
return renderCircuit(dsl);
|
|
20
|
+
case 'rayDiagram':
|
|
21
|
+
return renderRayDiagram(dsl);
|
|
22
|
+
case 'forceDiagram':
|
|
23
|
+
return renderForceDiagram(dsl);
|
|
24
|
+
case 'geometry':
|
|
25
|
+
return renderGeometry(dsl);
|
|
26
|
+
case 'coordinatePlane':
|
|
27
|
+
return renderCoordinatePlane(dsl);
|
|
28
|
+
case 'numberLine':
|
|
29
|
+
return renderNumberLine(dsl);
|
|
30
|
+
case 'graph':
|
|
31
|
+
return renderGraph(dsl);
|
|
32
|
+
case 'venn':
|
|
33
|
+
return renderVenn(dsl);
|
|
34
|
+
case 'bioTable':
|
|
35
|
+
return renderBioTable(dsl);
|
|
36
|
+
default:
|
|
37
|
+
throw new Error(`Unknown figure type: ${dsl.type}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Re-export parser
|
|
41
|
+
export { parseFigureDSL, validateFigureDSL } from './parse';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bioTable.d.ts","sourceRoot":"","sources":["../../src/renderers/bioTable.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAG5C,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAWvD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Biology table renderer
|
|
2
|
+
import { svgTag } from '../utils/svgTag';
|
|
3
|
+
export function renderBioTable(dsl) {
|
|
4
|
+
const { width, height, columns, rows } = dsl;
|
|
5
|
+
const elements = [
|
|
6
|
+
renderTable(columns, rows),
|
|
7
|
+
];
|
|
8
|
+
return svgTag('svg', {
|
|
9
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
10
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
11
|
+
}, elements.join(''));
|
|
12
|
+
}
|
|
13
|
+
function renderTable(columns, rows) {
|
|
14
|
+
// TODO: Implement table rendering with headers and cells
|
|
15
|
+
return '<g class="bio-table"></g>';
|
|
16
|
+
}
|
|
17
|
+
function renderHeader(column, index) {
|
|
18
|
+
// TODO: Implement header rendering
|
|
19
|
+
return '<g class="table-header"></g>';
|
|
20
|
+
}
|
|
21
|
+
function renderRow(row, rowIndex) {
|
|
22
|
+
// TODO: Implement row rendering
|
|
23
|
+
return '<g class="table-row"></g>';
|
|
24
|
+
}
|
|
25
|
+
function renderCell(cell, colIndex) {
|
|
26
|
+
// TODO: Implement cell rendering (text or image)
|
|
27
|
+
return '<g class="table-cell"></g>';
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit.d.ts","sourceRoot":"","sources":["../../src/renderers/circuit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAoB,MAAM,UAAU,CAAC;AAI7D,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAyCrD"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// Circuit diagram renderer
|
|
2
|
+
import { svgTag } from '../utils/svgTag';
|
|
3
|
+
import { line, rect, circle, text, path } from '../utils/shapes';
|
|
4
|
+
export function renderCircuit(dsl) {
|
|
5
|
+
const { width, height, components, title } = dsl;
|
|
6
|
+
const elements = [];
|
|
7
|
+
// Add arrow markers
|
|
8
|
+
elements.push(createArrowMarkers());
|
|
9
|
+
// Render components
|
|
10
|
+
components.forEach(comp => {
|
|
11
|
+
switch (comp.type) {
|
|
12
|
+
case 'battery':
|
|
13
|
+
elements.push(renderBattery(comp));
|
|
14
|
+
break;
|
|
15
|
+
case 'resistor':
|
|
16
|
+
elements.push(renderResistor(comp));
|
|
17
|
+
break;
|
|
18
|
+
case 'switch':
|
|
19
|
+
elements.push(renderSwitch(comp));
|
|
20
|
+
break;
|
|
21
|
+
case 'bulb':
|
|
22
|
+
elements.push(renderBulb(comp));
|
|
23
|
+
break;
|
|
24
|
+
case 'wire':
|
|
25
|
+
elements.push(renderWire(comp));
|
|
26
|
+
break;
|
|
27
|
+
case 'ammeter':
|
|
28
|
+
elements.push(renderAmmeter(comp));
|
|
29
|
+
break;
|
|
30
|
+
case 'voltmeter':
|
|
31
|
+
elements.push(renderVoltmeter(comp));
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const svg = svgTag('svg', {
|
|
36
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
37
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
38
|
+
}, elements.join(''));
|
|
39
|
+
return svg;
|
|
40
|
+
}
|
|
41
|
+
function renderBattery(comp) {
|
|
42
|
+
const { x, y, rotation = 0, label, value } = comp;
|
|
43
|
+
const size = 40;
|
|
44
|
+
const transform = rotation ? `transform="rotate(${rotation} ${x} ${y})"` : '';
|
|
45
|
+
const elements = [];
|
|
46
|
+
// Positive terminal (longer line)
|
|
47
|
+
elements.push(line(x - 20, y - 10, x + 20, y - 10, {
|
|
48
|
+
stroke: '#333',
|
|
49
|
+
'stroke-width': 3,
|
|
50
|
+
}));
|
|
51
|
+
// Negative terminal (shorter line)
|
|
52
|
+
elements.push(line(x - 10, y + 10, x + 10, y + 10, {
|
|
53
|
+
stroke: '#333',
|
|
54
|
+
'stroke-width': 3,
|
|
55
|
+
}));
|
|
56
|
+
// Connecting wires
|
|
57
|
+
elements.push(line(x, y - 10, x, y - 30, { stroke: '#333', 'stroke-width': 2 }));
|
|
58
|
+
elements.push(line(x, y + 10, x, y + 30, { stroke: '#333', 'stroke-width': 2 }));
|
|
59
|
+
// Label
|
|
60
|
+
if (label || value) {
|
|
61
|
+
const labelText = label || value || '';
|
|
62
|
+
elements.push(text(x + 30, y, labelText, {
|
|
63
|
+
'text-anchor': 'start',
|
|
64
|
+
'dominant-baseline': 'middle',
|
|
65
|
+
'font-size': 14,
|
|
66
|
+
fill: '#333',
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
return svgTag('g', { class: 'battery', transform }, elements.join(''));
|
|
70
|
+
}
|
|
71
|
+
function renderResistor(comp) {
|
|
72
|
+
const { x, y, rotation = 0, label, value } = comp;
|
|
73
|
+
const width = 60;
|
|
74
|
+
const height = 20;
|
|
75
|
+
const transform = rotation ? `transform="rotate(${rotation} ${x} ${y})"` : '';
|
|
76
|
+
const elements = [];
|
|
77
|
+
// Zigzag pattern
|
|
78
|
+
const zigzagPath = `
|
|
79
|
+
M ${x - width / 2} ${y}
|
|
80
|
+
L ${x - width / 2 + 10} ${y - height / 2}
|
|
81
|
+
L ${x - width / 2 + 20} ${y + height / 2}
|
|
82
|
+
L ${x - width / 2 + 30} ${y - height / 2}
|
|
83
|
+
L ${x - width / 2 + 40} ${y + height / 2}
|
|
84
|
+
L ${x - width / 2 + 50} ${y - height / 2}
|
|
85
|
+
L ${x + width / 2} ${y}
|
|
86
|
+
`;
|
|
87
|
+
elements.push(path(zigzagPath, {
|
|
88
|
+
fill: 'none',
|
|
89
|
+
stroke: '#333',
|
|
90
|
+
'stroke-width': 2,
|
|
91
|
+
}));
|
|
92
|
+
// Label
|
|
93
|
+
if (label || value) {
|
|
94
|
+
const labelText = label || value || '';
|
|
95
|
+
elements.push(text(x + 40, y - 15, labelText, {
|
|
96
|
+
'text-anchor': 'middle',
|
|
97
|
+
'font-size': 12,
|
|
98
|
+
fill: '#333',
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
return svgTag('g', { class: 'resistor', transform }, elements.join(''));
|
|
102
|
+
}
|
|
103
|
+
function renderSwitch(comp) {
|
|
104
|
+
const { x, y, rotation = 0, label } = comp;
|
|
105
|
+
const size = 40;
|
|
106
|
+
const transform = rotation ? `transform="rotate(${rotation} ${x} ${y})"` : '';
|
|
107
|
+
const elements = [];
|
|
108
|
+
// Terminals
|
|
109
|
+
elements.push(circle(x - size / 2, y, 3, { fill: '#333' }));
|
|
110
|
+
elements.push(circle(x + size / 2, y, 3, { fill: '#333' }));
|
|
111
|
+
// Open switch (angled line)
|
|
112
|
+
elements.push(line(x - size / 2, y, x + size / 2 - 5, y - 15, {
|
|
113
|
+
stroke: '#333',
|
|
114
|
+
'stroke-width': 2,
|
|
115
|
+
}));
|
|
116
|
+
// Label
|
|
117
|
+
if (label) {
|
|
118
|
+
elements.push(text(x, y - 25, label, {
|
|
119
|
+
'text-anchor': 'middle',
|
|
120
|
+
'font-size': 12,
|
|
121
|
+
fill: '#333',
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
return svgTag('g', { class: 'switch', transform }, elements.join(''));
|
|
125
|
+
}
|
|
126
|
+
function renderBulb(comp) {
|
|
127
|
+
const { x, y, rotation = 0, label } = comp;
|
|
128
|
+
const radius = 20;
|
|
129
|
+
const transform = rotation ? `transform="rotate(${rotation} ${x} ${y})"` : '';
|
|
130
|
+
const elements = [];
|
|
131
|
+
// Glass bulb
|
|
132
|
+
elements.push(circle(x, y, radius, {
|
|
133
|
+
fill: '#fff9e6',
|
|
134
|
+
stroke: '#333',
|
|
135
|
+
'stroke-width': 2,
|
|
136
|
+
}));
|
|
137
|
+
// Filament
|
|
138
|
+
elements.push(path(`M ${x - 8} ${y - 10} L ${x - 5} ${y} L ${x + 5} ${y} L ${x + 8} ${y - 10}`, {
|
|
139
|
+
fill: 'none',
|
|
140
|
+
stroke: '#666',
|
|
141
|
+
'stroke-width': 1,
|
|
142
|
+
}));
|
|
143
|
+
// Base terminals
|
|
144
|
+
elements.push(rect(x - 6, y + radius, 12, 8, {
|
|
145
|
+
fill: '#999',
|
|
146
|
+
stroke: '#333',
|
|
147
|
+
'stroke-width': 1,
|
|
148
|
+
}));
|
|
149
|
+
// Label
|
|
150
|
+
if (label) {
|
|
151
|
+
elements.push(text(x + radius + 10, y, label, {
|
|
152
|
+
'text-anchor': 'start',
|
|
153
|
+
'dominant-baseline': 'middle',
|
|
154
|
+
'font-size': 12,
|
|
155
|
+
fill: '#333',
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
return svgTag('g', { class: 'bulb', transform }, elements.join(''));
|
|
159
|
+
}
|
|
160
|
+
function renderWire(comp) {
|
|
161
|
+
const { x, y, rotation = 0 } = comp;
|
|
162
|
+
const transform = rotation ? `transform="rotate(${rotation} ${x} ${y})"` : '';
|
|
163
|
+
// Simple wire (line)
|
|
164
|
+
return svgTag('g', { class: 'wire', transform }, line(x - 20, y, x + 20, y, {
|
|
165
|
+
stroke: '#333',
|
|
166
|
+
'stroke-width': 2,
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
function renderAmmeter(comp) {
|
|
170
|
+
const { x, y, rotation = 0, label } = comp;
|
|
171
|
+
const radius = 18;
|
|
172
|
+
const transform = rotation ? `transform="rotate(${rotation} ${x} ${y})"` : '';
|
|
173
|
+
const elements = [];
|
|
174
|
+
// Circle
|
|
175
|
+
elements.push(circle(x, y, radius, {
|
|
176
|
+
fill: '#e6f7ff',
|
|
177
|
+
stroke: '#333',
|
|
178
|
+
'stroke-width': 2,
|
|
179
|
+
}));
|
|
180
|
+
// 'A' label
|
|
181
|
+
elements.push(text(x, y, 'A', {
|
|
182
|
+
'text-anchor': 'middle',
|
|
183
|
+
'dominant-baseline': 'middle',
|
|
184
|
+
'font-size': 16,
|
|
185
|
+
'font-weight': 'bold',
|
|
186
|
+
fill: '#333',
|
|
187
|
+
}));
|
|
188
|
+
// Custom label
|
|
189
|
+
if (label) {
|
|
190
|
+
elements.push(text(x + radius + 10, y, label, {
|
|
191
|
+
'text-anchor': 'start',
|
|
192
|
+
'dominant-baseline': 'middle',
|
|
193
|
+
'font-size': 12,
|
|
194
|
+
fill: '#333',
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
return svgTag('g', { class: 'ammeter', transform }, elements.join(''));
|
|
198
|
+
}
|
|
199
|
+
function renderVoltmeter(comp) {
|
|
200
|
+
const { x, y, rotation = 0, label } = comp;
|
|
201
|
+
const radius = 18;
|
|
202
|
+
const transform = rotation ? `transform="rotate(${rotation} ${x} ${y})"` : '';
|
|
203
|
+
const elements = [];
|
|
204
|
+
// Circle
|
|
205
|
+
elements.push(circle(x, y, radius, {
|
|
206
|
+
fill: '#fff0e6',
|
|
207
|
+
stroke: '#333',
|
|
208
|
+
'stroke-width': 2,
|
|
209
|
+
}));
|
|
210
|
+
// 'V' label
|
|
211
|
+
elements.push(text(x, y, 'V', {
|
|
212
|
+
'text-anchor': 'middle',
|
|
213
|
+
'dominant-baseline': 'middle',
|
|
214
|
+
'font-size': 16,
|
|
215
|
+
'font-weight': 'bold',
|
|
216
|
+
fill: '#333',
|
|
217
|
+
}));
|
|
218
|
+
// Custom label
|
|
219
|
+
if (label) {
|
|
220
|
+
elements.push(text(x + radius + 10, y, label, {
|
|
221
|
+
'text-anchor': 'start',
|
|
222
|
+
'dominant-baseline': 'middle',
|
|
223
|
+
'font-size': 12,
|
|
224
|
+
fill: '#333',
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
return svgTag('g', { class: 'voltmeter', transform }, elements.join(''));
|
|
228
|
+
}
|
|
229
|
+
function createArrowMarkers() {
|
|
230
|
+
return `
|
|
231
|
+
<defs>
|
|
232
|
+
<marker id="arrow-default" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto">
|
|
233
|
+
<path d="M0,0 L10,5 L0,10" fill="#333" />
|
|
234
|
+
</marker>
|
|
235
|
+
</defs>
|
|
236
|
+
`;
|
|
237
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coordinatePlane.d.ts","sourceRoot":"","sources":["../../src/renderers/coordinatePlane.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAuF,MAAM,UAAU,CAAC;AAMxI,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,kBAAkB,GAAG,MAAM,CA6CrE"}
|