@xrn07/figure-renderer 0.1.3 → 0.2.1

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/README.md CHANGED
@@ -2,13 +2,18 @@
2
2
 
3
3
  Zero-dependency pure TypeScript figure renderer - pure SVG string generation.
4
4
 
5
+ > **⚠️ Version 0.2.0 Breaking Change**
6
+ > This version introduces a new flexible DSL format. See [MIGRATION_V0.2.0.md](MIGRATION_V0.2.0.md) for details.
7
+
5
8
  ## Features
6
9
 
7
10
  - ✅ **Zero runtime dependencies** - Only ~8KB minified
8
11
  - ✅ **Universal** - Works in Node.js, browser, Next.js SSR, PDF export
9
12
  - ✅ **Type-safe** - Full TypeScript support
13
+ - ✅ **Flexible** - Generic type system supports any figure type
10
14
  - ✅ **Testable** - SVG strings are easily snapshot-able
11
15
  - ✅ **Accessible** - React component with proper ARIA labels
16
+ - ✅ **International** - Localization support via `alt` field
12
17
 
13
18
  ## Installation
14
19
 
@@ -74,27 +79,74 @@ React wrapper component for rendering figures.
74
79
 
75
80
  ## Supported Figure Types
76
81
 
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
82
+ - **`force_diagram`** - Force vector diagrams with compass directions ✨ **NEW**
83
+ - More types coming soon (extensible via custom renderers)
86
84
 
87
- ## DSL Schema
85
+ ## DSL Schema (v0.2.0)
88
86
 
89
- Each figure type has its own DSL structure. All share common base properties:
87
+ The DSL is now **generic and flexible**. All figures share common base properties:
90
88
 
91
89
  ```typescript
92
90
  interface FigureDSL {
93
- type: FigureType;
94
- width: number;
95
- height: number;
91
+ type: string; // Generic - any figure type supported
92
+ width?: number; // Optional - defaults to 400
93
+ height?: number; // Optional - defaults to 300
96
94
  title?: string;
97
- alt?: string;
95
+ alt?: string; // Localization support
96
+ [key: string]: any; // Additional properties per figure type
97
+ }
98
+ ```
99
+
100
+ ### Force Diagram DSL
101
+
102
+ ```json
103
+ {
104
+ "type": "force_diagram",
105
+ "alt": "বস্তুটির উপর ক্রিয়াশীল বলগুলোর চিত্র",
106
+ "width": 400,
107
+ "height": 300,
108
+ "arrows": [
109
+ {
110
+ "direction": "down",
111
+ "label": "মাধ্যাকর্ষণ",
112
+ "magnitude": "100 N",
113
+ "unit": "N",
114
+ "color": "#dc2626"
115
+ },
116
+ {
117
+ "direction": "up",
118
+ "label": "সমতলের প্রতিক্রিয়া বল",
119
+ "magnitude": "100 N",
120
+ "unit": "N",
121
+ "color": "#2563eb"
122
+ },
123
+ {
124
+ "direction": "right",
125
+ "label": "ঘর্ষণ বল",
126
+ "magnitude": "50 N",
127
+ "unit": "N",
128
+ "color": "#16a34a"
129
+ }
130
+ ]
131
+ }
132
+ ```
133
+
134
+ **Compass Directions:** `up`, `down`, `left`, `right`, `up-left`, `up-right`, `down-left`, `down-right`
135
+
136
+ **Optional Object:**
137
+
138
+ ```json
139
+ {
140
+ "type": "force_diagram",
141
+ "object": {
142
+ "type": "box",
143
+ "x": 200,
144
+ "y": 150,
145
+ "width": 60,
146
+ "height": 60,
147
+ "label": "5kg"
148
+ },
149
+ "arrows": [...]
98
150
  }
99
151
  ```
100
152
 
package/dist/parse.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import type { FigureDSL } from './types';
2
2
  /**
3
- * Safely parse FigureDSL from JSON string
3
+ * Safely parse FigureDSL from JSON string or object
4
4
  * Returns null if parsing or validation fails
5
5
  */
6
- export declare function parseFigureDSL(jsonString: string): FigureDSL | null;
6
+ export declare function parseFigureDSL(input: string | object): FigureDSL | null;
7
7
  /**
8
8
  * Validate a FigureDSL object
9
9
  */
@@ -1 +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"}
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAS,MAAM,SAAS,CAAC;AAuEhD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,CAwBvE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,SAAS,CAahE"}
package/dist/parse.js CHANGED
@@ -1,23 +1,84 @@
1
1
  // Safe DSL parser with Zod validation
2
2
  import { z } from 'zod';
