vizcraft 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './builder';
3
+ export * from './styles';
4
+ export * from './animations';
5
+ export * from './overlays';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './builder';
3
+ export * from './styles';
4
+ export * from './animations';
5
+ export * from './overlays';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { viz } from './index';
3
+ describe('vizcraft core', () => {
4
+ it('exports viz builder', () => {
5
+ expect(viz).toBeDefined();
6
+ expect(typeof viz).toBe('function');
7
+ });
8
+ it('creates a builder instance', () => {
9
+ const builder = viz();
10
+ expect(builder).toBeDefined();
11
+ // Verify default viewbox
12
+ const view = builder._getViewBox();
13
+ expect(view).toEqual({ w: 800, h: 600 });
14
+ });
15
+ });
@@ -0,0 +1,38 @@
1
+ import type { VizNode, VizEdge, VizOverlaySpec, VizScene } from './types';
2
+ export interface CoreOverlayRenderContext<T = any> {
3
+ spec: VizOverlaySpec<T>;
4
+ nodesById: Map<string, VizNode>;
5
+ edgesById: Map<string, VizEdge>;
6
+ scene: VizScene;
7
+ }
8
+ export interface CoreOverlayRenderer<T = any> {
9
+ render: (ctx: CoreOverlayRenderContext<T>) => string;
10
+ update?: (ctx: CoreOverlayRenderContext<T>, container: SVGGElement) => void;
11
+ }
12
+ export declare class CoreOverlayRegistry {
13
+ private overlays;
14
+ register(id: string, renderer: CoreOverlayRenderer): this;
15
+ get(id: string): CoreOverlayRenderer<any> | undefined;
16
+ }
17
+ export declare const coreSignalOverlay: CoreOverlayRenderer<{
18
+ from: string;
19
+ to: string;
20
+ progress: number;
21
+ magnitude?: number;
22
+ }>;
23
+ export declare const coreGridLabelsOverlay: CoreOverlayRenderer<{
24
+ colLabels?: Record<number, string>;
25
+ rowLabels?: Record<number, string>;
26
+ yOffset?: number;
27
+ xOffset?: number;
28
+ }>;
29
+ interface DataPoint {
30
+ id: string;
31
+ currentNodeId: string;
32
+ [key: string]: any;
33
+ }
34
+ export declare const coreDataPointOverlay: CoreOverlayRenderer<{
35
+ points: DataPoint[];
36
+ }>;
37
+ export declare const defaultCoreOverlayRegistry: CoreOverlayRegistry;
38
+ export {};
@@ -0,0 +1,139 @@
1
+ export class CoreOverlayRegistry {
2
+ overlays = new Map();
3
+ register(id, renderer) {
4
+ this.overlays.set(id, renderer);
5
+ return this;
6
+ }
7
+ get(id) {
8
+ return this.overlays.get(id);
9
+ }
10
+ }
11
+ // Built-in Overlay: Signal
12
+ export const coreSignalOverlay = {
13
+ render: ({ spec, nodesById }) => {
14
+ const { from, to, progress } = spec.params;
15
+ const start = nodesById.get(from);
16
+ const end = nodesById.get(to);
17
+ if (!start || !end)
18
+ return '';
19
+ const x = start.pos.x + (end.pos.x - start.pos.x) * progress;
20
+ const y = start.pos.y + (end.pos.y - start.pos.y) * progress;
21
+ let v = Math.abs(spec.params.magnitude ?? 1);
22
+ if (v > 1)
23
+ v = 1;
24
+ const r = 2 + v * 4;
25
+ const className = spec.className ?? 'viz-signal';
26
+ return `
27
+ <g transform="translate(${x}, ${y})">
28
+ <g class="${className}">
29
+ <circle r="10" fill="transparent" stroke="none" />
30
+ <circle r="${r}" class="viz-signal-shape" />
31
+ </g>
32
+ </g>
33
+ `;
34
+ },
35
+ };
36
+ // Built-in Overlay: Grid Labels
37
+ export const coreGridLabelsOverlay = {
38
+ render: ({ spec, scene }) => {
39
+ const grid = scene.grid;
40
+ if (!grid)
41
+ return '';
42
+ const { w, h } = scene.viewBox;
43
+ const { colLabels, rowLabels, yOffset = 20, xOffset = 20 } = spec.params;
44
+ // Safer string rendering for overlay to avoid weird spacing if grid missing
45
+ const cellW = (w - grid.padding.x * 2) / grid.cols;
46
+ const cellH = (h - grid.padding.y * 2) / grid.rows;
47
+ let output = '';
48
+ if (colLabels) {
49
+ Object.entries(colLabels).forEach(([colStr, text]) => {
50
+ const col = parseInt(colStr, 10);
51
+ const x = grid.padding.x + col * cellW + cellW / 2;
52
+ const cls = spec.className || 'viz-grid-label';
53
+ output += `<text x="${x}" y="${yOffset}" class="${cls}" text-anchor="middle">${text}</text>`;
54
+ });
55
+ }
56
+ if (rowLabels) {
57
+ Object.entries(rowLabels).forEach(([rowStr, text]) => {
58
+ const row = parseInt(rowStr, 10);
59
+ const y = grid.padding.y + row * cellH + cellH / 2;
60
+ const cls = spec.className || 'viz-grid-label';
61
+ output += `<text x="${xOffset}" y="${y}" dy=".35em" class="${cls}" text-anchor="middle">${text}</text>`;
62
+ });
63
+ }
64
+ return output;
65
+ },
66
+ };
67
+ // Built-in Overlay: Data Points
68
+ export const coreDataPointOverlay = {
69
+ render: ({ spec, nodesById }) => {
70
+ const { points } = spec.params;
71
+ let output = '';
72
+ points.forEach((point) => {
73
+ const node = nodesById.get(point.currentNodeId);
74
+ if (!node)
75
+ return;
76
+ const idNum = parseInt(point.id.split('-')[1] || '0', 10);
77
+ const offsetX = ((idNum % 5) - 2) * 10;
78
+ const offsetY = ((idNum % 3) - 1) * 10;
79
+ const x = node.pos.x + offsetX;
80
+ const y = node.pos.y + offsetY;
81
+ const cls = spec.className ?? 'viz-data-point';
82
+ // Important: Add data-id so we can find it later in update()
83
+ output += `<circle data-id="${point.id}" cx="${x}" cy="${y}" r="6" class="${cls}" />`;
84
+ });
85
+ return output;
86
+ },
87
+ update: ({ spec, nodesById }, container) => {
88
+ const { points } = spec.params;
89
+ const svgNS = 'http://www.w3.org/2000/svg';
90
+ // 1. Map existing elements by data-id
91
+ const existingMap = new Map();
92
+ Array.from(container.children).forEach((child) => {
93
+ if (child.tagName === 'circle') {
94
+ const id = child.getAttribute('data-id');
95
+ if (id)
96
+ existingMap.set(id, child);
97
+ }
98
+ });
99
+ const processedIds = new Set();
100
+ // 2. Create or Update Points
101
+ points.forEach((point) => {
102
+ const node = nodesById.get(point.currentNodeId);
103
+ if (!node)
104
+ return;
105
+ processedIds.add(point.id);
106
+ const idNum = parseInt(point.id.split('-')[1] || '0', 10);
107
+ const offsetX = ((idNum % 5) - 2) * 10;
108
+ const offsetY = ((idNum % 3) - 1) * 10;
109
+ const x = node.pos.x + offsetX;
110
+ const y = node.pos.y + offsetY;
111
+ let circle = existingMap.get(point.id);
112
+ if (!circle) {
113
+ // Create new
114
+ circle = document.createElementNS(svgNS, 'circle');
115
+ circle.setAttribute('data-id', point.id);
116
+ circle.setAttribute('r', '6');
117
+ container.appendChild(circle);
118
+ }
119
+ // Update attrs (this triggers CSS transition if class has it)
120
+ circle.setAttribute('cx', String(x));
121
+ circle.setAttribute('cy', String(y));
122
+ const cls = spec.className ?? 'viz-data-point';
123
+ // Only set class if different to avoid potential re-flows (though usually fine)
124
+ if (circle.getAttribute('class') !== cls) {
125
+ circle.setAttribute('class', cls);
126
+ }
127
+ });
128
+ // 3. Remove stale points
129
+ existingMap.forEach((el, id) => {
130
+ if (!processedIds.has(id)) {
131
+ el.remove();
132
+ }
133
+ });
134
+ },
135
+ };
136
+ export const defaultCoreOverlayRegistry = new CoreOverlayRegistry()
137
+ .register('signal', coreSignalOverlay)
138
+ .register('grid-labels', coreGridLabelsOverlay)
139
+ .register('data-points', coreDataPointOverlay);
@@ -0,0 +1 @@
1
+ export declare const DEFAULT_VIZ_CSS = "\n.viz-canvas {\n width: 100%;\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n\n.viz-canvas svg {\n width: 100%;\n height: 100%;\n overflow: visible;\n}\n\n/* Keyframes */\n@keyframes vizFlow {\n from {\n stroke-dashoffset: 20;\n }\n to {\n stroke-dashoffset: 0;\n }\n}\n\n/* Animation Classes */\n\n/* Flow Animation (Dashed line moving) */\n.viz-anim-flow .viz-edge {\n stroke-dasharray: 5, 5;\n animation: vizFlow var(--viz-anim-duration, 2s) linear infinite;\n}\n\n/* Node Transition */\n.viz-node-group {\n transition: transform 0.3s ease-out, opacity 0.3s ease-out;\n}\n\n/* Overlay Classes */\n.viz-grid-label {\n fill: #6B7280;\n font-size: 14px;\n font-weight: 600;\n opacity: 1;\n}\n\n.viz-signal {\n fill: #3B82F6;\n cursor: pointer;\n pointer-events: all; \n transition: transform 0.2s ease-out, fill 0.2s ease-out;\n}\n\n.viz-signal .viz-signal-shape {\n fill: inherit;\n}\n\n.viz-signal:hover {\n fill: #60A5FA;\n transform: scale(1.5);\n}\n\n.viz-data-point {\n fill: #F59E0B;\n transition: cx 0.3s ease-out, cy 0.3s ease-out;\n}\n";
package/dist/styles.js ADDED
@@ -0,0 +1,67 @@
1
+ export const DEFAULT_VIZ_CSS = `
2
+ .viz-canvas {
3
+ width: 100%;
4
+ height: 100%;
5
+ display: flex;
6
+ justify-content: center;
7
+ align-items: center;
8
+ }
9
+
10
+ .viz-canvas svg {
11
+ width: 100%;
12
+ height: 100%;
13
+ overflow: visible;
14
+ }
15
+
16
+ /* Keyframes */
17
+ @keyframes vizFlow {
18
+ from {
19
+ stroke-dashoffset: 20;
20
+ }
21
+ to {
22
+ stroke-dashoffset: 0;
23
+ }
24
+ }
25
+
26
+ /* Animation Classes */
27
+
28
+ /* Flow Animation (Dashed line moving) */
29
+ .viz-anim-flow .viz-edge {
30
+ stroke-dasharray: 5, 5;
31
+ animation: vizFlow var(--viz-anim-duration, 2s) linear infinite;
32
+ }
33
+
34
+ /* Node Transition */
35
+ .viz-node-group {
36
+ transition: transform 0.3s ease-out, opacity 0.3s ease-out;
37
+ }
38
+
39
+ /* Overlay Classes */
40
+ .viz-grid-label {
41
+ fill: #6B7280;
42
+ font-size: 14px;
43
+ font-weight: 600;
44
+ opacity: 1;
45
+ }
46
+
47
+ .viz-signal {
48
+ fill: #3B82F6;
49
+ cursor: pointer;
50
+ pointer-events: all;
51
+ transition: transform 0.2s ease-out, fill 0.2s ease-out;
52
+ }
53
+
54
+ .viz-signal .viz-signal-shape {
55
+ fill: inherit;
56
+ }
57
+
58
+ .viz-signal:hover {
59
+ fill: #60A5FA;
60
+ transform: scale(1.5);
61
+ }
62
+
63
+ .viz-data-point {
64
+ fill: #F59E0B;
65
+ transition: cx 0.3s ease-out, cy 0.3s ease-out;
66
+ }
67
+ `;
@@ -0,0 +1,86 @@
1
+ export type Vec2 = {
2
+ x: number;
3
+ y: number;
4
+ };
5
+ export type NodeShape = {
6
+ kind: 'circle';
7
+ r: number;
8
+ } | {
9
+ kind: 'rect';
10
+ w: number;
11
+ h: number;
12
+ rx?: number;
13
+ } | {
14
+ kind: 'diamond';
15
+ w: number;
16
+ h: number;
17
+ };
18
+ export type NodeLabel = {
19
+ text: string;
20
+ dx?: number;
21
+ dy?: number;
22
+ className?: string;
23
+ };
24
+ export type AnimationDuration = `${number}s`;
25
+ export interface AnimationConfig {
26
+ duration?: AnimationDuration;
27
+ [key: string]: any;
28
+ }
29
+ export interface VizAnimSpec<T = any> {
30
+ id: string;
31
+ params?: T;
32
+ when?: boolean;
33
+ }
34
+ export interface VizNode {
35
+ id: string;
36
+ pos: Vec2;
37
+ shape: NodeShape;
38
+ label?: NodeLabel;
39
+ className?: string;
40
+ data?: unknown;
41
+ onClick?: (id: string, node: VizNode) => void;
42
+ animations?: VizAnimSpec[];
43
+ }
44
+ export interface EdgeLabel {
45
+ text: string;
46
+ position: 'start' | 'mid' | 'end';
47
+ className?: string;
48
+ dx?: number;
49
+ dy?: number;
50
+ }
51
+ export interface VizEdge {
52
+ id: string;
53
+ from: string;
54
+ to: string;
55
+ label?: EdgeLabel;
56
+ markerEnd?: 'arrow' | 'none';
57
+ className?: string;
58
+ hitArea?: number;
59
+ data?: unknown;
60
+ onClick?: (id: string, edge: VizEdge) => void;
61
+ animations?: VizAnimSpec[];
62
+ }
63
+ export type VizOverlaySpec<T = any> = {
64
+ id: string;
65
+ key?: string;
66
+ params: T;
67
+ className?: string;
68
+ };
69
+ export interface VizGridConfig {
70
+ cols: number;
71
+ rows: number;
72
+ padding: {
73
+ x: number;
74
+ y: number;
75
+ };
76
+ }
77
+ export type VizScene = {
78
+ viewBox: {
79
+ w: number;
80
+ h: number;
81
+ };
82
+ grid?: VizGridConfig;
83
+ nodes: VizNode[];
84
+ edges: VizEdge[];
85
+ overlays?: VizOverlaySpec[];
86
+ };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "vizcraft",
3
+ "version": "0.1.1",
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
+ "keywords": [
6
+ "visualization",
7
+ "svg",
8
+ "graph",
9
+ "diagram",
10
+ "animation",
11
+ "react",
12
+ "typescript"
13
+ ],
14
+ "author": {
15
+ "name": "Chipili Kafwilo",
16
+ "email": "ckafwilo@gmail.com",
17
+ "url": "https://chipilidev.com"
18
+ },
19
+ "homepage": "https://github.com/ChipiKaf/vizcraft#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/ChipiKaf/vizcraft/issues"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/ChipiKaf/vizcraft.git"
26
+ },
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.mjs",
29
+ "types": "./dist/index.d.ts",
30
+ "license": "MIT",
31
+ "devDependencies": {
32
+ "typescript": "^5.9.3"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc",
36
+ "lint": "eslint 'src/**/*.{ts,tsx}'",
37
+ "test": "vitest run"
38
+ }
39
+ }
@@ -0,0 +1,53 @@
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);