create-imagine 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 (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +40 -0
  3. package/bin/create-imagine.js +407 -0
  4. package/package.json +19 -0
  5. package/templates/blank/README.md +96 -0
  6. package/templates/blank/gitignore +6 -0
  7. package/templates/blank/index.html +13 -0
  8. package/templates/blank/package.json +42 -0
  9. package/templates/blank/projects/example/figures/hello-world.tsx +45 -0
  10. package/templates/blank/projects/example/manifest.ts +44 -0
  11. package/templates/blank/projects/example/project.ts +16 -0
  12. package/templates/blank/projects/example/props.json +4 -0
  13. package/templates/blank/scripts/list.ts +46 -0
  14. package/templates/blank/scripts/projects.ts +43 -0
  15. package/templates/blank/scripts/render.ts +288 -0
  16. package/templates/blank/scripts/server.ts +117 -0
  17. package/templates/blank/src/core/__tests__/controls.test.ts +14 -0
  18. package/templates/blank/src/core/__tests__/manifest.test.ts +45 -0
  19. package/templates/blank/src/core/__tests__/props.test.ts +9 -0
  20. package/templates/blank/src/core/controls.ts +14 -0
  21. package/templates/blank/src/core/manifest.ts +134 -0
  22. package/templates/blank/src/core/props.ts +7 -0
  23. package/templates/blank/src/framework/Figure.tsx +34 -0
  24. package/templates/blank/src/framework/__tests__/sizing.test.ts +29 -0
  25. package/templates/blank/src/framework/charts/Axes.tsx +103 -0
  26. package/templates/blank/src/framework/charts/GridLines.tsx +59 -0
  27. package/templates/blank/src/framework/charts/Legend.tsx +31 -0
  28. package/templates/blank/src/framework/charts/Series.tsx +50 -0
  29. package/templates/blank/src/framework/charts/scales.ts +19 -0
  30. package/templates/blank/src/framework/diagrams/primitives.tsx +134 -0
  31. package/templates/blank/src/framework/layout/PanelGrid.tsx +60 -0
  32. package/templates/blank/src/framework/math/MathSvg.tsx +35 -0
  33. package/templates/blank/src/framework/math/mathjax.ts +64 -0
  34. package/templates/blank/src/framework/sizing.ts +28 -0
  35. package/templates/blank/src/framework/theme.ts +35 -0
  36. package/templates/blank/src/framework/types.ts +42 -0
  37. package/templates/blank/src/main.tsx +11 -0
  38. package/templates/blank/src/studio/StudioApp.tsx +130 -0
  39. package/templates/blank/src/studio/StudioRoot.tsx +14 -0
  40. package/templates/blank/src/studio/base64url.ts +8 -0
  41. package/templates/blank/src/studio/figureLoader.ts +30 -0
  42. package/templates/blank/src/studio/projectLoader.ts +40 -0
  43. package/templates/blank/src/studio/propsApi.ts +26 -0
  44. package/templates/blank/src/studio/routes/FigureView.tsx +365 -0
  45. package/templates/blank/src/studio/routes/ProjectHome.tsx +107 -0
  46. package/templates/blank/src/studio/routes/ProjectsHome.tsx +63 -0
  47. package/templates/blank/src/studio/routes/RenderView.tsx +123 -0
  48. package/templates/blank/src/studio/studio.css +540 -0
  49. package/templates/blank/src/studio/useProjectProps.ts +129 -0
  50. package/templates/blank/src/vite-env.d.ts +2 -0
  51. package/templates/blank/tsconfig.json +20 -0
  52. package/templates/blank/vite.config.ts +82 -0
  53. package/templates/blank/vitest.config.ts +8 -0
  54. package/templates/example/README.md +96 -0
  55. package/templates/example/gitignore +6 -0
  56. package/templates/example/index.html +13 -0
  57. package/templates/example/package.json +42 -0
  58. package/templates/example/projects/example/figures/ai-agent-architecture.tsx +133 -0
  59. package/templates/example/projects/example/figures/equation.tsx +29 -0
  60. package/templates/example/projects/example/figures/hello-world.tsx +45 -0
  61. package/templates/example/projects/example/figures/line-chart.tsx +80 -0
  62. package/templates/example/projects/example/figures/multi-panel.tsx +51 -0
  63. package/templates/example/projects/example/figures/pipeline-diagram.tsx +51 -0
  64. package/templates/example/projects/example/manifest.ts +161 -0
  65. package/templates/example/projects/example/project.ts +31 -0
  66. package/templates/example/projects/example/props.json +10 -0
  67. package/templates/example/public/projects/example/previews/ai-agent-architecture--default.png +0 -0
  68. package/templates/example/public/projects/example/previews/equation--default.png +0 -0
  69. package/templates/example/public/projects/example/previews/hello-world--default.png +0 -0
  70. package/templates/example/public/projects/example/previews/line-chart--default.png +0 -0
  71. package/templates/example/public/projects/example/previews/multi-panel--default.png +0 -0
  72. package/templates/example/public/projects/example/previews/pipeline-diagram--default.png +0 -0
  73. package/templates/example/scripts/list.ts +46 -0
  74. package/templates/example/scripts/projects.ts +43 -0
  75. package/templates/example/scripts/render.ts +288 -0
  76. package/templates/example/scripts/server.ts +117 -0
  77. package/templates/example/src/core/__tests__/controls.test.ts +14 -0
  78. package/templates/example/src/core/__tests__/manifest.test.ts +45 -0
  79. package/templates/example/src/core/__tests__/props.test.ts +9 -0
  80. package/templates/example/src/core/controls.ts +14 -0
  81. package/templates/example/src/core/manifest.ts +134 -0
  82. package/templates/example/src/core/props.ts +7 -0
  83. package/templates/example/src/framework/Figure.tsx +34 -0
  84. package/templates/example/src/framework/__tests__/sizing.test.ts +29 -0
  85. package/templates/example/src/framework/charts/Axes.tsx +103 -0
  86. package/templates/example/src/framework/charts/GridLines.tsx +59 -0
  87. package/templates/example/src/framework/charts/Legend.tsx +31 -0
  88. package/templates/example/src/framework/charts/Series.tsx +50 -0
  89. package/templates/example/src/framework/charts/scales.ts +19 -0
  90. package/templates/example/src/framework/diagrams/primitives.tsx +134 -0
  91. package/templates/example/src/framework/layout/PanelGrid.tsx +60 -0
  92. package/templates/example/src/framework/math/MathSvg.tsx +35 -0
  93. package/templates/example/src/framework/math/mathjax.ts +64 -0
  94. package/templates/example/src/framework/sizing.ts +28 -0
  95. package/templates/example/src/framework/theme.ts +35 -0
  96. package/templates/example/src/framework/types.ts +42 -0
  97. package/templates/example/src/main.tsx +11 -0
  98. package/templates/example/src/studio/StudioApp.tsx +130 -0
  99. package/templates/example/src/studio/StudioRoot.tsx +14 -0
  100. package/templates/example/src/studio/base64url.ts +8 -0
  101. package/templates/example/src/studio/figureLoader.ts +30 -0
  102. package/templates/example/src/studio/projectLoader.ts +40 -0
  103. package/templates/example/src/studio/propsApi.ts +26 -0
  104. package/templates/example/src/studio/routes/FigureView.tsx +365 -0
  105. package/templates/example/src/studio/routes/ProjectHome.tsx +107 -0
  106. package/templates/example/src/studio/routes/ProjectsHome.tsx +63 -0
  107. package/templates/example/src/studio/routes/RenderView.tsx +123 -0
  108. package/templates/example/src/studio/studio.css +540 -0
  109. package/templates/example/src/studio/useProjectProps.ts +129 -0
  110. package/templates/example/src/vite-env.d.ts +2 -0
  111. package/templates/example/tsconfig.json +20 -0
  112. package/templates/example/vite.config.ts +82 -0
  113. package/templates/example/vitest.config.ts +8 -0
@@ -0,0 +1,82 @@
1
+ import react from '@vitejs/plugin-react';
2
+ import { defineConfig } from 'vite';
3
+ import type { Plugin } from 'vite';
4
+ import path from 'node:path';
5
+ import fs from 'node:fs/promises';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { emptyPropsFile, PROJECT_ID_RE, validatePropsFileV1 } from './src/core/manifest';
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+
11
+ function imaginePropsApi(): Plugin {
12
+ return {
13
+ name: 'imagine-props-api',
14
+ apply: 'serve',
15
+ configureServer(server) {
16
+ server.middlewares.use((req, res, next) => {
17
+ void (async () => {
18
+ const url = new URL(req.url ?? '/', 'http://localhost');
19
+ if (url.pathname !== '/__imagine/props') return next();
20
+
21
+ const projectId = url.searchParams.get('projectId') ?? '';
22
+ if (!PROJECT_ID_RE.test(projectId)) {
23
+ res.statusCode = 400;
24
+ res.setHeader('content-type', 'application/json; charset=utf-8');
25
+ res.end(JSON.stringify({ error: 'Invalid projectId' }));
26
+ return;
27
+ }
28
+
29
+ const filePath = path.resolve(__dirname, 'projects', projectId, 'props.json');
30
+
31
+ if (req.method === 'GET') {
32
+ const body = await fs
33
+ .readFile(filePath, 'utf8')
34
+ .then((raw) => validatePropsFileV1(JSON.parse(raw)))
35
+ .catch(() => emptyPropsFile());
36
+
37
+ res.statusCode = 200;
38
+ res.setHeader('content-type', 'application/json; charset=utf-8');
39
+ res.end(JSON.stringify(body));
40
+ return;
41
+ }
42
+
43
+ if (req.method === 'POST') {
44
+ const chunks: Buffer[] = [];
45
+ await new Promise<void>((resolve, reject) => {
46
+ req.on('data', (c) => chunks.push(c as Buffer));
47
+ req.on('end', () => resolve());
48
+ req.on('error', reject);
49
+ });
50
+
51
+ const raw = Buffer.concat(chunks).toString('utf8');
52
+ const json = JSON.parse(raw);
53
+ const validated = validatePropsFileV1(json);
54
+
55
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
56
+ await fs.writeFile(filePath, JSON.stringify(validated, null, 2) + '\n', 'utf8');
57
+
58
+ res.statusCode = 204;
59
+ res.end();
60
+ return;
61
+ }
62
+
63
+ res.statusCode = 405;
64
+ res.end('Method not allowed');
65
+ })().catch(next);
66
+ });
67
+ }
68
+ };
69
+ }
70
+
71
+ export default defineConfig({
72
+ server: {
73
+ port: 5173,
74
+ strictPort: true
75
+ },
76
+ resolve: {
77
+ alias: {
78
+ '@': path.resolve(__dirname, 'src')
79
+ }
80
+ },
81
+ plugins: [react(), imaginePropsApi()]
82
+ });
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'node'
6
+ }
7
+ });
8
+
@@ -0,0 +1,96 @@
1
+ # Imagine
2
+
3
+ React → scientific figures (PNG + SVG) with a live Studio and a Playwright renderer.
4
+
5
+ ## Quickstart
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ Open the Studio at http://localhost:5173
13
+
14
+ ## Projects
15
+
16
+ Figures are organized into projects under `projects/<id>/`.
17
+
18
+ The Studio home shows projects. Clicking a project shows:
19
+ - a preview gallery (static images from `public/`)
20
+ - the live React-generated figures for that project
21
+
22
+ ```bash
23
+ npm run list
24
+ ```
25
+
26
+ List one project’s figures:
27
+
28
+ ```bash
29
+ npm run list -- --project example
30
+ ```
31
+
32
+ ## Render exports
33
+
34
+ ```bash
35
+ npm run render -- --project example
36
+ ```
37
+
38
+ By default outputs are written to `out/<projectId>/`:
39
+ - `out/<projectId>/<figure>--<variant>.png`
40
+ - `out/<projectId>/<figure>--<variant>.svg`
41
+ - `out/<projectId>/manifest.json`
42
+
43
+ ### Render options
44
+
45
+ ```bash
46
+ # Only one figure
47
+ npm run render -- --project example --fig line-chart
48
+
49
+ # Only one variant
50
+ npm run render -- --project example --fig hello-world --variant transparent
51
+
52
+ # Dev-mode rendering (requires `npm run dev` already running)
53
+ npm run render:dev -- --project example
54
+ ```
55
+
56
+ Common flags:
57
+ - `--project <id>`
58
+ - `--fig <id>` (repeatable or comma-separated)
59
+ - `--variant <id>` (repeatable or comma-separated)
60
+ - `--formats png,svg`
61
+ - `--out <dir>`
62
+ - `--mode build|dev`
63
+ - `--url http://localhost:5173` (dev mode)
64
+ - `--no-props` (ignore Studio-saved overrides)
65
+
66
+ ## Text editing (Studio Controls)
67
+
68
+ When you edit figure text in Studio, it saves overrides into:
69
+ - `projects/<projectId>/props.json`
70
+
71
+ `npm run render` automatically uses those overrides (unless `--no-props`).
72
+
73
+ ## Create a new project
74
+
75
+ 1. Copy `projects/example/` to `projects/<your-id>/`
76
+ 2. Update `projects/<your-id>/project.ts` and `projects/<your-id>/manifest.ts`
77
+ 3. Restart `npm run dev` (project discovery is build-time)
78
+
79
+ ## Create a new figure (inside a project)
80
+
81
+ 1. Add a new file in `projects/<projectId>/figures/` that default-exports a React component whose root is an `<svg>`.
82
+ 2. Register it in `projects/<projectId>/manifest.ts` with an `id`, `title`, `moduleKey`, and at least one variant.
83
+ 3. Optionally add preview images under `public/projects/<projectId>/previews/` and reference them from `projects/<projectId>/project.ts`.
84
+
85
+ Notes:
86
+ - `moduleKey` must match the figure filename (without `.tsx`). Example: `projects/example/figures/line-chart.tsx` → `moduleKey: "line-chart"`.
87
+ - Sizes can be specified as pixels or paper-friendly `mm + dpi` (converted to px for rendering).
88
+ - MathJax is loaded on-demand from CDN by default. Override with `VITE_MATHJAX_URL` if needed.
89
+
90
+ ## Generate example previews
91
+
92
+ ```bash
93
+ npm run render:previews
94
+ ```
95
+
96
+ This writes PNGs to `public/projects/example/previews/`.
@@ -0,0 +1,6 @@
1
+ node_modules
2
+ dist
3
+ out
4
+ .DS_Store
5
+ *.log
6
+
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Imagine Studio</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
13
+
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "imagine-project",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "list": "tsx scripts/list.ts",
11
+ "render": "tsx scripts/render.ts",
12
+ "render:dev": "tsx scripts/render.ts --mode dev",
13
+ "render:previews": "tsx scripts/render.ts --project example --variant default --formats png --no-props --no-manifest --out public/projects/example/previews",
14
+ "typecheck": "tsc --noEmit",
15
+ "test": "vitest run",
16
+ "test:watch": "vitest"
17
+ },
18
+ "dependencies": {
19
+ "d3-array": "^3.2.4",
20
+ "d3-format": "^3.1.0",
21
+ "d3-scale": "^4.0.2",
22
+ "d3-shape": "^3.2.0",
23
+ "react": "^18.3.1",
24
+ "react-dom": "^18.3.1",
25
+ "react-router-dom": "^6.28.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/d3-array": "^3.2.2",
29
+ "@types/d3-format": "^3.0.4",
30
+ "@types/d3-scale": "^4.0.9",
31
+ "@types/d3-shape": "^3.1.8",
32
+ "@types/node": "^22.10.7",
33
+ "@types/react": "^18.3.12",
34
+ "@types/react-dom": "^18.3.1",
35
+ "@vitejs/plugin-react": "^4.3.4",
36
+ "playwright": "^1.49.1",
37
+ "tsx": "^4.19.2",
38
+ "typescript": "^5.7.2",
39
+ "vite": "^6.0.6",
40
+ "vitest": "^2.1.8"
41
+ }
42
+ }
@@ -0,0 +1,133 @@
1
+ import { Figure } from '@/framework/Figure';
2
+ import type { FigureComponentBaseProps } from '@/framework/types';
3
+ import { Box, Callout, Label, Arrow } from '@/framework/diagrams/primitives';
4
+ import { theme } from '@/framework/theme';
5
+
6
+ function DashedArrow({
7
+ x1,
8
+ y1,
9
+ x2,
10
+ y2,
11
+ stroke = theme.colors.axis,
12
+ strokeWidth = theme.strokes.thick
13
+ }: {
14
+ x1: number;
15
+ y1: number;
16
+ x2: number;
17
+ y2: number;
18
+ stroke?: string;
19
+ strokeWidth?: number;
20
+ }) {
21
+ return (
22
+ <line
23
+ x1={x1}
24
+ y1={y1}
25
+ x2={x2}
26
+ y2={y2}
27
+ stroke={stroke}
28
+ strokeWidth={strokeWidth}
29
+ strokeDasharray="6,4"
30
+ markerEnd="none"
31
+ />
32
+ );
33
+ }
34
+
35
+ export default function AIAgentArchitectureFigure({
36
+ width,
37
+ height,
38
+ background,
39
+ title = 'AI Agent Architecture',
40
+ subtitle = 'Multi-agent system with tool use and memory',
41
+ userInput = 'User Input',
42
+ orchestrator = 'Orchestrator',
43
+ memory = 'Memory / Context',
44
+ llm = 'LLM / Model',
45
+ tools1 = 'Tool A',
46
+ tools2 = 'Tool B',
47
+ tools3 = 'Tool C',
48
+ output = 'Response',
49
+ notes = 'Components communicate via function calling'
50
+ }: FigureComponentBaseProps & {
51
+ title?: string;
52
+ subtitle?: string;
53
+ userInput?: string;
54
+ orchestrator?: string;
55
+ memory?: string;
56
+ llm?: string;
57
+ tools1?: string;
58
+ tools2?: string;
59
+ tools3?: string;
60
+ output?: string;
61
+ notes?: string;
62
+ }) {
63
+ const startY = 100;
64
+ const boxW = 160;
65
+ const boxH = 70;
66
+ const mainBoxH = 90;
67
+ const gapX = 60;
68
+ const gapY = 80;
69
+
70
+ const centerX = 500;
71
+ const xOrchestrator = centerX - boxW / 2;
72
+ const xMemory = centerX - boxW / 2;
73
+ const xLLM = centerX - boxW / 2;
74
+ const xOutput = centerX - boxW / 2;
75
+
76
+ const yUser = startY;
77
+ const yOrchestrator = yUser + mainBoxH + gapY;
78
+ const yMemory = yOrchestrator + mainBoxH + gapY - 20;
79
+ const yLLM = yOrchestrator;
80
+ const yTools = yOrchestrator;
81
+ const yOutput = yMemory + boxH + gapY;
82
+
83
+ const xToolsStart = xOrchestrator + boxW + gapX + 40;
84
+ const xTools1 = xToolsStart;
85
+ const xTools2 = xToolsStart;
86
+ const xTools3 = xToolsStart;
87
+ const yTools1 = yTools - 50;
88
+ const yTools2 = yTools + 10;
89
+ const yTools3 = yTools + 70;
90
+
91
+ return (
92
+ <Figure width={width} height={height} background={background} title="AI Agent Architecture">
93
+ <text x={70} y={44} fontSize={18} fontWeight={800} fill={theme.colors.text}>
94
+ {title}
95
+ </text>
96
+ <Label x={70} y={76} text={subtitle} fontSize={13} fill={theme.colors.subtleText} />
97
+
98
+ <Box x={centerX - boxW / 2} y={yUser} width={boxW} height={boxH} label={userInput} fill={theme.colors.panel} stroke={theme.colors.blue} />
99
+
100
+ <Box x={xOrchestrator} y={yOrchestrator} width={boxW} height={mainBoxH} label={orchestrator} fill={theme.colors.panel} stroke={theme.colors.teal} />
101
+
102
+ <Arrow x1={centerX} y1={yUser + boxH} x2={centerX} y2={yOrchestrator} />
103
+
104
+ <Box x={xLLM} y={yLLM} width={boxW} height={mainBoxH} label={llm} fill={theme.colors.panel} stroke={theme.colors.orange} />
105
+
106
+ <Arrow x1={xOrchestrator + boxW} y1={yOrchestrator + mainBoxH / 2} x2={xLLM} y2={yLLM + mainBoxH / 2} />
107
+ <DashedArrow x1={xLLM} y1={yLLM + mainBoxH / 2} x2={xOrchestrator + boxW} y2={yOrchestrator + mainBoxH / 2} stroke={theme.colors.subtleText} />
108
+
109
+ <Box x={xTools1} y={yTools1} width={boxW - 20} height={50} label={tools1} fill="#F3F4F6" stroke="#9CA3AF" />
110
+ <Box x={xTools2} y={yTools2} width={boxW - 20} height={50} label={tools2} fill="#F3F4F6" stroke="#9CA3AF" />
111
+ <Box x={xTools3} y={yTools3} width={boxW - 20} height={50} label={tools3} fill="#F3F4F6" stroke="#9CA3AF" />
112
+
113
+ <Arrow x1={xLLM + boxW} y1={yLLM + 25} x2={xTools1} y2={yTools1 + 25} />
114
+ <Arrow x1={xLLM + boxW} y1={yLLM + mainBoxH / 2} x2={xTools2} y2={yTools2 + 25} />
115
+ <Arrow x1={xLLM + boxW} y1={yLLM + mainBoxH - 25} x2={xTools3} y2={yTools3 + 25} />
116
+
117
+ <DashedArrow x1={xTools1} y1={yTools1 + 25} x2={xLLM + boxW} y2={yLLM + 25} stroke={theme.colors.subtleText} />
118
+ <DashedArrow x1={xTools2} y1={yTools2 + 25} x2={xLLM + boxW} y2={yLLM + mainBoxH / 2} stroke={theme.colors.subtleText} />
119
+ <DashedArrow x1={xTools3} y1={yTools3 + 25} x2={xLLM + boxW} y2={yLLM + mainBoxH - 25} stroke={theme.colors.subtleText} />
120
+
121
+ <Box x={xMemory} y={yMemory} width={boxW} height={boxH} label={memory} fill={theme.colors.panel} stroke={theme.colors.red} />
122
+
123
+ <Arrow x1={xOrchestrator + boxW / 2} y1={yOrchestrator + mainBoxH} x2={xMemory + boxW / 2} y2={yMemory} />
124
+ <DashedArrow x1={xMemory + boxW / 2} y1={yMemory + boxH} x2={xOrchestrator + boxW / 2} y2={yOrchestrator + mainBoxH} stroke={theme.colors.subtleText} />
125
+
126
+ <Box x={xOutput} y={yOutput} width={boxW} height={boxH} label={output} fill={theme.colors.panel} stroke={theme.colors.teal} />
127
+
128
+ <Arrow x1={centerX} y1={yMemory + boxH} x2={centerX} y2={yOutput} />
129
+
130
+ <Callout x={70} y={yOutput + boxH + 30} width={boxW + 100} height={44} text={notes} />
131
+ </Figure>
132
+ );
133
+ }
@@ -0,0 +1,29 @@
1
+ import { Figure } from '@/framework/Figure';
2
+ import type { FigureComponentBaseProps } from '@/framework/types';
3
+ import { theme } from '@/framework/theme';
4
+ import { MathSvg } from '@/framework/math/MathSvg';
5
+
6
+ export default function EquationFigure({
7
+ width,
8
+ height,
9
+ background,
10
+ title = 'Example: equation (MathJax SVG)',
11
+ subtitle = 'Uses MathJax tex2svg (pure SVG; no foreignObject)',
12
+ tex = String.raw`E=mc^2`
13
+ }: FigureComponentBaseProps & { title?: string; subtitle?: string; tex?: string }) {
14
+ return (
15
+ <Figure width={width} height={height} background={background} title="Equation">
16
+ <text x={40} y={54} fontSize={18} fontWeight={800} fill={theme.colors.text}>
17
+ {title}
18
+ </text>
19
+ <text x={40} y={84} fontSize={13} fill={theme.colors.subtleText}>
20
+ {subtitle}
21
+ </text>
22
+
23
+ <g transform={`translate(40, 120)`}>
24
+ <rect x={0} y={0} width={width - 80} height={height - 160} rx={theme.radii.md} fill={theme.colors.panel} />
25
+ <MathSvg tex={tex} x={24} y={40} scale={1.1} />
26
+ </g>
27
+ </Figure>
28
+ );
29
+ }
@@ -0,0 +1,45 @@
1
+ import { Figure } from '@/framework/Figure';
2
+ import type { FigureComponentBaseProps } from '@/framework/types';
3
+ import { theme } from '@/framework/theme';
4
+
5
+ export default function HelloWorldFigure({
6
+ width,
7
+ height,
8
+ background,
9
+ heading = 'Imagine',
10
+ subtitle = 'React components → scientific figures (PNG + SVG)',
11
+ tipHeading = 'Tips',
12
+ tip1 = 'Edit the figure component and watch this update live.',
13
+ tip2 = 'Use the Controls panel to adjust text, then export via `npm run render`.'
14
+ }: FigureComponentBaseProps & {
15
+ heading?: string;
16
+ subtitle?: string;
17
+ tipHeading?: string;
18
+ tip1?: string;
19
+ tip2?: string;
20
+ }) {
21
+ return (
22
+ <Figure width={width} height={height} background={background} title="Hello world">
23
+ <g>
24
+ <text x={40} y={70} fontSize={34} fontWeight={700} fill={theme.colors.text}>
25
+ {heading}
26
+ </text>
27
+ <text x={40} y={110} fontSize={16} fill={theme.colors.subtleText}>
28
+ {subtitle}
29
+ </text>
30
+
31
+ <rect x={40} y={150} width={width - 80} height={height - 190} rx={theme.radii.md} fill={theme.colors.panel} />
32
+
33
+ <text x={70} y={210} fontSize={16} fill={theme.colors.text} fontWeight={600}>
34
+ {tipHeading}
35
+ </text>
36
+ <text x={70} y={245} fontSize={14} fill={theme.colors.text}>
37
+ • {tip1}
38
+ </text>
39
+ <text x={70} y={270} fontSize={14} fill={theme.colors.text}>
40
+ • {tip2}
41
+ </text>
42
+ </g>
43
+ </Figure>
44
+ );
45
+ }
@@ -0,0 +1,80 @@
1
+ import { Figure } from '@/framework/Figure';
2
+ import type { FigureComponentBaseProps } from '@/framework/types';
3
+ import { theme } from '@/framework/theme';
4
+ import type { Point } from '@/framework/charts/scales';
5
+ import { extentX, extentY, linearScale } from '@/framework/charts/scales';
6
+ import { AxisBottom, AxisLeft } from '@/framework/charts/Axes';
7
+ import { GridLines } from '@/framework/charts/GridLines';
8
+ import { LineSeries, ScatterSeries } from '@/framework/charts/Series';
9
+ import { Legend } from '@/framework/charts/Legend';
10
+
11
+ const dataA: Point[] = [
12
+ { x: 0, y: 0.2 },
13
+ { x: 1, y: 0.9 },
14
+ { x: 2, y: 1.3 },
15
+ { x: 3, y: 1.8 },
16
+ { x: 4, y: 2.2 },
17
+ { x: 5, y: 2.4 }
18
+ ];
19
+
20
+ const dataB: Point[] = [
21
+ { x: 0, y: 0.4 },
22
+ { x: 1, y: 0.7 },
23
+ { x: 2, y: 1.1 },
24
+ { x: 3, y: 1.5 },
25
+ { x: 4, y: 1.9 },
26
+ { x: 5, y: 2.15 }
27
+ ];
28
+
29
+ export default function LineChartFigure({
30
+ width,
31
+ height,
32
+ background,
33
+ title = 'Example: signal over time',
34
+ xLabel = 'Time (a.u.)',
35
+ yLabel = 'Response',
36
+ seriesALabel = 'Condition A',
37
+ seriesBLabel = 'Condition B'
38
+ }: FigureComponentBaseProps & {
39
+ title?: string;
40
+ xLabel?: string;
41
+ yLabel?: string;
42
+ seriesALabel?: string;
43
+ seriesBLabel?: string;
44
+ }) {
45
+ const margin = { left: 70, top: 40, right: 20, bottom: 60 };
46
+ const plotW = width - margin.left - margin.right;
47
+ const plotH = height - margin.top - margin.bottom;
48
+ const all = [...dataA, ...dataB];
49
+ const xScale = linearScale(extentX(all), [0, plotW]);
50
+ const yScale = linearScale(extentY(all), [plotH, 0]);
51
+
52
+ return (
53
+ <Figure width={width} height={height} background={background} title="Line chart">
54
+ <text x={margin.left} y={22} fontSize={14} fill={theme.colors.text} fontWeight={700}>
55
+ {title}
56
+ </text>
57
+
58
+ <g transform={`translate(${margin.left}, ${margin.top})`}>
59
+ <rect x={0} y={0} width={plotW} height={plotH} fill="transparent" />
60
+ <GridLines x={0} y={0} width={plotW} height={plotH} xScale={xScale} yScale={yScale} />
61
+ <LineSeries xScale={xScale} yScale={yScale} data={dataA} stroke={theme.colors.blue} />
62
+ <ScatterSeries xScale={xScale} yScale={yScale} data={dataA} fill={theme.colors.blue} />
63
+ <LineSeries xScale={xScale} yScale={yScale} data={dataB} stroke={theme.colors.teal} />
64
+ <ScatterSeries xScale={xScale} yScale={yScale} data={dataB} fill={theme.colors.teal} />
65
+ </g>
66
+
67
+ <AxisLeft x={margin.left} y={margin.top} scale={yScale} label={yLabel} />
68
+ <AxisBottom x={margin.left} y={height - margin.bottom} scale={xScale} label={xLabel} />
69
+
70
+ <Legend
71
+ x={width - 210}
72
+ y={48}
73
+ items={[
74
+ { label: seriesALabel, color: theme.colors.blue },
75
+ { label: seriesBLabel, color: theme.colors.teal }
76
+ ]}
77
+ />
78
+ </Figure>
79
+ );
80
+ }
@@ -0,0 +1,51 @@
1
+ import { Figure } from '@/framework/Figure';
2
+ import type { FigureComponentBaseProps } from '@/framework/types';
3
+ import { theme } from '@/framework/theme';
4
+ import { PanelGrid } from '@/framework/layout/PanelGrid';
5
+ import { Box, Label } from '@/framework/diagrams/primitives';
6
+
7
+ export default function MultiPanelFigure({
8
+ width,
9
+ height,
10
+ background,
11
+ title = 'Example: multi-panel figure',
12
+ subtitle = 'Use PanelGrid to compose sub-panels (a, b, c…)',
13
+ panelA = 'Panel: raw',
14
+ panelB = 'Panel: processed',
15
+ panelC = 'Panel: ablation',
16
+ panelD = 'Panel: summary'
17
+ }: FigureComponentBaseProps & {
18
+ title?: string;
19
+ subtitle?: string;
20
+ panelA?: string;
21
+ panelB?: string;
22
+ panelC?: string;
23
+ panelD?: string;
24
+ }) {
25
+ const panelW = 440;
26
+ const panelH = 210;
27
+
28
+ return (
29
+ <Figure width={width} height={height} background={background} title="Multi-panel layout">
30
+ <text x={70} y={44} fontSize={18} fontWeight={800} fill={theme.colors.text}>
31
+ {title}
32
+ </text>
33
+ <Label x={70} y={76} text={subtitle} fontSize={13} fill={theme.colors.subtleText} />
34
+
35
+ <PanelGrid x={40} y={90} width={width - 80} height={height - 120} rows={2} cols={2}>
36
+ <g>
37
+ <Box x={0} y={0} width={panelW} height={panelH} label={panelA} fill="#EEF2FF" stroke="#6366F1" />
38
+ </g>
39
+ <g>
40
+ <Box x={0} y={0} width={panelW} height={panelH} label={panelB} fill="#ECFDF5" stroke="#10B981" />
41
+ </g>
42
+ <g>
43
+ <Box x={0} y={0} width={panelW} height={panelH} label={panelC} fill="#FFFBEB" stroke="#F59E0B" />
44
+ </g>
45
+ <g>
46
+ <Box x={0} y={0} width={panelW} height={panelH} label={panelD} fill="#FEF2F2" stroke="#EF4444" />
47
+ </g>
48
+ </PanelGrid>
49
+ </Figure>
50
+ );
51
+ }
@@ -0,0 +1,51 @@
1
+ import { Figure } from '@/framework/Figure';
2
+ import type { FigureComponentBaseProps } from '@/framework/types';
3
+ import { Box, Callout, Label, Arrow } from '@/framework/diagrams/primitives';
4
+ import { theme } from '@/framework/theme';
5
+
6
+ export default function PipelineDiagramFigure({
7
+ width,
8
+ height,
9
+ background,
10
+ title = 'Example: analysis pipeline',
11
+ subtitle = 'Pure-SVG boxes/arrows + theme tokens',
12
+ step1 = 'Ingest',
13
+ step2 = 'Process',
14
+ step3 = 'Publish',
15
+ callout = 'Edit labels in Controls'
16
+ }: FigureComponentBaseProps & {
17
+ title?: string;
18
+ subtitle?: string;
19
+ step1?: string;
20
+ step2?: string;
21
+ step3?: string;
22
+ callout?: string;
23
+ }) {
24
+ const y = 120;
25
+ const boxW = 220;
26
+ const boxH = 86;
27
+ const gap = 70;
28
+ const startX = 70;
29
+
30
+ const x1 = startX;
31
+ const x2 = x1 + boxW + gap;
32
+ const x3 = x2 + boxW + gap;
33
+
34
+ return (
35
+ <Figure width={width} height={height} background={background} title="Pipeline diagram">
36
+ <text x={70} y={44} fontSize={18} fontWeight={800} fill={theme.colors.text}>
37
+ {title}
38
+ </text>
39
+ <Label x={70} y={76} text={subtitle} fontSize={13} fill={theme.colors.subtleText} />
40
+
41
+ <Box x={x1} y={y} width={boxW} height={boxH} label={step1} />
42
+ <Box x={x2} y={y} width={boxW} height={boxH} label={step2} />
43
+ <Box x={x3} y={y} width={boxW} height={boxH} label={step3} />
44
+
45
+ <Arrow x1={x1 + boxW} y1={y + boxH / 2} x2={x2} y2={y + boxH / 2} />
46
+ <Arrow x1={x2 + boxW} y1={y + boxH / 2} x2={x3} y2={y + boxH / 2} />
47
+
48
+ <Callout x={x2 - 10} y={y + boxH + 40} width={boxW + 20} height={44} text={callout} />
49
+ </Figure>
50
+ );
51
+ }