3
3
  /**
4
- * Zod schema for FigureDSL validation
4
+ * Compass direction validation
5
+ */
6
+ const compassDirectionSchema = z.enum(['up', 'down', 'left', 'right', 'up-left', 'up-right', 'down-left', 'down-right']);
7
+ /**
8
+ * Arrow validation schema
9
+ */
10
+ const arrowSchema = z.object({
11
+ direction: compassDirectionSchema,
12
+ label: z.string(),
13
+ magnitude: z.union([z.string(), z.number()]), // Accept both string and number
14
+ unit: z.string().optional(),
15
+ color: z.string().optional()
16
+ });
17
+ /**
18
+ * Base FigureDSL validation schema - flexible and generic
5
19
  */
6
20
  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(),
21
+ type: z.string(), // Generic type - any string allowed
22
+ width: z.number().optional(), // Optional with defaults
23
+ height: z.number().optional(), // Optional with defaults
10
24
  title: z.string().optional(),
11
25
  alt: z.string().optional(),
12
- // Allow additional properties for specific figure types
26
+ // Allow additional properties for flexibility
13
27
  }).passthrough());
14
28
  /**
15
- * Safely parse FigureDSL from JSON string
29
+ * Force diagram specific validation
30
+ */
31
+ const forceDiagramSchema = z.object({
32
+ type: z.literal('force_diagram'),
33
+ width: z.number().optional(),
34
+ height: z.number().optional(),
35
+ title: z.string().optional(),
36
+ alt: z.string().optional(),
37
+ arrows: z.array(arrowSchema),
38
+ object: z.object({
39
+ type: z.enum(['box', 'sphere', 'point']),
40
+ x: z.number().optional(),
41
+ y: z.number().optional(),
42
+ width: z.number().optional(),
43
+ height: z.number().optional(),
44
+ radius: z.number().optional(),
45
+ label: z.string().optional()
46
+ }).optional()
47
+ }).passthrough();
48
+ /**
49
+ * Normalize API response fields to DSL format
50
+ */
51
+ function normalizeAPIResponse(obj) {
52
+ if (!obj || typeof obj !== 'object') {
53
+ return obj;
54
+ }
55
+ const normalized = { ...obj };
56
+ // Map alt_bn to alt
57
+ if (normalized.alt_bn && !normalized.alt) {
58
+ normalized.alt = normalized.alt_bn;
59
+ }
60
+ return normalized;
61
+ }
62
+ /**
63
+ * Safely parse FigureDSL from JSON string or object
16
64
  * Returns null if parsing or validation fails
17
65
  */
