vizcraft 0.1.3 → 0.1.5
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/CHANGELOG.md +12 -0
- package/README.md +133 -0
- package/dist/builder.d.ts +4 -0
- package/dist/builder.js +129 -20
- package/dist/types.d.ts +12 -0
- package/package.json +6 -1
- package/.turbo/turbo-build.log +0 -4
- package/src/animations.ts +0 -53
- package/src/builder.ts +0 -946
- package/src/index.test.ts +0 -17
- package/src/index.ts +0 -5
- package/src/overlays.ts +0 -203
- package/src/styles.ts +0 -67
- package/src/types.ts +0 -83
- package/tsconfig.json +0 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# vizcraft
|
|
2
2
|
|
|
3
|
+
## 0.1.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`ecb5ade`](https://github.com/ChipiKaf/vizcraft/commit/ecb5adef774e30589a0714699ccdb7839530bd50) Thanks [@ChipiKaf](https://github.com/ChipiKaf)! - update docs with correct code snippets
|
|
8
|
+
|
|
9
|
+
## 0.1.4
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`10926fc`](https://github.com/ChipiKaf/vizcraft/commit/10926fcce211d00dfba2697eaac62948ac0ef69d) Thanks [@ChipiKaf](https://github.com/ChipiKaf)! - update readme
|
|
14
|
+
|
|
3
15
|
## 0.1.3
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# VizCraft
|
|
2
|
+
|
|
3
|
+
**A declarative, builder-based library for creating animated SVG network visualizations and algorithm demos.**
|
|
4
|
+
|
|
5
|
+
VizCraft is designed to make creating beautiful, animated node-link diagrams and complex visualizations intuitive and powerful. Whether you are building an educational tool, explaining an algorithm, or just need a great looking graph, VizCraft provides the primitives you need.
|
|
6
|
+
|
|
7
|
+
## ✨ Features
|
|
8
|
+
|
|
9
|
+
- **Fluent Builder API**: Define your visualization scene using a readable, chainable API.
|
|
10
|
+
- **Grid System**: Built-in 2D grid system for easy, structured layout of nodes.
|
|
11
|
+
- **Declarative Animations**: Animate layout changes, edge flow, and node states with a simple declarative config.
|
|
12
|
+
- **Framework Agnostic**: The core logic is pure TypeScript and can be used with any framework or Vanilla JS.
|
|
13
|
+
- **Custom Overlays**: Create complex, custom UI elements that float on top of your visualization.
|
|
14
|
+
|
|
15
|
+
## 📦 Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install vizcraft
|
|
19
|
+
# or
|
|
20
|
+
pnpm add vizcraft
|
|
21
|
+
# or
|
|
22
|
+
yarn add vizcraft
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 🚀 Getting Started
|
|
26
|
+
|
|
27
|
+
You can use the core library directly to generate SVG content or mount to a DOM element.
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { viz } from 'vizcraft';
|
|
31
|
+
|
|
32
|
+
const builder = viz().view(800, 600);
|
|
33
|
+
|
|
34
|
+
builder
|
|
35
|
+
.view(500, 500)
|
|
36
|
+
.node('a')
|
|
37
|
+
.at(100, 100)
|
|
38
|
+
.circle(15)
|
|
39
|
+
.label('A')
|
|
40
|
+
.node('b')
|
|
41
|
+
.at(400, 100)
|
|
42
|
+
.circle(15)
|
|
43
|
+
.label('B')
|
|
44
|
+
.edge('a', 'b')
|
|
45
|
+
.arrow();
|
|
46
|
+
|
|
47
|
+
const container = document.getElementById('viz-basic');
|
|
48
|
+
if (container) builder.mount(container);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 📖 Core Concepts
|
|
52
|
+
|
|
53
|
+
### The Builder (`VizBuilder`)
|
|
54
|
+
|
|
55
|
+
The heart of VizCraft is the `VizBuilder`. It allows you to construct a `VizScene` which acts as the blueprint for your visualization.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
b.view(width, height) // Set the coordinate space
|
|
59
|
+
.grid(cols, rows) // (Optional) Define layout grid
|
|
60
|
+
.node(id) // Start defining a node
|
|
61
|
+
.edge(from, to); // Start defining an edge
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Nodes
|
|
65
|
+
|
|
66
|
+
Nodes are the primary entities in your graph. They can have shapes, labels, and styles.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
b.node('n1')
|
|
70
|
+
.at(x, y) // Absolute position
|
|
71
|
+
// OR
|
|
72
|
+
.cell(col, row) // Grid position
|
|
73
|
+
.circle(radius) // Shape definition
|
|
74
|
+
.label('Text', { dy: 5 }) // Label with offset
|
|
75
|
+
.class('css-class') // Custom CSS class
|
|
76
|
+
.data({ ... }) // Attach custom data
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Edges
|
|
80
|
+
|
|
81
|
+
Edges connect nodes and can be styled, directed, or animated.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
b.edge('n1', 'n2')
|
|
85
|
+
.arrow() // Add an arrowhead
|
|
86
|
+
.straight() // (Default) Straight line
|
|
87
|
+
.label('Connection')
|
|
88
|
+
.animate('flow'); // Add animation
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Animations
|
|
92
|
+
|
|
93
|
+
VizCraft supports declarative animations. You define _what_ happens, and the renderer handles the interpolation.
|
|
94
|
+
|
|
95
|
+
- **`stream`**: Particles flowing along an edge.
|
|
96
|
+
- **`pulse`**: Rhythmic scaling or opacity changes.
|
|
97
|
+
- **Transition**: Moving a node from one position to another.
|
|
98
|
+
|
|
99
|
+
## 🎨 Styling
|
|
100
|
+
|
|
101
|
+
VizCraft generates standard SVG elements with predictable classes, making it easy to style with CSS.
|
|
102
|
+
|
|
103
|
+
```css
|
|
104
|
+
/* Custom node style */
|
|
105
|
+
.viz-node-shape {
|
|
106
|
+
fill: #fff;
|
|
107
|
+
stroke: #333;
|
|
108
|
+
stroke-width: 2px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Specific node class */
|
|
112
|
+
.my-node .viz-node-shape {
|
|
113
|
+
fill: #ff6b6b;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Edge styling */
|
|
117
|
+
.viz-edge {
|
|
118
|
+
stroke: #ccc;
|
|
119
|
+
stroke-width: 2;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 🤝 Contributing
|
|
124
|
+
|
|
125
|
+
Contributions are welcome! This is a monorepo managed with Turbo.
|
|
126
|
+
|
|
127
|
+
1. Clone the repo
|
|
128
|
+
2. Install dependencies: `pnpm install`
|
|
129
|
+
3. Run dev server: `pnpm dev`
|
|
130
|
+
|
|
131
|
+
## 📄 License
|
|
132
|
+
|
|
133
|
+
MIT License © Chipili Kafwilo
|
package/dist/builder.d.ts
CHANGED
|
@@ -24,6 +24,9 @@ interface NodeBuilder {
|
|
|
24
24
|
rect(w: number, h: number, rx?: number): NodeBuilder;
|
|
25
25
|
diamond(w: number, h: number): NodeBuilder;
|
|
26
26
|
label(text: string, opts?: Partial<NodeLabel>): NodeBuilder;
|
|
27
|
+
fill(color: string): NodeBuilder;
|
|
28
|
+
stroke(color: string, width?: number): NodeBuilder;
|
|
29
|
+
opacity(value: number): NodeBuilder;
|
|
27
30
|
class(name: string): NodeBuilder;
|
|
28
31
|
animate(type: string, config?: AnimationConfig): NodeBuilder;
|
|
29
32
|
data(payload: unknown): NodeBuilder;
|
|
@@ -39,6 +42,7 @@ interface EdgeBuilder {
|
|
|
39
42
|
straight(): EdgeBuilder;
|
|
40
43
|
label(text: string, opts?: Partial<EdgeLabel>): EdgeBuilder;
|
|
41
44
|
arrow(enabled?: boolean): EdgeBuilder;
|
|
45
|
+
connect(anchor: 'center' | 'boundary'): EdgeBuilder;
|
|
42
46
|
class(name: string): EdgeBuilder;
|
|
43
47
|
hitArea(px: number): EdgeBuilder;
|
|
44
48
|
animate(type: string, config?: AnimationConfig): EdgeBuilder;
|
package/dist/builder.js
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
import { DEFAULT_VIZ_CSS } from './styles';
|
|
2
2
|
import { defaultCoreAnimationRegistry } from './animations';
|
|
3
3
|
import { defaultCoreOverlayRegistry } from './overlays';
|
|
4
|
+
function setSvgAttributes(el, attrs) {
|
|
5
|
+
Object.entries(attrs).forEach(([key, value]) => {
|
|
6
|
+
if (value === undefined) {
|
|
7
|
+
el.removeAttribute(key);
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
el.setAttribute(key, String(value));
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function svgAttributeString(attrs) {
|
|
15
|
+
return Object.entries(attrs)
|
|
16
|
+
.filter(([, value]) => value !== undefined)
|
|
17
|
+
.map(([key, value]) => ` ${key}="${String(value)}"`)
|
|
18
|
+
.join('');
|
|
19
|
+
}
|
|
20
|
+
function computeNodeAnchor(node, target, anchor) {
|
|
21
|
+
if (anchor === 'center') {
|
|
22
|
+
return { x: node.pos.x, y: node.pos.y };
|
|
23
|
+
}
|
|
24
|
+
const dx = target.x - node.pos.x;
|
|
25
|
+
const dy = target.y - node.pos.y;
|
|
26
|
+
if (dx === 0 && dy === 0) {
|
|
27
|
+
return { x: node.pos.x, y: node.pos.y };
|
|
28
|
+
}
|
|
29
|
+
if (node.shape.kind === 'circle') {
|
|
30
|
+
const dist = Math.hypot(dx, dy) || 1;
|
|
31
|
+
const scale = node.shape.r / dist;
|
|
32
|
+
return {
|
|
33
|
+
x: node.pos.x + dx * scale,
|
|
34
|
+
y: node.pos.y + dy * scale,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (node.shape.kind === 'rect') {
|
|
38
|
+
const hw = node.shape.w / 2;
|
|
39
|
+
const hh = node.shape.h / 2;
|
|
40
|
+
const scale = Math.min(hw / Math.abs(dx || 1e-6), hh / Math.abs(dy || 1e-6));
|
|
41
|
+
return {
|
|
42
|
+
x: node.pos.x + dx * scale,
|
|
43
|
+
y: node.pos.y + dy * scale,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const hw = node.shape.w / 2;
|
|
47
|
+
const hh = node.shape.h / 2;
|
|
48
|
+
const denom = Math.abs(dx) / hw + Math.abs(dy) / hh;
|
|
49
|
+
const scale = denom === 0 ? 0 : 1 / denom;
|
|
50
|
+
return {
|
|
51
|
+
x: node.pos.x + dx * scale,
|
|
52
|
+
y: node.pos.y + dy * scale,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function computeEdgeEndpoints(start, end, edge) {
|
|
56
|
+
const anchor = edge.anchor ?? 'boundary';
|
|
57
|
+
const startAnchor = computeNodeAnchor(start, end.pos, anchor);
|
|
58
|
+
const endAnchor = computeNodeAnchor(end, start.pos, anchor);
|
|
59
|
+
return { start: startAnchor, end: endAnchor };
|
|
60
|
+
}
|
|
4
61
|
class VizBuilderImpl {
|
|
5
62
|
_viewBox = { w: 800, h: 600 };
|
|
6
63
|
_nodes = new Map();
|
|
@@ -217,12 +274,13 @@ class VizBuilderImpl {
|
|
|
217
274
|
});
|
|
218
275
|
}
|
|
219
276
|
group.setAttribute('class', classes);
|
|
277
|
+
const endpoints = computeEdgeEndpoints(start, end, edge);
|
|
220
278
|
// Update Line
|
|
221
279
|
const line = group.querySelector('.viz-edge');
|
|
222
|
-
line.setAttribute('x1', String(start.
|
|
223
|
-
line.setAttribute('y1', String(start.
|
|
224
|
-
line.setAttribute('x2', String(end.
|
|
225
|
-
line.setAttribute('y2', String(end.
|
|
280
|
+
line.setAttribute('x1', String(endpoints.start.x));
|
|
281
|
+
line.setAttribute('y1', String(endpoints.start.y));
|
|
282
|
+
line.setAttribute('x2', String(endpoints.end.x));
|
|
283
|
+
line.setAttribute('y2', String(endpoints.end.y));
|
|
226
284
|
line.setAttribute('stroke', 'currentColor');
|
|
227
285
|
if (edge.markerEnd === 'arrow') {
|
|
228
286
|
line.setAttribute('marker-end', 'url(#viz-arrow)');
|
|
@@ -236,10 +294,10 @@ class VizBuilderImpl {
|
|
|
236
294
|
if (edge.hitArea || edge.onClick) {
|
|
237
295
|
const hit = document.createElementNS(svgNS, 'line');
|
|
238
296
|
hit.setAttribute('class', 'viz-edge-hit'); // Add class for selection
|
|
239
|
-
hit.setAttribute('x1', String(start.
|
|
240
|
-
hit.setAttribute('y1', String(start.
|
|
241
|
-
hit.setAttribute('x2', String(end.
|
|
242
|
-
hit.setAttribute('y2', String(end.
|
|
297
|
+
hit.setAttribute('x1', String(endpoints.start.x));
|
|
298
|
+
hit.setAttribute('y1', String(endpoints.start.y));
|
|
299
|
+
hit.setAttribute('x2', String(endpoints.end.x));
|
|
300
|
+
hit.setAttribute('y2', String(endpoints.end.y));
|
|
243
301
|
hit.setAttribute('stroke', 'transparent');
|
|
244
302
|
hit.setAttribute('stroke-width', String(edge.hitArea || 10));
|
|
245
303
|
hit.style.cursor = edge.onClick ? 'pointer' : '';
|
|
@@ -257,8 +315,8 @@ class VizBuilderImpl {
|
|
|
257
315
|
oldLabel.remove();
|
|
258
316
|
if (edge.label) {
|
|
259
317
|
const text = document.createElementNS(svgNS, 'text');
|
|
260
|
-
const mx = (start.
|
|
261
|
-
const my = (start.
|
|
318
|
+
const mx = (endpoints.start.x + endpoints.end.x) / 2 + (edge.label.dx || 0);
|
|
319
|
+
const my = (endpoints.start.y + endpoints.end.y) / 2 + (edge.label.dy || 0);
|
|
262
320
|
text.setAttribute('x', String(mx));
|
|
263
321
|
text.setAttribute('y', String(my));
|
|
264
322
|
text.setAttribute('class', `viz-edge-label ${edge.label.className || ''}`);
|
|
@@ -375,13 +433,17 @@ class VizBuilderImpl {
|
|
|
375
433
|
const pts = `${x},${y - hh} ${x + hw},${y} ${x},${y + hh} ${x - hw},${y}`;
|
|
376
434
|
shape.setAttribute('points', pts);
|
|
377
435
|
}
|
|
436
|
+
setSvgAttributes(shape, {
|
|
437
|
+
fill: node.style?.fill ?? 'none',
|
|
438
|
+
stroke: node.style?.stroke ?? '#111',
|
|
439
|
+
'stroke-width': node.style?.strokeWidth ?? 2,
|
|
440
|
+
opacity: node.style?.opacity,
|
|
441
|
+
});
|
|
378
442
|
// Label (Recreate for simplicity as usually just text/pos changes)
|
|
379
443
|
let label = group.querySelector('.viz-node-label');
|
|
380
444
|
if (!label && node.label) {
|
|
381
445
|
label = document.createElementNS(svgNS, 'text');
|
|
382
446
|
label.setAttribute('class', 'viz-node-label');
|
|
383
|
-
label.setAttribute('text-anchor', 'middle');
|
|
384
|
-
label.setAttribute('dominant-baseline', 'middle');
|
|
385
447
|
group.appendChild(label);
|
|
386
448
|
}
|
|
387
449
|
if (node.label) {
|
|
@@ -389,8 +451,15 @@ class VizBuilderImpl {
|
|
|
389
451
|
const ly = y + (node.label.dy || 0);
|
|
390
452
|
label.setAttribute('x', String(lx));
|
|
391
453
|
label.setAttribute('y', String(ly));
|
|
454
|
+
label.setAttribute('text-anchor', node.label.textAnchor || 'middle');
|
|
455
|
+
label.setAttribute('dominant-baseline', node.label.dominantBaseline || 'middle');
|
|
392
456
|
// Update class carefully to preserve 'viz-node-label'
|
|
393
457
|
label.setAttribute('class', `viz-node-label ${node.label.className || ''}`);
|
|
458
|
+
setSvgAttributes(label, {
|
|
459
|
+
fill: node.label.fill,
|
|
460
|
+
'font-size': node.label.fontSize,
|
|
461
|
+
'font-weight': node.label.fontWeight,
|
|
462
|
+
});
|
|
394
463
|
label.textContent = node.label.text;
|
|
395
464
|
}
|
|
396
465
|
else if (label) {
|
|
@@ -498,12 +567,13 @@ class VizBuilderImpl {
|
|
|
498
567
|
});
|
|
499
568
|
}
|
|
500
569
|
const markerEnd = edge.markerEnd === 'arrow' ? 'marker-end="url(#viz-arrow)"' : '';
|
|
570
|
+
const endpoints = computeEdgeEndpoints(start, end, edge);
|
|
501
571
|
svgContent += `<g class="viz-edge-group ${edge.className || ''} ${animClasses}" style="${animStyleStr}">`;
|
|
502
|
-
svgContent += `<line x1="${start.
|
|
572
|
+
svgContent += `<line x1="${endpoints.start.x}" y1="${endpoints.start.y}" x2="${endpoints.end.x}" y2="${endpoints.end.y}" class="viz-edge" ${markerEnd} stroke="currentColor" />`;
|
|
503
573
|
// Edge Label
|
|
504
574
|
if (edge.label) {
|
|
505
|
-
const mx = (start.
|
|
506
|
-
const my = (start.
|
|
575
|
+
const mx = (endpoints.start.x + endpoints.end.x) / 2 + (edge.label.dx || 0);
|
|
576
|
+
const my = (endpoints.start.y + endpoints.end.y) / 2 + (edge.label.dy || 0);
|
|
507
577
|
const labelClass = `viz-edge-label ${edge.label.className || ''}`;
|
|
508
578
|
svgContent += `<text x="${mx}" y="${my}" class="${labelClass}" text-anchor="middle" dominant-baseline="middle">${edge.label.text}</text>`;
|
|
509
579
|
}
|
|
@@ -536,25 +606,38 @@ class VizBuilderImpl {
|
|
|
536
606
|
}
|
|
537
607
|
const className = `viz-node-group ${node.className || ''} ${animClasses}`;
|
|
538
608
|
svgContent += `<g class="${className}" style="${animStyleStr}">`;
|
|
609
|
+
const shapeStyleAttrs = svgAttributeString({
|
|
610
|
+
fill: node.style?.fill ?? 'none',
|
|
611
|
+
stroke: node.style?.stroke ?? '#111',
|
|
612
|
+
'stroke-width': node.style?.strokeWidth ?? 2,
|
|
613
|
+
opacity: node.style?.opacity,
|
|
614
|
+
});
|
|
539
615
|
// Shape
|
|
540
616
|
if (shape.kind === 'circle') {
|
|
541
|
-
svgContent += `<circle cx="${x}" cy="${y}" r="${shape.r}" class="viz-node-shape" />`;
|
|
617
|
+
svgContent += `<circle cx="${x}" cy="${y}" r="${shape.r}" class="viz-node-shape"${shapeStyleAttrs} />`;
|
|
542
618
|
}
|
|
543
619
|
else if (shape.kind === 'rect') {
|
|
544
|
-
svgContent += `<rect x="${x - shape.w / 2}" y="${y - shape.h / 2}" width="${shape.w}" height="${shape.h}" rx="${shape.rx || 0}" class="viz-node-shape" />`;
|
|
620
|
+
svgContent += `<rect x="${x - shape.w / 2}" y="${y - shape.h / 2}" width="${shape.w}" height="${shape.h}" rx="${shape.rx || 0}" class="viz-node-shape"${shapeStyleAttrs} />`;
|
|
545
621
|
}
|
|
546
622
|
else if (shape.kind === 'diamond') {
|
|
547
623
|
const hw = shape.w / 2;
|
|
548
624
|
const hh = shape.h / 2;
|
|
549
625
|
const pts = `${x},${y - hh} ${x + hw},${y} ${x},${y + hh} ${x - hw},${y}`;
|
|
550
|
-
svgContent += `<polygon points="${pts}" class="viz-node-shape" />`;
|
|
626
|
+
svgContent += `<polygon points="${pts}" class="viz-node-shape"${shapeStyleAttrs} />`;
|
|
551
627
|
}
|
|
552
628
|
// Label
|
|
553
629
|
if (node.label) {
|
|
554
630
|
const lx = x + (node.label.dx || 0);
|
|
555
631
|
const ly = y + (node.label.dy || 0);
|
|
556
632
|
const labelClass = `viz-node-label ${node.label.className || ''}`;
|
|
557
|
-
|
|
633
|
+
const labelAttrs = svgAttributeString({
|
|
634
|
+
fill: node.label.fill,
|
|
635
|
+
'font-size': node.label.fontSize,
|
|
636
|
+
'font-weight': node.label.fontWeight,
|
|
637
|
+
'text-anchor': node.label.textAnchor || 'middle',
|
|
638
|
+
'dominant-baseline': node.label.dominantBaseline || 'middle',
|
|
639
|
+
});
|
|
640
|
+
svgContent += `<text x="${lx}" y="${ly}" class="${labelClass}"${labelAttrs}>${node.label.text}</text>`;
|
|
558
641
|
}
|
|
559
642
|
svgContent += '</g>';
|
|
560
643
|
});
|
|
@@ -626,6 +709,28 @@ class NodeBuilderImpl {
|
|
|
626
709
|
this.nodeDef.label = { text, ...opts };
|
|
627
710
|
return this;
|
|
628
711
|
}
|
|
712
|
+
fill(color) {
|
|
713
|
+
this.nodeDef.style = {
|
|
714
|
+
...(this.nodeDef.style || {}),
|
|
715
|
+
fill: color,
|
|
716
|
+
};
|
|
717
|
+
return this;
|
|
718
|
+
}
|
|
719
|
+
stroke(color, width) {
|
|
720
|
+
this.nodeDef.style = {
|
|
721
|
+
...(this.nodeDef.style || {}),
|
|
722
|
+
stroke: color,
|
|
723
|
+
strokeWidth: width ?? this.nodeDef.style?.strokeWidth,
|
|
724
|
+
};
|
|
725
|
+
return this;
|
|
726
|
+
}
|
|
727
|
+
opacity(value) {
|
|
728
|
+
this.nodeDef.style = {
|
|
729
|
+
...(this.nodeDef.style || {}),
|
|
730
|
+
opacity: value,
|
|
731
|
+
};
|
|
732
|
+
return this;
|
|
733
|
+
}
|
|
629
734
|
class(name) {
|
|
630
735
|
if (this.nodeDef.className) {
|
|
631
736
|
this.nodeDef.className += ` ${name}`;
|
|
@@ -682,13 +787,17 @@ class EdgeBuilderImpl {
|
|
|
682
787
|
return this;
|
|
683
788
|
}
|
|
684
789
|
label(text, opts) {
|
|
685
|
-
this.edgeDef.label = { position: 'mid', text, ...opts };
|
|
790
|
+
this.edgeDef.label = { position: 'mid', text, dy: -10, ...opts };
|
|
686
791
|
return this;
|
|
687
792
|
}
|
|
688
793
|
arrow(enabled = true) {
|
|
689
794
|
this.edgeDef.markerEnd = enabled ? 'arrow' : 'none';
|
|
690
795
|
return this;
|
|
691
796
|
}
|
|
797
|
+
connect(anchor) {
|
|
798
|
+
this.edgeDef.anchor = anchor;
|
|
799
|
+
return this;
|
|
800
|
+
}
|
|
692
801
|
class(name) {
|
|
693
802
|
if (this.edgeDef.className) {
|
|
694
803
|
this.edgeDef.className += ` ${name}`;
|
package/dist/types.d.ts
CHANGED
|
@@ -20,6 +20,11 @@ export type NodeLabel = {
|
|
|
20
20
|
dx?: number;
|
|
21
21
|
dy?: number;
|
|
22
22
|
className?: string;
|
|
23
|
+
fill?: string;
|
|
24
|
+
fontSize?: number | string;
|
|
25
|
+
fontWeight?: number | string;
|
|
26
|
+
textAnchor?: 'start' | 'middle' | 'end';
|
|
27
|
+
dominantBaseline?: string;
|
|
23
28
|
};
|
|
24
29
|
export type AnimationDuration = `${number}s`;
|
|
25
30
|
export interface AnimationConfig {
|
|
@@ -36,6 +41,12 @@ export interface VizNode {
|
|
|
36
41
|
pos: Vec2;
|
|
37
42
|
shape: NodeShape;
|
|
38
43
|
label?: NodeLabel;
|
|
44
|
+
style?: {
|
|
45
|
+
fill?: string;
|
|
46
|
+
stroke?: string;
|
|
47
|
+
strokeWidth?: number;
|
|
48
|
+
opacity?: number;
|
|
49
|
+
};
|
|
39
50
|
className?: string;
|
|
40
51
|
data?: unknown;
|
|
41
52
|
onClick?: (id: string, node: VizNode) => void;
|
|
@@ -54,6 +65,7 @@ export interface VizEdge {
|
|
|
54
65
|
to: string;
|
|
55
66
|
label?: EdgeLabel;
|
|
56
67
|
markerEnd?: 'arrow' | 'none';
|
|
68
|
+
anchor?: 'center' | 'boundary';
|
|
57
69
|
className?: string;
|
|
58
70
|
hitArea?: number;
|
|
59
71
|
data?: unknown;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vizcraft",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "A fluent, type-safe SVG scene builder for composing nodes, edges, animations, and overlays with incremental DOM updates and no framework dependency.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"visualization",
|
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
"module": "./dist/index.mjs",
|
|
29
29
|
"types": "./dist/index.d.ts",
|
|
30
30
|
"license": "MIT",
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md",
|
|
34
|
+
"CHANGELOG.md"
|
|
35
|
+
],
|
|
31
36
|
"devDependencies": {
|
|
32
37
|
"typescript": "^5.9.3"
|
|
33
38
|
},
|
package/.turbo/turbo-build.log
DELETED
package/src/animations.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import type { VizNode, VizEdge, VizAnimSpec } from './types';
|
|
3
|
-
|
|
4
|
-
export interface CoreAnimRendererContext<T = any> {
|
|
5
|
-
spec: VizAnimSpec<T>;
|
|
6
|
-
element: VizNode | VizEdge;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface CoreAnimRenderer<T = any> {
|
|
10
|
-
getClass?: (ctx: CoreAnimRendererContext<T>) => string;
|
|
11
|
-
getStyle?: (
|
|
12
|
-
ctx: CoreAnimRendererContext<T>
|
|
13
|
-
) => Record<string, string | number>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class CoreAnimationRegistry {
|
|
17
|
-
private nodeAnims = new Map<string, CoreAnimRenderer>();
|
|
18
|
-
private edgeAnims = new Map<string, CoreAnimRenderer>();
|
|
19
|
-
|
|
20
|
-
constructor() {}
|
|
21
|
-
|
|
22
|
-
registerNode(id: string, renderer: CoreAnimRenderer) {
|
|
23
|
-
this.nodeAnims.set(id, renderer);
|
|
24
|
-
return this;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
registerEdge(id: string, renderer: CoreAnimRenderer) {
|
|
28
|
-
this.edgeAnims.set(id, renderer);
|
|
29
|
-
return this;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
getNodeRenderer(id: string): CoreAnimRenderer | undefined {
|
|
33
|
-
return this.nodeAnims.get(id);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
getEdgeRenderer(id: string): CoreAnimRenderer | undefined {
|
|
37
|
-
return this.edgeAnims.get(id);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Default Implementations
|
|
42
|
-
export const coreFlowAnimation: CoreAnimRenderer<{ duration?: string }> = {
|
|
43
|
-
getClass: () => 'viz-anim-flow',
|
|
44
|
-
getStyle: ({ spec }) => {
|
|
45
|
-
const duration = spec.params?.duration ?? '2s';
|
|
46
|
-
return {
|
|
47
|
-
'--viz-anim-duration': duration,
|
|
48
|
-
};
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export const defaultCoreAnimationRegistry =
|
|
53
|
-
new CoreAnimationRegistry().registerEdge('flow', coreFlowAnimation);
|