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.
- package/LICENSE +21 -0
- package/README.md +40 -0
- package/bin/create-imagine.js +407 -0
- package/package.json +19 -0
- package/templates/blank/README.md +96 -0
- package/templates/blank/gitignore +6 -0
- package/templates/blank/index.html +13 -0
- package/templates/blank/package.json +42 -0
- package/templates/blank/projects/example/figures/hello-world.tsx +45 -0
- package/templates/blank/projects/example/manifest.ts +44 -0
- package/templates/blank/projects/example/project.ts +16 -0
- package/templates/blank/projects/example/props.json +4 -0
- package/templates/blank/scripts/list.ts +46 -0
- package/templates/blank/scripts/projects.ts +43 -0
- package/templates/blank/scripts/render.ts +288 -0
- package/templates/blank/scripts/server.ts +117 -0
- package/templates/blank/src/core/__tests__/controls.test.ts +14 -0
- package/templates/blank/src/core/__tests__/manifest.test.ts +45 -0
- package/templates/blank/src/core/__tests__/props.test.ts +9 -0
- package/templates/blank/src/core/controls.ts +14 -0
- package/templates/blank/src/core/manifest.ts +134 -0
- package/templates/blank/src/core/props.ts +7 -0
- package/templates/blank/src/framework/Figure.tsx +34 -0
- package/templates/blank/src/framework/__tests__/sizing.test.ts +29 -0
- package/templates/blank/src/framework/charts/Axes.tsx +103 -0
- package/templates/blank/src/framework/charts/GridLines.tsx +59 -0
- package/templates/blank/src/framework/charts/Legend.tsx +31 -0
- package/templates/blank/src/framework/charts/Series.tsx +50 -0
- package/templates/blank/src/framework/charts/scales.ts +19 -0
- package/templates/blank/src/framework/diagrams/primitives.tsx +134 -0
- package/templates/blank/src/framework/layout/PanelGrid.tsx +60 -0
- package/templates/blank/src/framework/math/MathSvg.tsx +35 -0
- package/templates/blank/src/framework/math/mathjax.ts +64 -0
- package/templates/blank/src/framework/sizing.ts +28 -0
- package/templates/blank/src/framework/theme.ts +35 -0
- package/templates/blank/src/framework/types.ts +42 -0
- package/templates/blank/src/main.tsx +11 -0
- package/templates/blank/src/studio/StudioApp.tsx +130 -0
- package/templates/blank/src/studio/StudioRoot.tsx +14 -0
- package/templates/blank/src/studio/base64url.ts +8 -0
- package/templates/blank/src/studio/figureLoader.ts +30 -0
- package/templates/blank/src/studio/projectLoader.ts +40 -0
- package/templates/blank/src/studio/propsApi.ts +26 -0
- package/templates/blank/src/studio/routes/FigureView.tsx +365 -0
- package/templates/blank/src/studio/routes/ProjectHome.tsx +107 -0
- package/templates/blank/src/studio/routes/ProjectsHome.tsx +63 -0
- package/templates/blank/src/studio/routes/RenderView.tsx +123 -0
- package/templates/blank/src/studio/studio.css +540 -0
- package/templates/blank/src/studio/useProjectProps.ts +129 -0
- package/templates/blank/src/vite-env.d.ts +2 -0
- package/templates/blank/tsconfig.json +20 -0
- package/templates/blank/vite.config.ts +82 -0
- package/templates/blank/vitest.config.ts +8 -0
- package/templates/example/README.md +96 -0
- package/templates/example/gitignore +6 -0
- package/templates/example/index.html +13 -0
- package/templates/example/package.json +42 -0
- package/templates/example/projects/example/figures/ai-agent-architecture.tsx +133 -0
- package/templates/example/projects/example/figures/equation.tsx +29 -0
- package/templates/example/projects/example/figures/hello-world.tsx +45 -0
- package/templates/example/projects/example/figures/line-chart.tsx +80 -0
- package/templates/example/projects/example/figures/multi-panel.tsx +51 -0
- package/templates/example/projects/example/figures/pipeline-diagram.tsx +51 -0
- package/templates/example/projects/example/manifest.ts +161 -0
- package/templates/example/projects/example/project.ts +31 -0
- package/templates/example/projects/example/props.json +10 -0
- package/templates/example/public/projects/example/previews/ai-agent-architecture--default.png +0 -0
- package/templates/example/public/projects/example/previews/equation--default.png +0 -0
- package/templates/example/public/projects/example/previews/hello-world--default.png +0 -0
- package/templates/example/public/projects/example/previews/line-chart--default.png +0 -0
- package/templates/example/public/projects/example/previews/multi-panel--default.png +0 -0
- package/templates/example/public/projects/example/previews/pipeline-diagram--default.png +0 -0
- package/templates/example/scripts/list.ts +46 -0
- package/templates/example/scripts/projects.ts +43 -0
- package/templates/example/scripts/render.ts +288 -0
- package/templates/example/scripts/server.ts +117 -0
- package/templates/example/src/core/__tests__/controls.test.ts +14 -0
- package/templates/example/src/core/__tests__/manifest.test.ts +45 -0
- package/templates/example/src/core/__tests__/props.test.ts +9 -0
- package/templates/example/src/core/controls.ts +14 -0
- package/templates/example/src/core/manifest.ts +134 -0
- package/templates/example/src/core/props.ts +7 -0
- package/templates/example/src/framework/Figure.tsx +34 -0
- package/templates/example/src/framework/__tests__/sizing.test.ts +29 -0
- package/templates/example/src/framework/charts/Axes.tsx +103 -0
- package/templates/example/src/framework/charts/GridLines.tsx +59 -0
- package/templates/example/src/framework/charts/Legend.tsx +31 -0
- package/templates/example/src/framework/charts/Series.tsx +50 -0
- package/templates/example/src/framework/charts/scales.ts +19 -0
- package/templates/example/src/framework/diagrams/primitives.tsx +134 -0
- package/templates/example/src/framework/layout/PanelGrid.tsx +60 -0
- package/templates/example/src/framework/math/MathSvg.tsx +35 -0
- package/templates/example/src/framework/math/mathjax.ts +64 -0
- package/templates/example/src/framework/sizing.ts +28 -0
- package/templates/example/src/framework/theme.ts +35 -0
- package/templates/example/src/framework/types.ts +42 -0
- package/templates/example/src/main.tsx +11 -0
- package/templates/example/src/studio/StudioApp.tsx +130 -0
- package/templates/example/src/studio/StudioRoot.tsx +14 -0
- package/templates/example/src/studio/base64url.ts +8 -0
- package/templates/example/src/studio/figureLoader.ts +30 -0
- package/templates/example/src/studio/projectLoader.ts +40 -0
- package/templates/example/src/studio/propsApi.ts +26 -0
- package/templates/example/src/studio/routes/FigureView.tsx +365 -0
- package/templates/example/src/studio/routes/ProjectHome.tsx +107 -0
- package/templates/example/src/studio/routes/ProjectsHome.tsx +63 -0
- package/templates/example/src/studio/routes/RenderView.tsx +123 -0
- package/templates/example/src/studio/studio.css +540 -0
- package/templates/example/src/studio/useProjectProps.ts +129 -0
- package/templates/example/src/vite-env.d.ts +2 -0
- package/templates/example/tsconfig.json +20 -0
- package/templates/example/vite.config.ts +82 -0
- 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,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,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
|
+
}
|