@slxu/graphsx 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shenglong Xu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,588 @@
1
+ # GraphSX
2
+
3
+ GraphSX is a JSX-like DSL for drawing diagrams, plots, and Markdown-native figures as SVG.
4
+
5
+ It is designed for notes, papers, docs, and small scientific figures where the source should stay readable:
6
+
7
+ - JSX-like tags and props instead of a separate command language
8
+ - reusable shapes with public ports
9
+ - links, explicit paths, routing, arrowheads, and labels
10
+ - standalone plots with axes, data, ticks, legends, annotations, and KaTeX labels
11
+ - nested plots inside graphs for subplots and mixed plot/diagram figures
12
+ - Markdown fences and CodeMirror live-preview widgets
13
+
14
+ Try the playground: https://slxuphys.github.io/graphsx/
15
+
16
+ Current npm package name: `@slxu/graphsx`. Project and repo name: GraphSX.
17
+
18
+ Install from npm:
19
+
20
+ ```bash
21
+ npm install @slxu/graphsx
22
+ ```
23
+
24
+ ## What It Looks Like
25
+
26
+ ### Port Diagram
27
+
28
+ ```jsx
29
+ <Graph>
30
+ <Style id="box" fill="#eef6ff" stroke="#1d4ed8" />
31
+ <Style id="wire" stroke="#7c3aed" strokeWidth={3} />
32
+
33
+ <Rect id="A" at={[70, 82]} size={[100, 60]} label="alpha" useStyle="box">
34
+ <Port id="out" right label="xy" />
35
+ </Rect>
36
+ <Circle id="B" at={[280, 112]} r={40} label="B">
37
+ <Port id="in" left />
38
+ </Circle>
39
+
40
+ <Link headArrow from="A.out" to="B.in" useStyle="wire" />
41
+ </Graph>
42
+ ```
43
+
44
+ ![GraphSX port diagram rendered as SVG](docs/assets/basic-graph.svg)
45
+
46
+ ### Parametric Plot
47
+
48
+ ```jsx
49
+ <Plot width={430} height={330} xDomain={[-18, 18]} yDomain={[-18, 14]} frame box>
50
+ <Data
51
+ id="heart"
52
+ x="16 * pow(sin(t), 3)"
53
+ y="13*cos(t) - 5*cos(2*t) - 2*cos(3*t) - cos(4*t)"
54
+ domain={[0, 2*pi]}
55
+ samples={360}
56
+ />
57
+
58
+ <Axis x label="x" ticks grid />
59
+ <Axis y label="y" ticks grid />
60
+ <Line data="heart" stroke="#e11d48" strokeWidth={2.6} />
61
+ </Plot>
62
+ ```
63
+
64
+ ![GraphSX parametric heart curve rendered as SVG](docs/assets/plot-heart.svg)
65
+
66
+ ### Reusable Shapes And Repeat
67
+
68
+ ```jsx
69
+ <Graph route="straight">
70
+ <Shape id="Tensor" groupBox={false}>
71
+ <Rect id="box" size={[54, 54]} corner={8} label={tensorLabel}>
72
+ <Port id="left" left r={0} />
73
+ <Port id="right" right r={0} />
74
+ <Port id="phys" bottom r={0} />
75
+ </Rect>
76
+ <Port id="left" target="box.left" />
77
+ <Port id="right" target="box.right" />
78
+ <Port id="phys" target="box.phys" />
79
+ </Shape>
80
+
81
+ <Repeat count={4} as="i" step={[96, 0]}>
82
+ <Tensor id={`A${i}`} at={[70, 60]} tensorLabel={`A${i}`} />
83
+ </Repeat>
84
+ </Graph>
85
+ ```
86
+
87
+ ![GraphSX repeated tensor network rendered as SVG](docs/assets/tensor-repeat.svg)
88
+
89
+ ## Quick Example
90
+
91
+ ```jsx
92
+ <Graph>
93
+ <Style id="box" fill="#eef6ff" stroke="#1d4ed8" strokeWidth={2} />
94
+ <Style id="wire" stroke="#7c3aed" strokeWidth={3} />
95
+
96
+ <Rect id="A" at={[100, 100]} size={[100, 60]} label="$\alpha$" useStyle="box">
97
+ <Port id="out" right label="xy" />
98
+ </Rect>
99
+
100
+ <Circle id="B" at={[300, 100]} r={40} label="B">
101
+ <Port id="in" left />
102
+ </Circle>
103
+
104
+ <Link headArrow from="A.out" to="B.in" useStyle="wire" />
105
+ </Graph>
106
+ ```
107
+
108
+ Use it from JavaScript:
109
+
110
+ ```js
111
+ import { parseGraphSXDocument, renderGraphSXDocument } from "@slxu/graphsx";
112
+ import katex from "katex";
113
+
114
+ const model = parseGraphSXDocument(source);
115
+ renderGraphSXDocument(document.querySelector("svg"), model, { katex });
116
+ ```
117
+
118
+ Labels are opt-in. Use `label="xy"` for plain text and `label="$\alpha$"` for KaTeX math. If there is no `label` prop, no label is rendered.
119
+
120
+ ## Syntax Model
121
+
122
+ GraphSX should feel familiar if you already understand JSX:
123
+
124
+ - tags define components: `<Rect />`, `<Plot />`, `<Shape />`
125
+ - props configure components: `at={[100, 80]}`, `label="$x$"`, `frame`
126
+ - children nest inside parents: ports inside shapes, ticks inside axes
127
+ - braces hold static values and simple arithmetic: `{[L / 2, 20]}`
128
+ - backtick strings support substitution: ``label={`$A^{[${site}]}$`}``
129
+
130
+ GraphSX is not a full JSX compiler. Values are parsed by a small safe parser, not by executing arbitrary JavaScript.
131
+
132
+ ## Styling
133
+
134
+ Most visible components can be styled inline:
135
+
136
+ ```jsx
137
+ <Rect id="A" style={{ fill: "#eef6ff", stroke: "#1d4ed8", strokeWidth: 2 }} />
138
+ <Link from="A.right" to="B.left" style={{ stroke: "#7c3aed", strokeWidth: 3 }} />
139
+ ```
140
+
141
+ Or with reusable style libraries:
142
+
143
+ ```jsx
144
+ <Graph>
145
+ <Style id="tensor" fill="#6aa4d8" stroke="#111111" strokeWidth={3} />
146
+ <Style id="wire" stroke="#111111" strokeWidth={2.5} />
147
+
148
+ <Rect id="A" useStyle="tensor" />
149
+ <Link from="A.right" to="B.left" useStyle="wire" />
150
+ </Graph>
151
+ ```
152
+
153
+ Inline `style` overrides `useStyle`. Style keys can use camelCase, such as `strokeWidth`; the renderer maps them to SVG attributes.
154
+
155
+ Plot-specific boxes also support dedicated style props, such as `frameStyle`, `boxStyle`, `textStyle`, and `labelStyle`.
156
+
157
+ ## Graphs
158
+
159
+ `<Graph>` is the general figure canvas. It auto-sizes around its content and supports nodes, ports, links, paths, reusable shapes, repeats, and nested plots.
160
+
161
+ Canonical built-in graph nodes:
162
+
163
+ - `Rect`
164
+ - `Circle`
165
+ - `Point`, `Anchor`
166
+ - `Plot` when placed inside a graph
167
+
168
+ `Rect`, `Circle`, and nested `Plot` expose default ports:
169
+
170
+ ```txt
171
+ A.left
172
+ A.right
173
+ A.top
174
+ A.bottom
175
+ ```
176
+
177
+ `Point` and `Anchor` expose:
178
+
179
+ ```txt
180
+ J.center
181
+ ```
182
+
183
+ ### Ports
184
+
185
+ Ports can use side shorthand or custom local coordinates:
186
+
187
+ ```jsx
188
+ <Rect id="A" at={[100, 100]} size={[120, 80]}>
189
+ <Port id="in" left />
190
+ <Port id="tap" at={[60, 20]} angle={35} label="$t$" />
191
+ </Rect>
192
+ ```
193
+
194
+ `at` on a port is relative to its shape. `angle` controls the direction a routed link emits from or enters the port. `0` points right, `90` points down, `180` points left, and `-90` points up.
195
+
196
+ ### Links
197
+
198
+ Links connect quoted port addresses:
199
+
200
+ ```jsx
201
+ <Link headArrow from="A.right" to="B.left" />
202
+ ```
203
+
204
+ Routing options:
205
+
206
+ ```jsx
207
+ <Graph route="auto" grid={20} padding={16} corner={8}>
208
+ <Rect id="A" at={[60, 100]} size={[90, 60]} />
209
+ <Rect id="Block" at={[210, 70]} size={[90, 110]} />
210
+ <Rect id="B" at={[380, 100]} size={[90, 60]} />
211
+
212
+ <Link headArrow from="A.right" to="B.left" />
213
+ <Link headArrow from="A.top" to="B.top" route="orthogonal" corner={0} />
214
+ </Graph>
215
+ ```
216
+
217
+ Available routes:
218
+
219
+ - default curved route
220
+ - `route="straight"`
221
+ - `route="orthogonal"`
222
+ - `route="auto"` for first-pass obstacle avoidance
223
+
224
+ `route="auto"` avoids shape boxes. It does not yet optimize for edge crossings.
225
+
226
+ ### Paths
227
+
228
+ Use `<Path>` for exact geometry rather than semantic port-to-port links:
229
+
230
+ ```jsx
231
+ <Graph>
232
+ <Path points={[[90, 80], [90, 240], [180, 240]]} />
233
+ <Path points={[[120, 80], [170, 80], [170, 160]]} corner={6} headArrow />
234
+ <Path d="M 90 80 L 180 80" tailArrow arrowSize={8} />
235
+ </Graph>
236
+ ```
237
+
238
+ `Path` accepts `points` or raw SVG `d`. Add `headArrow`, `tailArrow`, and `arrowSize` for arrows. `corner` rounds bends in point paths.
239
+
240
+ ### Layout
241
+
242
+ Coordinates are optional when graph layout is enabled:
243
+
244
+ ```jsx
245
+ <Graph layout="dag" direction="right" rankGap={200} nodeGap={90}>
246
+ <Rect id="A" size={[100, 60]} />
247
+ <Rect id="B" size={[100, 60]} />
248
+ <Rect id="C" size={[100, 60]} />
249
+
250
+ <Link headArrow from="A.right" to="B.left" />
251
+ <Link headArrow from="A.right" to="C.left" />
252
+ </Graph>
253
+ ```
254
+
255
+ Supported layouts:
256
+
257
+ - `layout="row"`
258
+ - `layout="column"`
259
+ - `layout="dag"`
260
+
261
+ Explicit `at={[x, y]}` positions win over automatic layout.
262
+
263
+ ## Reusable Shapes
264
+
265
+ Define custom shapes with `<Shape>`, then instantiate them by tag name:
266
+
267
+ ```jsx
268
+ <Graph>
269
+ <Shape id="Tensor" groupBox={false}>
270
+ <Rect id="box" at={[0, 0]} size={[56, 56]} corner={8} label={`$A^{[${site}]}$`} />
271
+ <Port id="left" target="box.left" />
272
+ <Port id="right" target="box.right" />
273
+ <Port id="phys" target="box.bottom" />
274
+ </Shape>
275
+
276
+ <Repeat count={4} as="i" step={[100, 0]}>
277
+ <Tensor id={`A${i}`} at={[100, 100]} site={i} />
278
+ </Repeat>
279
+
280
+ <Repeat count={3} as="i">
281
+ <Link from={`A${i}.right`} to={`A${i+1}.left`} />
282
+ </Repeat>
283
+ </Graph>
284
+ ```
285
+
286
+ Address paths use ids. `A0.left` is the public port on the `Tensor` instance. `A0.box.left` is the `left` port on the internal child rectangle.
287
+
288
+ Grouped shapes render a dashed group box by default. Use `groupBox={false}` on the shape definition or on an instance to hide it.
289
+
290
+ Shape variants can inherit from another shape:
291
+
292
+ ```jsx
293
+ <Shape id="Gate" groupBox={false}>
294
+ <Rect id="box" size={[70, 40]} label={label} />
295
+ <Port id="in" target="box.left" />
296
+ <Port id="out" target="box.right" />
297
+ </Shape>
298
+
299
+ <Shape id="WideGate" from="Gate" w={110} label="$U$" />
300
+ ```
301
+
302
+ Derived shapes can override props, but not replace inherited children with the same id.
303
+
304
+ ## Repeat
305
+
306
+ Use `<Repeat>` to expand repeated nodes, links, paths, or shape internals:
307
+
308
+ ```jsx
309
+ <Graph>
310
+ <Repeat count={2} as="row" step={[0, 90]}>
311
+ <Repeat count={3} as="col" step={[100, 0]}>
312
+ <Rect id={`cell-${row}-${col}`} at={[100, 100]} size={[70, 50]} label={`cell ${row},${col}`} />
313
+ </Repeat>
314
+ </Repeat>
315
+ </Graph>
316
+ ```
317
+
318
+ `step` offsets each repeated copy. Backtick strings can use `${i}`, `${i+1}`, `${row}`, `${col}`, and simple arithmetic.
319
+
320
+ ## Plots
321
+
322
+ `<Plot>` can be used standalone:
323
+
324
+ ```jsx
325
+ <Plot width={560} height={340} xDomain={[0, 2*pi]} yDomain={[-1.2, 1.2]} frame box>
326
+ <Data id="sin" y="sin(x)" domain={[0, 2*pi]} samples={160} />
327
+
328
+ <Axis x label="$x$">
329
+ <Ticks
330
+ values={[0, pi/2, pi, 3*pi/2, 2*pi]}
331
+ labels={["$0$", "$\pi/2$", "$\pi$", "$3\pi/2$", "$2\pi$"]}
332
+ grid
333
+ />
334
+ </Axis>
335
+ <Axis y label="$\sin(x)$" ticks grid />
336
+
337
+ <Line data="sin" stroke="#2563eb" strokeWidth={2} label="$\sin(x)$" />
338
+ <Legend />
339
+ </Plot>
340
+ ```
341
+
342
+ Or inside a graph as a placed subplot:
343
+
344
+ ```jsx
345
+ <Graph>
346
+ <Plot id="left" at={[0, 0]} width={320} height={220} frame box>
347
+ <Port id="out" right />
348
+ <Axis x ticks />
349
+ <Axis y ticks />
350
+ <Line points={[[0, 1], [1, 2], [2, 4]]} />
351
+ </Plot>
352
+
353
+ <Plot id="right" at={left.right + [90, 0]} width={320} height={220} frame box>
354
+ <Port id="in" left />
355
+ <Axis x ticks />
356
+ <Axis y ticks />
357
+ <Line points={[[0, 4], [1, 2], [2, 1]]} />
358
+ </Plot>
359
+
360
+ <Link from="left.out" to="right.in" headArrow />
361
+ </Graph>
362
+ ```
363
+
364
+ In a plot:
365
+
366
+ - `frame` draws the outer plot panel
367
+ - `box` draws the inner axis/data box
368
+ - `padding` controls the gap between frame and axes
369
+ - `xDomain` and `yDomain` set data coordinates
370
+ - `Data` can hold points, x/y arrays, or generated math data
371
+ - generated `Data` always stores point fields named `x` and `y`
372
+ - parametric data uses `x="..."` and `y="..."`; the default sampling variable is `t`
373
+ - `Line`, `Curve`, `Mark`, `Scatter`, `Text`, `Legend`, and annotation shapes render on top
374
+ - generated data expressions may be complex; plotted coordinates use the real part by default
375
+
376
+ Parametric curves sample a variable and evaluate both coordinates:
377
+
378
+ ```jsx
379
+ <Plot width={500} height={460} xDomain={[-18, 18]} yDomain={[-18, 14]} frame box>
380
+ <Data
381
+ id="heart"
382
+ x="16 * pow(sin(t), 3)"
383
+ y="13*cos(t) - 5*cos(2*t) - 2*cos(3*t) - cos(4*t)"
384
+ domain={[0, 2*pi]}
385
+ samples={420}
386
+ />
387
+ <Line data="heart" stroke="#e11d48" strokeWidth={2.8} />
388
+ </Plot>
389
+ ```
390
+
391
+ For `y="..."` alone, the default sampling variable is `x`, and the sampled domain value is stored as the point's `x` field. For `x="..."` plus `y="..."`, the default sampling variable is `t`; both expressions are evaluated from that variable and stored as point fields named `x` and `y`. Use `variable="theta"` to override either default.
392
+
393
+ ```jsx
394
+ <Data id="phase" y="exp(1j*t)" variable="t" domain={[0, 2*pi]} />
395
+ ```
396
+
397
+ This stores points like `{ x: t, y: exp(1j*t) }`.
398
+
399
+ Complex math uses Python-style imaginary literals such as `1j`, `2.5j`, and `x + 3j`. Bare `j` is just a normal variable name, so `exp(j*x)` only works if `j` is declared in `params`.
400
+
401
+ ```jsx
402
+ <Plot width={560} height={340} xDomain={[-1, 1]} yDomain={[-1.1, 1.1]} frame box>
403
+ <Data id="root" y="sqrt(x)" domain={[-1, 1]} samples={200} />
404
+
405
+ <Axis x label="$x$" ticks grid />
406
+ <Axis y label="$\sqrt{x}$" ticks grid />
407
+
408
+ <Line data="root" label="real" />
409
+ <Line data="root" yMap="imag(y)" stroke="#dc2626" label="imag" />
410
+ <Line data="root" yMap="abs(y)" stroke="#16a34a" label="abs" />
411
+ <Legend />
412
+ </Plot>
413
+ ```
414
+
415
+ Use `xMap` and `yMap` to transform stored points at plot time. The map scope contains the stored `x` and `y` values, which may be real or complex. Either map can use either stored field:
416
+
417
+ ```jsx
418
+ <Plot width={420} height={420} xDomain={[-1.2, 1.2]} yDomain={[-1.2, 1.2]} frame box>
419
+ <Data id="phase" y="exp(1j*t)" variable="t" domain={[0, 2*pi]} samples={240} />
420
+
421
+ <Line data="phase" xMap="real(y)" yMap="imag(y)" label="unit circle" />
422
+ </Plot>
423
+ ```
424
+
425
+ Plot annotations can use `Rect`, `Circle`, `Anchor`, `Link`, and `Path`:
426
+
427
+ ```jsx
428
+ <Plot width={500} height={300} xDomain={[0, 5]} yDomain={[0, 10]} frame box>
429
+ <Line points={[[0, 1], [2, 4], [5, 9]]} />
430
+ <Circle id="peak" at={[5, 9]} r={5} fill="#ef4444" />
431
+ <Rect id="note" at={[3.2, 8.2]} size={[90, 28]} label="peak">
432
+ <Port id="tip" left />
433
+ </Rect>
434
+ <Link from="note.tip" to="peak.top" headArrow />
435
+ </Plot>
436
+ ```
437
+
438
+ Annotation coordinates default to data coordinates. Use `atUnit="screen"` for screen/SVG coordinates.
439
+
440
+ ## Supported Tags
441
+
442
+ Common top-level tags:
443
+
444
+ | Tag | Children | Key props |
445
+ | --- | --- | --- |
446
+ | `Graph` | `Style`, `Shape`, nodes, `Plot`, `Link`, `Path`, `Repeat` | `route`, `layout`, `direction`, `grid`, `padding`, `corner` |
447
+ | `Plot` | plot children, and `Port` when nested in `Graph` | `width`, `height`, `padding`, `xDomain`, `yDomain`, `frame`, `box` |
448
+
449
+ Graph tags:
450
+
451
+ | Tag | Children | Key props |
452
+ | --- | --- | --- |
453
+ | `Rect` | `Port` | `id`, `at`, `size`, `w`, `h`, `label`, `corner`, `anchor`, `origin`, `rotate`, `flipX`, `flipY`, `style`, `useStyle` |
454
+ | `Circle` | `Port` | `id`, `at`, `r`, `label`, `anchor`, `origin`, `rotate`, `flipX`, `flipY`, `style`, `useStyle` |
455
+ | `Point`, `Anchor` | `Port` | `id`, `at`, `label`, `r`, `style`, `useStyle` |
456
+ | `Port`, `Leg` | none | `id`, `left`, `right`, `top`, `bottom`, `side`, `at`, `angle`, `target`, `label`, `r`, `style`, `useStyle` |
457
+ | `Link` | none | `from`, `to`, `route`, `corner`, `stub`, `headArrow`, `tailArrow`, `arrowSize`, `style`, `useStyle` |
458
+ | `Path` | none | `points`, `d`, `at`, `corner`, `closed`, `headArrow`, `tailArrow`, `arrowSize`, `rotate`, `flipX`, `flipY`, `style`, `useStyle` |
459
+ | `Shape` | graph nodes, `Port`, `Link`, `Path`, `Repeat` | `id`, `from`, `groupBox`, user-defined props |
460
+ | `Repeat` | repeatable graph children | `count`, `as`, `step` |
461
+ | `Style` | none | `id`, SVG style props |
462
+
463
+ Plot tags:
464
+
465
+ | Tag | Children | Key props |
466
+ | --- | --- | --- |
467
+ | `Data` | none | `id`, `points`, `x`, `y`, `domain`, `samples`, `params`, `variable` |
468
+ | `Axis` | `Ticks` | `x`, `y`, `label`, `ticks`, `grid`, `labelGap`, `tickLabelGap`, `tickSize`, `style`, `labelStyle` |
469
+ | `Ticks` | none | `values`, `labels`, `grid`, `labelGap`, `tickLabelGap`, `tickSize`, `style`, `labelStyle` |
470
+ | `Line`, `Curve` | none | `data`, `points`, `x`, `y`, `xMap`, `yMap`, `fmt`, `label`, `animate`, `stroke`, `strokeWidth`, `fill`, `r`, `style`, `useStyle` |
471
+ | `Mark`, `Scatter` | none | `data`, `at`, `points`, `x`, `y`, `xMap`, `yMap`, `r`, `label`, `animate`, `fill`, `stroke`, `style`, `useStyle` |
472
+ | `Text`, `Label` | none | `at`, `label`, `fontSize`, `anchor`, `baseline`, `rotate`, `style`, `useStyle` |
473
+ | `Legend` | none | `position`, `box`, `padding`, `margin`, `fontSize`, `textStyle`, `boxStyle` |
474
+
475
+ Canonical tags use PascalCase. A few aliases are accepted for convenience, but new examples should prefer the canonical form:
476
+
477
+ | Canonical | Aliases |
478
+ | --- | --- |
479
+ | `Rect` | `rect`, `Rec`, `rec` |
480
+ | `Circle` | `circle`, `Circ`, `circ` |
481
+ | `Point` | `point` |
482
+ | `Anchor` | `anchor` |
483
+ | `Path` | `path` |
484
+ | `Data` | `Dataset` |
485
+ | `Axis` | `XAxis`, `YAxis` |
486
+ | `Ticks` | `ticks` |
487
+ | `Curve` | `Series` |
488
+ | `Legend` | `legend` |
489
+
490
+ ## Markdown
491
+
492
+ GraphSX works in Markdown with a `markdown-it` plugin:
493
+
494
+ ```js
495
+ import MarkdownIt from "markdown-it";
496
+ import katex from "katex";
497
+ import { graphsxMarkdownIt, renderGraphSXBlocks } from "@slxu/graphsx";
498
+ import "@slxu/graphsx/markdown.css";
499
+
500
+ const md = new MarkdownIt().use(graphsxMarkdownIt);
501
+ const preview = document.querySelector("#preview");
502
+
503
+ preview.innerHTML = md.render(markdownSource);
504
+ renderGraphSXBlocks(preview, { katex });
505
+ ```
506
+
507
+ Authors use `graphsx` fences:
508
+
509
+ ````md
510
+ ```graphsx
511
+ <Graph>
512
+ <Rect id="A" />
513
+ <Rect id="B" at={[220, 0]} />
514
+ <Link headArrow from="A.right" to="B.left" />
515
+ </Graph>
516
+ ```
517
+ ````
518
+
519
+ Reusable style/shape libraries can be declared with hidden `graphsx-defs` fences:
520
+
521
+ ````md
522
+ ```graphsx-defs theme
523
+ <Style id="tensor" fill="#6aa4d8" stroke="#111111" />
524
+ <Style id="wire" stroke="#111111" strokeWidth={2.5} />
525
+ ```
526
+
527
+ ```graphsx use="theme"
528
+ <Graph>
529
+ <Rect id="A" useStyle="tensor" />
530
+ <Rect id="B" at={[120, 0]} useStyle="tensor" />
531
+ <Link from="A.right" to="B.left" useStyle="wire" />
532
+ </Graph>
533
+ ```
534
+ ````
535
+
536
+ Multiple library names can be separated by spaces or commas.
537
+
538
+ ## CodeMirror Live Preview
539
+
540
+ Use the CodeMirror extension to render `graphsx` fences as editable live widgets inside a Markdown editor:
541
+
542
+ ```js
543
+ import { EditorView, basicSetup } from "codemirror";
544
+ import { markdown } from "@codemirror/lang-markdown";
545
+ import { jsxLanguage } from "@codemirror/lang-javascript";
546
+ import { graphsxCodeMirrorLivePreview } from "@slxu/graphsx/codemirror";
547
+ import "@slxu/graphsx/codemirror.css";
548
+
549
+ new EditorView({
550
+ doc,
551
+ extensions: [
552
+ basicSetup,
553
+ markdown({
554
+ codeLanguages: (info) => {
555
+ const name = info.trim().split(/\s+/)[0];
556
+ return name === "graphsx" || name === "graphsx-defs" ? jsxLanguage : null;
557
+ }
558
+ }),
559
+ graphsxCodeMirrorLivePreview({ katex })
560
+ ],
561
+ parent: document.querySelector("#editor")
562
+ });
563
+ ```
564
+
565
+ When the cursor is outside a `graphsx` fence, the fence renders as an SVG widget. Clicking the widget moves the cursor into the original fenced code. `graphsx-defs` fences render as compact library markers and feed reusable shapes/styles to later fences.
566
+
567
+ ## Development
568
+
569
+ ```bash
570
+ npm install
571
+ npm test
572
+ npm run playground
573
+ ```
574
+
575
+ The playground runs at the Vite URL printed in your terminal, usually `http://127.0.0.1:5173/`.
576
+
577
+ Build the GitHub Pages version:
578
+
579
+ ```bash
580
+ npm run build:pages
581
+ ```
582
+
583
+ ## Notes
584
+
585
+ - Graph labels and plot labels support KaTeX when a `katex` instance is passed to the renderer.
586
+ - Graph `<Plot>` nodes behave like rectangles: they have `at`, `width`, `height`, and default side ports.
587
+ - Standalone `<Plot>` is best for one plot. Use `<Graph>` when you want subplots or a mixed figure.
588
+ - The parser intentionally supports a constrained JSX-like subset rather than arbitrary JavaScript.
@@ -0,0 +1,15 @@
1
+ <svg viewBox="62 62 286 116" width="286" height="116" xmlns="http://www.w3.org/2000/svg"><style>
2
+ .shape { fill: #ffffff; stroke: #26312d; stroke-width: 2; }
3
+ .node-label { font: 700 14px ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1e2724; pointer-events: none; }
4
+ .leg-label { font: 11px ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #52605a; pointer-events: none; }
5
+ .edge { fill: none; stroke: #2d6cdf; stroke-width: 2.5; }
6
+ .path { fill: none; stroke: #111111; stroke-width: 2; }
7
+ .leg-dot { fill: #16846f; stroke: #ffffff; stroke-width: 2; }
8
+ .plot-frame, .plot-box { fill: none; stroke: #26312d; stroke-width: 1.8; }
9
+ .plot-axis { stroke: #26312d; stroke-width: 1.8; }
10
+ .plot-tick { stroke: #26312d; stroke-width: 1.3; }
11
+ .plot-grid { stroke: #d8ded8; stroke-width: 1; }
12
+ .plot-line, .plot-curve { fill: none; stroke: #2563eb; stroke-width: 2; }
13
+ .plot-line-marker { fill: #ffffff; stroke: #2563eb; stroke-width: 2; }
14
+ .plot-axis-label, .plot-tick-label, .plot-label { font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1e2724; }
15
+ </style><defs><marker id="graphsx-arrow-head" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto" markerUnits="strokeWidth"><path d="M 2 2 L 10 6 L 2 10 z" fill="context-stroke"></path></marker><marker id="graphsx-arrow-tail" markerWidth="12" markerHeight="12" refX="2" refY="6" orient="auto" markerUnits="strokeWidth"><path d="M 10 2 L 2 6 L 10 10 z" fill="context-stroke"></path></marker></defs><g><path class="edge" marker-end="url(#graphsx-arrow-head)" d="M 180 120 C 228 120, 202 120, 250 120" style="stroke: #7c3aed; stroke-width: 3"></path></g><g></g><g><g><rect class="shape" x="80" y="90" width="100" height="60" rx="6" style="fill: #eef6ff; stroke: #1d4ed8; stroke-width: 2"></rect><text class="node-label" x="130" y="124" text-anchor="middle">alpha</text><g><circle class="leg-dot" cx="180" cy="120" r="5"></circle><text class="leg-label" x="190" y="114" text-anchor="start">xy</text></g></g><g><circle class="shape" cx="290" cy="120" r="40"></circle><text class="node-label" x="290" y="124" text-anchor="middle">B</text><g><circle class="leg-dot" cx="250" cy="120" r="5"></circle></g></g></g></svg>