18
- export function parseFigureDSL(jsonString) {
66
+ export function parseFigureDSL(input) {
19
67
  try {
20
- const parsed = JSON.parse(jsonString);
68
+ let parsed;
69
+ if (typeof input === 'string') {
70
+ parsed = JSON.parse(input);
71
+ }
72
+ else {
73
+ parsed = input;
74
+ }
75
+ // Normalize API response fields
76
+ parsed = normalizeAPIResponse(parsed);
77
+ // Detect type and use appropriate schema
78
+ if (parsed.type === 'force_diagram') {
79
+ return forceDiagramSchema.parse(parsed);
80
+ }
81
+ // Use generic schema for other types
21
82
  return figureDSLSchema.parse(parsed);
22
83
  }
23
84
  catch (error) {
@@ -30,8 +91,12 @@ export function parseFigureDSL(jsonString) {
30
91
  */
31
92
  export function validateFigureDSL(obj) {
32
93
  try {
33
- figureDSLSchema.parse(obj);
34
- return true;
94
+ const parsed = obj;
95
+ // Detect type and use appropriate schema
96
+ if (parsed?.type === 'force_diagram') {
97
+ return forceDiagramSchema.safeParse(parsed).success;
98
+ }
99
+ return figureDSLSchema.safeParse(obj).success;
35
100
  }
36
101
  catch {
37
102
  return false;
@@ -0,0 +1,42 @@
1
+ import type { FigureDSL, PhysicalObject } from './types';
2
+ /**
3
+ * Default configuration for force diagrams
4
+ */
5
+ export interface ForceDiagramDefaults {
6
+ width: number;
7
+ height: number;
8
+ object: PhysicalObject;
9
+ arrowLength: number;
10
+ arrowHeadSize: number;
11
+ colors: {
12
+ object: string;
13
+ arrowDefault: string;
14
+ arrowText: string;
15
+ };
16
+ }
17
+ /**
18
+ * Global defaults configuration
19
+ */
20
+ export interface FigureDefaults {
21
+ force_diagram: ForceDiagramDefaults;
22
+ }
23
+ /**
24
+ * Default props for force diagrams
25
+ */
26
+ export declare const defaultForceDiagramProps: ForceDiagramDefaults;
27
+ /**
28
+ * Get default props for a specific figure type
29
+ */
30
+ export declare function getDefaults(type: string): Partial<FigureDefaults[keyof FigureDefaults]>;
31
+ /**
32
+ * Merge user config with defaults
33
+ */
34
+ export declare function withDefaults<T extends FigureDSL>(config: T, defaults?: Partial<FigureDefaults[keyof FigureDefaults]>): T;
35
+ /**
36
+ * Calculate center position for object
37
+ */
38
+ export declare function calculateCenter(width: number, height: number): {
39
+ x: number;
40
+ y: number;
41
+ };
42
+ //# sourceMappingURL=props.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"props.d.ts","sourceRoot":"","sources":["../src/props.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,oBAAoB,CAAC;CACrC;AAED;;GAEG;AACH,eAAO,MAAM,wBAAwB,EAAE,oBAgBtC,CAAC;AAEF;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,MAAM,cAAc,CAAC,CAAC,CAMvF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,SAAS,EAC9C,MAAM,EAAE,CAAC,EACT,QAAQ,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,MAAM,cAAc,CAAC,CAAC,GACvD,CAAC,CAWH;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAKvF"}
package/dist/props.js ADDED
@@ -0,0 +1,52 @@
1
+ // Default props system for figure rendering
2
+ /**
3
+ * Default props for force diagrams
4
+ */
5
+ export const defaultForceDiagramProps = {
6
+ width: 400,
7
+ height: 300,
8
+ object: {
9
+ type: 'box',
10
+ width: 60,
11
+ height: 60,
12
+ label: ''
13
+ },
14
+ arrowLength: 60,
15
+ arrowHeadSize: 10,
16
+ colors: {
17
+ object: '#374151',
18
+ arrowDefault: '#dc2626',
19
+ arrowText: '#1f2937'
20
+ }
21
+ };
22
+ /**
23
+ * Get default props for a specific figure type
24
+ */
25
+ export function getDefaults(type) {
26
+ const defaults = {
27
+ force_diagram: defaultForceDiagramProps
28
+ };
29
+ return defaults[type] || {};
30
+ }
31
+ /**
32
+ * Merge user config with defaults
33
+ */
34
+ export function withDefaults(config, defaults) {
35
+ const typeDefaults = getDefaults(config.type);
36
+ // Merge defaults with user config
37
+ const merged = {
38
+ ...config,
39
+ width: config.width ?? (defaults?.width ?? typeDefaults?.width ?? 400),
40
+ height: config.height ?? (defaults?.height ?? typeDefaults?.height ?? 300)
41
+ };
42
+ return merged;
43
+ }
44
+ /**
45
+ * Calculate center position for object
46
+ */
47
+ export function calculateCenter(width, height) {
48
+ return {
49
+ x: width / 2,
50
+ y: height / 2
51
+ };
52
+ }
package/dist/render.d.ts CHANGED
@@ -1,4 +1,10 @@
1
1
  import type { FigureDSL } from './types';
2
+ /**
3
+ * Register a custom renderer for a specific figure type
4
+ * @param type - Figure type identifier
5
+ * @param renderer - Rendering function that takes DSL and returns SVG string
6
+ */
7
+ export declare function registerRenderer(type: string, renderer: (dsl: any) => string): void;
2
8
  /**
3
9
  * Main rendering function - dispatches to appropriate renderer based on DSL type
4
10
  * @param dsl - Figure DSL object
@@ -1 +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"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAazC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,GAAG,IAAI,CAEnF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAanD;AAGD,mBAAmB,SAAS,CAAC;AAG7B,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC"}
package/dist/render.js CHANGED
@@ -1,41 +1,36 @@
1
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';
2
+ import { renderForceDiagramV2 } from './renderers/forceDiagram';
3
+ import { withDefaults } from './props';
4
+ /**
5
+ * Renderer registry for dynamic renderer lookup
6
+ * Allows adding new renderers without modifying the switch statement
7
+ */
8
+ const renderRegistry = new Map();
9
+ // Register built-in renderers
10
+ renderRegistry.set('force_diagram', renderForceDiagramV2);
11
+ /**
12
+ * Register a custom renderer for a specific figure type
13
+ * @param type - Figure type identifier
14
+ * @param renderer - Rendering function that takes DSL and returns SVG string
15
+ */
16
+ export function registerRenderer(type, renderer) {
17
+ renderRegistry.set(type, renderer);
18
+ }
11
19
  /**
12
20
  * Main rendering function - dispatches to appropriate renderer based on DSL type
13
21
  * @param dsl - Figure DSL object
14
22
  * @returns SVG string
15
23
  */
16
24
  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}`);
25
+ // Apply defaults if dimensions are not specified
26
+ const config = withDefaults(dsl);
27
+ // Check if we have a registered renderer for this type
28
+ const renderer = renderRegistry.get(config.type);
29
+ if (renderer) {
30
+ return renderer(config);
38
31
  }
32
+ // For unregistered types, throw a helpful error
33
+ throw new Error(`Unknown figure type: "${config.type}". Register a renderer using registerRenderer("${config.type}", renderer)`);
39
34
  }
40
35
  // Re-export parser
41
36
  export { parseFigureDSL, validateFigureDSL } from './parse';
@@ -1,3 +1,6 @@
1
1
  import type { ForceDiagramDSL } from '../types';
2
- export declare function renderForceDiagram(dsl: ForceDiagramDSL): string;
2
+ /**
3
+ * Render force diagram with new compass-based format
4
+ */
5
+ export declare function renderForceDiagramV2(dsl: ForceDiagramDSL): string;
3
6
  //# sourceMappingURL=forceDiagram.d.ts.map
@@ -1 +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"}
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;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAiDjE"}
@@ -1,31 +1,64 @@
1
- // Force diagram renderer
1
+ // Force diagram renderer (new format with compass directions)
2
2
  import { svgTag } from '../utils/svgTag';
3
3
  import { rect, circle, text, line } from '../utils/shapes';
4
- export function renderForceDiagram(dsl) {
5
- const { width, height, object, forces, title } = dsl;
4
+ import { compassToAngle, parseMagnitude, calculateArrowEndpoint, calculateArrowStart } from '../utils/compass';
5
+ /**
6
+ * Render force diagram with new compass-based format
7
+ */
8
+ export function renderForceDiagramV2(dsl) {
9
+ const { width = 400, height = 300, arrows = [], object, title, alt } = dsl;
10
+ // Use default object if not specified
11
+ const defaultObject = {
12
+ type: 'box',
13
+ x: width / 2,
14
+ y: height / 2,
15
+ width: 60,
16
+ height: 60
17
+ };
18
+ const obj = object || defaultObject;
19
+ const centerX = obj.x ?? width / 2;
20
+ const centerY = obj.y ?? height / 2;
6
21
  const elements = [];
7
- // Add arrow markers
8
- elements.push(createForceMarkers());
22
+ // Add arrow markers for all colors used
23
+ const colors = [...new Set(arrows.map(a => a.color).filter(Boolean))];
24
+ if (colors.length === 0)
25
+ colors.push('#dc2626'); // Default color
26
+ elements.push(createArrowMarkers(colors));
9
27
  // Render object
10
- elements.push(renderObject(object));
11
- // Render forces
12
- forces.forEach(force => {
13
- elements.push(renderForce(force));
28
+ elements.push(renderObject(obj, width, height));
29
+ // Render arrows
30
+ arrows.forEach(arrow => {
31
+ elements.push(renderArrow(arrow, centerX, centerY));
14
32
  });
33
+ // Add title if present
34
+ if (title) {
35
+ elements.push(text(width / 2, 20, title, {
36
+ 'text-anchor': 'middle',
37
+ 'font-size': 18,
38
+ 'font-weight': 'bold',
39
+ fill: '#333',
40
+ }));
41
+ }
15
42
  const svg = svgTag('svg', {
16
43
  viewBox: `0 0 ${width} ${height}`,
17
44
  xmlns: 'http://www.w3.org/2000/svg',
45
+ role: 'img',
46
+ 'aria-label': alt || title || 'Force diagram',
18
47
  }, elements.join(''));
19
48
  return svg;
20
49
  }
21
- function renderObject(obj) {
50
+ function renderObject(obj, canvasWidth, canvasHeight) {
22
51
  const elements = [];
52
+ const x = obj.x ?? canvasWidth / 2;
53
+ const y = obj.y ?? canvasHeight / 2;
23
54
  switch (obj.type) {
24
55
  case 'box': {
25
- const { x, y, width: w = 60, height: h = 60, label } = obj;
56
+ const w = obj.width ?? 60;
57
+ const h = obj.height ?? 60;
58
+ const label = obj.label;
26
59
  elements.push(rect(x - w / 2, y - h / 2, w, h, {
27
- fill: '#f0f0f0',
28
- stroke: '#333',
60
+ fill: '#f3f4f6',
61
+ stroke: '#374151',
29
62
  'stroke-width': 2,
30
63
  }));
31
64
  if (label) {
@@ -33,17 +66,17 @@ function renderObject(obj) {
33
66
  'text-anchor': 'middle',
34
67
  'dominant-baseline': 'middle',
35
68
  'font-size': 14,
36
- fill: '#333',
69
+ fill: '#1f2937',
37
70
  }));
38
71
  }
39
72
  break;
40
73
  }
41
74
  case 'sphere': {
42
- const { x, y, label } = obj;
43
- const radius = 30;
75
+ const radius = obj.radius ?? 30;
76
+ const label = obj.label;
44
77
  elements.push(circle(x, y, radius, {
45
- fill: '#e6f7ff',
46
- stroke: '#333',
78
+ fill: '#dbeafe',
79
+ stroke: '#1e40af',
47
80
  'stroke-width': 2,
48
81
  }));
49
82
  if (label) {
@@ -51,22 +84,22 @@ function renderObject(obj) {
51
84
  'text-anchor': 'middle',
52
85
  'dominant-baseline': 'middle',
53
86
  'font-size': 12,
54
- fill: '#333',
87
+ fill: '#1e3a8a',
55
88
  }));
56
89
  }
57
90
  break;
58
91
  }
59
92
  case 'point': {
60
- const { x, y, label } = obj;
93
+ const label = obj.label;
61
94
  elements.push(circle(x, y, 5, {
62
- fill: '#333',
95
+ fill: '#1f2937',
63
96
  }));
64
97
  if (label) {
65
98
  elements.push(text(x + 10, y, label, {
66
99
  'text-anchor': 'start',
67
100
  'dominant-baseline': 'middle',
68
101
  'font-size': 12,
69
- fill: '#333',
102
+ fill: '#374151',
70
103
  }));
71
104
  }
72
105
  break;
@@ -74,57 +107,52 @@ function renderObject(obj) {
74
107
  }
75
108
  return svgTag('g', { class: 'object' }, elements.join(''));
76
109
  }
77
- function renderForce(force) {
78
- const { originX, originY, magnitude, angle, label, color = '#e53e3e' } = force;
110
+ function renderArrow(arrow, centerX, centerY) {
111
+ const { direction, label, magnitude, color = '#dc2626' } = arrow;
79
112
  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, {
113
+ // Parse magnitude to get value and unit
114
+ const { value: magnitudeValue, unit: magnitudeUnit } = parseMagnitude(magnitude);
115
+ // Calculate arrow length based on magnitude (with minimum and maximum)
116
+ const arrowLength = Math.max(40, Math.min(100, magnitudeValue * 0.8));
117
+ // Convert compass direction to angle
118
+ const angle = compassToAngle(direction);
119
+ // Calculate start and end points
120
+ const start = calculateArrowStart(centerX, centerY, direction, arrowLength);
121
+ const end = calculateArrowEndpoint(centerX, centerY, direction, arrowLength);
122
+ // Draw arrow line
123
+ elements.push(line(start.x, start.y, end.x, end.y, {
87
124
  stroke: color,
88
125
  'stroke-width': 3,
89
- 'marker-end': `url(#arrow-${color})`,
126
+ 'marker-end': `url(#arrow-${color.replace('#', '')})`,
90
127
  }));
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(''));
128
+ // Format magnitude for display (add unit if not present)
129
+ const magnitudeDisplay = magnitudeUnit ? `${magnitudeValue} ${magnitudeUnit}` : `${magnitudeValue}`;
130
+ // Draw label with magnitude
131
+ const labelText = label ? `${label} (${magnitudeDisplay})` : magnitudeDisplay;
132
+ // Calculate label position (offset from arrow end)
133
+ const angleRad = (angle * Math.PI) / 180;
134
+ const labelOffset = 20;
135
+ const labelX = end.x + labelOffset * Math.cos(angleRad);
136
+ const labelY = end.y + labelOffset * Math.sin(angleRad);
137
+ elements.push(text(labelX, labelY, labelText, {
138
+ 'text-anchor': 'start',
139
+ 'dominant-baseline': 'middle',
140
+ 'font-size': 14,
141
+ 'font-weight': '600',
142
+ fill: color,
143
+ }));
144
+ return svgTag('g', { class: 'arrow' }, elements.join(''));
106
145
  }
107
- function createForceMarkers() {
146
+ function createArrowMarkers(colors) {
147
+ const markers = colors.map(color => {
148
+ const colorId = color.replace('#', '');
149
+ return ` <marker id="arrow-${colorId}" markerWidth="10" markerHeight="10" refX="9" refY="5" orient="auto">
150
+ <path d="M0,0 L10,5 L0,10" fill="${color}" />
151
+ </marker>`;
152
+ }).join('\n');
108
153
  return `
109
154
  <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>
155
+ ${markers}
128
156
  </defs>
129
157
  `;
130
158
  }
package/dist/types.d.ts CHANGED
@@ -1,225 +1,42 @@
1
1
  /**
2
- * Base figure DSL structure
2
+ * Base figure DSL structure - generic and flexible
3
3
  */
4
4
  export interface FigureDSL {
5
- type: FigureType;
6
- width: number;
7
- height: number;
5
+ type: string;
6
+ width?: number;
7
+ height?: number;
8
8
  title?: string;
9
9
  alt?: string;
10
+ [key: string]: any;
10
11
  }
11
12
  /**
12
- * Supported figure types
13
- */
14
- export type FigureType = 'circuit' | 'rayDiagram' | 'forceDiagram' | 'geometry' | 'coordinatePlane' | 'numberLine' | 'graph' | 'venn' | 'bioTable';
15
- /**
16
- * Circuit diagram DSL
13
+ * Force diagram DSL (new format)
17
14
  */
18
- export interface CircuitDSL extends FigureDSL {
19
- type: 'circuit';
20
- components: CircuitComponent[];
21
- }
22
- export interface CircuitComponent {
23
- type: 'battery' | 'resistor' | 'switch' | 'bulb' | 'wire' | 'ammeter' | 'voltmeter';
24
- x: number;
25
- y: number;
26
- rotation?: number;
27
- label?: string;
28
- value?: string;
15
+ export interface ForceDiagramDSL extends FigureDSL {
16
+ type: 'force_diagram';
17
+ arrows: Arrow[];
18
+ object?: PhysicalObject;
29
19
  }
30
20
  /**
31
- * Ray diagram DSL
21
+ * Arrow configuration with compass directions
32
22
  */
33
- export interface RayDiagramDSL extends FigureDSL {
34
- type: 'rayDiagram';
35
- opticalElements: OpticalElement[];
36
- rays: Ray[];
37
- }
38
- export interface OpticalElement {
39
- type: 'lens' | 'mirror' | 'prism' | 'screen';
40
- x: number;
41
- y: number;
42
- width?: number;
43
- height?: number;
44
- focalLength?: number;
45
- curvature?: string;
46
- }
47
- export interface Ray {
48
- startX: number;
49
- startY: number;
50
- angle: number;
23
+ export interface Arrow {
24
+ direction: 'up' | 'down' | 'left' | 'right' | 'up-left' | 'up-right' | 'down-left' | 'down-right';
25
+ label: string;
26
+ magnitude: string | number;
27
+ unit?: string;
51
28
  color?: string;
52
- label?: string;
53
29
  }
54
30
  /**
55
- * Force diagram DSL
31
+ * Physical object definition
56
32
  */
57
- export interface ForceDiagramDSL extends FigureDSL {
58
- type: 'forceDiagram';
59
- object: PhysicalObject;
60
- forces: Force[];
61
- }
62
33
  export interface PhysicalObject {
63
34
  type: 'box' | 'sphere' | 'point';
64
- x: number;
65
- y: number;
35
+ x?: number;
36
+ y?: number;
66
37
  width?: number;
67
38
  height?: number;
39
+ radius?: number;
68
40
  label?: string;
69
41
  }
70
- export interface Force {
71
- originX: number;
72
- originY: number;
73
- magnitude: number;
74
- angle: number;
75
- label: string;
76
- color?: string;
77
- }
78
- /**
79
- * Geometry DSL
80
- */
81
- export interface GeometryDSL extends FigureDSL {
82
- type: 'geometry';
83
- shapes: GeometryShape[];
84
- }
85
- export type GeometryShape = {
86
- type: 'line';
87
- x1: number;
88
- y1: number;
89
- x2: number;
90
- y2: number;
91
- label?: string;
92
- } | {
93
- type: 'circle';
94
- cx: number;
95
- cy: number;
96
- r: number;
97
- label?: string;
98
- } | {
99
- type: 'rect';
100
- x: number;
101
- y: number;
102
- width: number;
103
- height: number;
104
- label?: string;
105
- } | {
106
- type: 'triangle';
107
- points: [number, number][];
108
- label?: string;
109
- } | {
110
- type: 'angle';
111
- vertex: [number, number];
112
- rays: [[number, number], [number, number]];
113
- label?: string;
114
- };
115
- /**
116
- * Coordinate plane DSL
117
- */
118
- export interface CoordinatePlaneDSL extends FigureDSL {
119
- type: 'coordinatePlane';
120
- xRange: [number, number];
121
- yRange: [number, number];
122
- showGrid?: boolean;
123
- showLabels?: boolean;
124
- elements: PlaneElement[];
125
- }
126
- export interface PlaneElement {
127
- type: 'point' | 'line' | 'curve' | 'label';
128
- data: PointElementData | LineElementData | CurveElementData | LabelElementData;
129
- }
130
- export interface PointElementData {
131
- x: number;
132
- y: number;
133
- label?: string;
134
- color?: string;
135
- }
136
- export interface LineElementData {
137
- x1: number;
138
- y1: number;
139
- x2: number;
140
- y2: number;
141
- color?: string;
142
- style?: 'solid' | 'dashed' | 'dotted';
143
- }
144
- export interface CurveElementData {
145
- points: [number, number][];
146
- color?: string;
147
- style?: 'solid' | 'dashed' | 'dotted';
148
- }
149
- export interface LabelElementData {
150
- x: number;
151
- y: number;
152
- text: string;
153
- align?: 'left' | 'center' | 'right';
154
- }
155
- /**
156
- * Number line DSL
157
- */
158
- export interface NumberLineDSL extends FigureDSL {
159
- type: 'numberLine';
160
- range: [number, number];
161
- ticks: number[];
162
- marks: NumberLineMark[];
163
- arrowEnds?: 'left' | 'right' | 'both' | 'none';
164
- }
165
- export interface NumberLineMark {
166
- value: number;
167
- label: string;
168
- style?: 'open' | 'closed' | 'arrow';
169
- }
170
- /**
171
- * Graph DSL
172
- */
173
- export interface GraphDSL extends FigureDSL {
174
- type: 'graph';
175
- xRange: [number, number];
176
- yRange: [number, number];
177
- functions: GraphFunction[];
178
- }
179
- export interface GraphFunction {
180
- equation: string;
181
- color: string;
182
- style?: 'solid' | 'dashed' | 'dotted';
183
- }
184
- /**
185
- * Venn diagram DSL
186
- */
187
- export interface VennDSL extends FigureDSL {
188
- type: 'venn';
189
- circles: VennCircle[];
190
- labels: VennLabel[];
191
- }
192
- export interface VennCircle {
193
- label: string;
194
- cx: number;
195
- cy: number;
196
- r: number;
197
- color: string;
198
- }
199
- export interface VennLabel {
200
- text: string;
201
- x: number;
202
- y: number;
203
- region: string;
204
- }
205
- /**
206
- * Biology table DSL
207
- */
208
- export interface BioTableDSL extends FigureDSL {
209
- type: 'bioTable';
210
- columns: BioColumn[];
211
- rows: BioRow[];
212
- }
213
- export interface BioColumn {
214
- header: string;
215
- width: number;
216
- }
217
- export interface BioRow {
218
- cells: (string | BioCellImage)[];
219
- }
220
- export interface BioCellImage {
221
- type: 'image';
222
- src: string;
223
- alt: string;
224
- }
225
42
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,KAAK;IACpB,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,YAAY,CAAC;IAClG,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;IACjC,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
package/dist/types.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // DSL Type Definitions
2
- // All figure type interfaces will be defined here
2
+ // Flexible, generic type system supporting any figure type
3
3
  export {};
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Compass directions supported
3
+ */
4
+ export type CompassDirection = 'up' | 'down' | 'left' | 'right' | 'up-left' | 'up-right' | 'down-left' | 'down-right';
5
+ /**
6
+ * Convert compass direction to angle in degrees
7
+ */
8
+ export declare function compassToAngle(direction: CompassDirection): number;
9
+ /**
10
+ * Convert angle in degrees to compass direction
11
+ */
12
+ export declare function angleToCompass(angle: number): CompassDirection;
13
+ /**
14
+ * Parse magnitude string or number to extract numeric value and unit
15
+ * Examples: "100 N" -> { value: 100, unit: "N" }
16
+ * "50 kg" -> { value: 50, unit: "kg" }
17
+ * "25" -> { value: 25, unit: "" }
18
+ * 25 -> { value: 25, unit: "" }
19
+ */
20
+ export declare function parseMagnitude(magnitude: string | number): {
21
+ value: number;
22
+ unit: string;
23
+ };
24
+ /**
25
+ * Calculate arrow endpoint based on center, direction, and length
26
+ */
27
+ export declare function calculateArrowEndpoint(centerX: number, centerY: number, direction: CompassDirection, length: number): {
28
+ x: number;
29
+ y: number;
30
+ };
31
+ /**
32
+ * Calculate arrow start point (opposite of endpoint)
33
+ */
34
+ export declare function calculateArrowStart(centerX: number, centerY: number, direction: CompassDirection, length: number): {
35
+ x: number;
36
+ y: number;
37
+ };
38
+ //# sourceMappingURL=compass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compass.d.ts","sourceRoot":"","sources":["../../src/utils/compass.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,MAAM,gBAAgB,GACxB,IAAI,GACJ,MAAM,GACN,MAAM,GACN,OAAO,GACP,SAAS,GACT,UAAU,GACV,WAAW,GACX,YAAY,CAAC;AAEjB;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,gBAAgB,GAAG,MAAM,CAalE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,CAa9D;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAyB1F;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,gBAAgB,EAC3B,MAAM,EAAE,MAAM,GACb;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAQ1B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,gBAAgB,EAC3B,MAAM,EAAE,MAAM,GACb;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAQ1B"}
@@ -0,0 +1,90 @@
1
+ // Compass direction utilities for force diagram rendering
2
+ /**
3
+ * Convert compass direction to angle in degrees
4
+ */
5
+ export function compassToAngle(direction) {
6
+ const mapping = {
7
+ 'up': -90,
8
+ 'down': 90,
9
+ 'left': 180,
10
+ 'right': 0,
11
+ 'up-left': -135,
12
+ 'up-right': -45,
13
+ 'down-left': 135,
14
+ 'down-right': 45
15
+ };
16
+ return mapping[direction] ?? 0;
17
+ }
18
+ /**
19
+ * Convert angle in degrees to compass direction
20
+ */
21
+ export function angleToCompass(angle) {
22
+ const normalizedAngle = ((angle % 360) + 360) % 360;
23
+ if (normalizedAngle === 0 || normalizedAngle === 360)
24
+ return 'right';
25
+ if (normalizedAngle === 90)
26
+ return 'down';
27
+ if (normalizedAngle === 180)
28
+ return 'left';
29
+ if (normalizedAngle === 270)
30
+ return 'up';
31
+ if (normalizedAngle > 270 && normalizedAngle < 360)
32
+ return 'up-right';
33
+ if (normalizedAngle > 180 && normalizedAngle < 270)
34
+ return 'up-left';
35
+ if (normalizedAngle > 90 && normalizedAngle < 180)
36
+ return 'down-left';
37
+ if (normalizedAngle > 0 && normalizedAngle < 90)
38
+ return 'down-right';
39
+ return 'right'; // Default
40
+ }
41
+ /**
42
+ * Parse magnitude string or number to extract numeric value and unit
43
+ * Examples: "100 N" -> { value: 100, unit: "N" }
44
+ * "50 kg" -> { value: 50, unit: "kg" }
45
+ * "25" -> { value: 25, unit: "" }
46
+ * 25 -> { value: 25, unit: "" }
47
+ */
48
+ export function parseMagnitude(magnitude) {
49
+ // If it's already a number, return it
50
+ if (typeof magnitude === 'number') {
51
+ return { value: magnitude, unit: '' };
52
+ }
53
+ const trimmed = magnitude.trim();
54
+ // Try to extract numeric value and unit
55
+ const match = trimmed.match(/^(-?\d+\.?\d*)\s*(.*)$/);
56
+ if (match) {
57
+ return {
58
+ value: parseFloat(match[1]),
59
+ unit: match[2].trim()
60
+ };
61
+ }
62
+ // If no unit, just parse the number
63
+ const value = parseFloat(trimmed);
64
+ if (!isNaN(value)) {
65
+ return { value, unit: '' };
66
+ }
67
+ return { value: 0, unit: '' };
68
+ }
69
+ /**
70
+ * Calculate arrow endpoint based on center, direction, and length
71
+ */
72
+ export function calculateArrowEndpoint(centerX, centerY, direction, length) {
73
+ const angle = compassToAngle(direction);
74
+ const radians = (angle * Math.PI) / 180;
75
+ return {
76
+ x: centerX + length * Math.cos(radians),
77
+ y: centerY + length * Math.sin(radians)
78
+ };
79
+ }
80
+ /**
81
+ * Calculate arrow start point (opposite of endpoint)
82
+ */
83
+ export function calculateArrowStart(centerX, centerY, direction, length) {
84
+ const angle = compassToAngle(direction);
85
+ const radians = (angle * Math.PI) / 180;
86
+ return {
87
+ x: centerX - length * Math.cos(radians),
88
+ y: centerY - length * Math.sin(radians)
89
+ };
90
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xrn07/figure-renderer",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "Zero-dependency pure TypeScript figure renderer - pure SVG string generation",
5
5
  "main": "./dist/render.js",
6
6
  "module": "./dist/render.js",