quixotic-gol 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/README.md ADDED
@@ -0,0 +1,424 @@
1
+ # quixotic-gol
2
+
3
+ A powerful React library for custom Graphviz layout rendering with Web Worker support for handling large graphs without blocking the UI.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Web Worker Support** - Process large graphs in background without blocking the UI
8
+ - ⚛️ **React Components** - Ready-to-use React hooks and components
9
+ - 🎨 **Custom Rendering** - Full control over graph visualization with D3.js
10
+ - 📦 **TypeScript First** - Full TypeScript support with comprehensive type definitions
11
+ - 🔧 **Configurable** - Customizable layout configuration (spacing, corner radius, orthogonal rendering)
12
+ - 📊 **Depth Filtering** - Filter graph nodes by depth level
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install quixotic-gol
18
+ # or
19
+ pnpm add quixotic-gol
20
+ # or
21
+ yarn add quixotic-gol
22
+ ```
23
+
24
+ ## Peer Dependencies
25
+
26
+ ```bash
27
+ npm install react react-dom
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ### Simplest Way: Using GraphvizRenderer Component (Recommended)
33
+
34
+ The easiest way to render DOT strings with built-in worker and loading indicator:
35
+
36
+ ```tsx
37
+ import { GraphvizRenderer } from 'quixotic-gol';
38
+
39
+ function MyGraph() {
40
+ const dotString = `
41
+ digraph G {
42
+ A -> B;
43
+ B -> C;
44
+ }
45
+ `;
46
+
47
+ return (
48
+ <GraphvizRenderer
49
+ dotString={dotString}
50
+ onSuccess={(jsonString) => {
51
+ const json = JSON.parse(jsonString);
52
+ console.log('Rendered:', json);
53
+ // Use the JSON result for custom rendering
54
+ }}
55
+ onError={(error) => {
56
+ console.error('Error:', error);
57
+ }}
58
+ />
59
+ );
60
+ }
61
+ ```
62
+
63
+ **Features:**
64
+ - ✅ Only uses Web Worker (no main thread blocking)
65
+ - ✅ Built-in loading indicator with cancel button
66
+ - ✅ Automatic error handling
67
+ - ✅ Simple, declarative API
68
+
69
+ See [GraphvizRenderer documentation](./docs/GraphvizRenderer.md) for more examples.
70
+
71
+ ### Basic Usage with Hook
72
+
73
+ ```tsx
74
+ import { useGraphvizWorker, renderCustomGraph } from 'quixotic-gol';
75
+ import { useRef, useEffect } from 'react';
76
+
77
+ function MyGraph() {
78
+ const containerRef = useRef<HTMLDivElement>(null);
79
+ const { layout, isLoading } = useGraphvizWorker();
80
+
81
+ useEffect(() => {
82
+ const dotString = `
83
+ digraph G {
84
+ A -> B;
85
+ B -> C;
86
+ }
87
+ `;
88
+
89
+ layout(dotString).then((jsonStr) => {
90
+ const json = JSON.parse(jsonStr);
91
+ if (containerRef.current) {
92
+ renderCustomGraph(
93
+ containerRef.current,
94
+ json,
95
+ {}, // node mapping
96
+ null, // max depth
97
+ true, // include lesser depths
98
+ createLayoutConfig(),
99
+ true, // use orthogonal rendering
100
+ 14 // font size
101
+ );
102
+ }
103
+ });
104
+ }, [layout]);
105
+
106
+ return (
107
+ <div>
108
+ {isLoading && <p>Loading...</p>}
109
+ <div ref={containerRef} />
110
+ </div>
111
+ );
112
+ }
113
+ ```
114
+
115
+ ### With Loading Indicator Component
116
+
117
+ ```tsx
118
+ import {
119
+ useGraphvizWorker,
120
+ WorkerLoadingIndicator
121
+ } from 'quixotic-gol';
122
+
123
+ function MyGraph() {
124
+ const { layout, cancel, isLoading } = useGraphvizWorker();
125
+
126
+ return (
127
+ <div className="relative">
128
+ <div ref={containerRef} className="graph-container" />
129
+
130
+ {isLoading && (
131
+ <WorkerLoadingIndicator onCancel={cancel} />
132
+ )}
133
+ </div>
134
+ );
135
+ }
136
+ ```
137
+
138
+ ## API Reference
139
+
140
+ ### Hooks
141
+
142
+ #### `useGraphvizWorker()`
143
+
144
+ React hook for processing DOT strings using a Web Worker.
145
+
146
+ **Returns:**
147
+ ```typescript
148
+ {
149
+ layout: (dot: string) => Promise<string>; // Process DOT string, returns JSON
150
+ cancel: () => void; // Cancel current processing
151
+ isLoading: boolean; // Loading state
152
+ error: string | null; // Error message
153
+ }
154
+ ```
155
+
156
+ ### Components
157
+
158
+ #### `GraphvizRenderer` (Recommended)
159
+
160
+ A powerful component that renders DOT strings using Web Worker with built-in loading indicator, zoom controls, and edge selection support.
161
+
162
+ **Props:**
163
+ ```typescript
164
+ interface GraphvizRendererProps {
165
+ /** DOT string to render (required) */
166
+ dotString: string;
167
+ /** Node mapping for custom rendering */
168
+ nodeMapping?: Record<string, NodeMapping>;
169
+ /** SVG font size (default: 14) */
170
+ svgFontSize?: number;
171
+ /** Custom loading indicator component */
172
+ loadingIndicator?: React.ReactNode;
173
+ /** Callback when rendering succeeds with parsed JSON result */
174
+ onSuccess?: (json: GraphvizJson) => void;
175
+ /** Callback when rendering fails */
176
+ onError?: (error: Error) => void;
177
+ /** Callback on zoom with font size information */
178
+ onZoom?: (info: { baseFontSize: number; actualFontSize: number; scale: number }) => void;
179
+ /** Callback for render progress with elapsed time in milliseconds */
180
+ onRenderProgress?: (elapsedMs: number) => void;
181
+ /** Additional class name for the container */
182
+ className?: string;
183
+ /** Show loading indicator (default: true) */
184
+ showLoading?: boolean;
185
+ /** Enable debug visual indicators - yellow border and center dot (default: false) */
186
+ debug?: boolean;
187
+ /** Currently selected edge ID (controlled component) */
188
+ selectedEdgeId?: number | null;
189
+ /** Callback when an edge is selected or deselected */
190
+ onEdgeSelect?: (edgeId: number | null) => void;
191
+ /** Use native Graphviz rendering instead of custom rendering (default: false) */
192
+ useNativeRendering?: boolean;
193
+ /** Custom node width in points (default: 240) */
194
+ nodeWidth?: number;
195
+ /** Custom node height in points (default: 64) */
196
+ nodeHeight?: number;
197
+ /** Custom spacing between node columns in points (default: 800) */
198
+ nodeColumnSpacing?: number;
199
+ }
200
+ ```
201
+
202
+ **Ref API:**
203
+
204
+ Access programmatic zoom and rendering controls via ref:
205
+
206
+ ```typescript
207
+ interface GraphvizRendererRef {
208
+ /** Zoom in by increasing the effective font size */
209
+ zoomIn: (percentage?: number) => void;
210
+ /** Zoom out by decreasing the effective font size */
211
+ zoomOut: (percentage?: number) => void;
212
+ /** Toggle between full overview and centered view with normal font size (16px) */
213
+ toggleZoom: () => void;
214
+ /** Stop the current rendering operation and terminate the worker */
215
+ stopRendering: () => void;
216
+ }
217
+ ```
218
+
219
+ **Basic Example:**
220
+ ```tsx
221
+ <GraphvizRenderer
222
+ dotString={myDotString}
223
+ onSuccess={(json) => console.log('Rendered:', json)}
224
+ onError={(err) => console.error(err)}
225
+ />
226
+ ```
227
+
228
+ **Full-Featured Example with Ref API:**
229
+ ```tsx
230
+ import { useRef } from 'react';
231
+ import { GraphvizRenderer, GraphvizRendererRef } from 'quixotic-gol';
232
+
233
+ function MyGraph() {
234
+ const rendererRef = useRef<GraphvizRendererRef>(null);
235
+ const [selectedEdge, setSelectedEdge] = useState<number | null>(null);
236
+
237
+ return (
238
+ <div>
239
+ {/* Zoom controls */}
240
+ <button onClick={() => rendererRef.current?.zoomIn(10)}>Zoom In</button>
241
+ <button onClick={() => rendererRef.current?.zoomOut(10)}>Zoom Out</button>
242
+ <button onClick={() => rendererRef.current?.toggleZoom()}>Toggle Zoom</button>
243
+ <button onClick={() => rendererRef.current?.stopRendering()}>Stop</button>
244
+
245
+ <GraphvizRenderer
246
+ ref={rendererRef}
247
+ dotString={dotString}
248
+ nodeMapping={nodeMapping}
249
+ svgFontSize={14}
250
+ className="w-full h-full"
251
+ debug={false}
252
+ nodeWidth={240}
253
+ nodeHeight={64}
254
+ nodeColumnSpacing={800}
255
+ selectedEdgeId={selectedEdge}
256
+ onEdgeSelect={setSelectedEdge}
257
+ onZoom={({ actualFontSize, scale }) => {
258
+ console.log(`Zoom: ${scale}x, Font: ${actualFontSize}px`);
259
+ }}
260
+ onRenderProgress={(elapsedMs) => {
261
+ console.log(`Rendering: ${elapsedMs}ms`);
262
+ }}
263
+ onSuccess={(json) => console.log('Rendered:', json)}
264
+ onError={(err) => console.error('Error:', err)}
265
+ />
266
+ </div>
267
+ );
268
+ }
269
+ ```
270
+
271
+ See [GraphvizRenderer documentation](./docs/GraphvizRenderer.md) for more examples.
272
+
273
+ #### `WorkerLoadingIndicator`
274
+
275
+ Loading indicator component with cancel button.
276
+
277
+ **Props:**
278
+ ```typescript
279
+ {
280
+ onCancel: () => void; // Callback when cancel is clicked
281
+ }
282
+ ```
283
+
284
+ ### Functions
285
+
286
+ #### `renderCustomGraph()`
287
+
288
+ Render a graph with custom styling.
289
+
290
+ ```typescript
291
+ function renderCustomGraph(
292
+ container: HTMLDivElement,
293
+ json: GraphvizJson,
294
+ nodeMapping: Record<string, NodeMapping>,
295
+ maxDepth: number | null,
296
+ includeLesserDepths: boolean,
297
+ config: LayoutConfig,
298
+ useOrthogonalRendering: boolean,
299
+ svgFontSize: number
300
+ ): void
301
+ ```
302
+
303
+ #### `setupZoomPan()`
304
+
305
+ Setup zoom and pan interactions for the graph.
306
+
307
+ ```typescript
308
+ function setupZoomPan(container: HTMLDivElement): void
309
+ ```
310
+
311
+ #### `createLayoutConfig()`
312
+
313
+ Create layout configuration with optional overrides.
314
+
315
+ ```typescript
316
+ function createLayoutConfig(overrides?: Partial<LayoutConfig>): LayoutConfig
317
+ ```
318
+
319
+ **Default Configuration:**
320
+ ```typescript
321
+ {
322
+ edgeSpacing: 80, // Spacing between edges in pixels
323
+ cornerRadius: 15, // Corner radius for edge curves
324
+ }
325
+ ```
326
+
327
+ ### Types
328
+
329
+ ```typescript
330
+ export interface NodeMapping {
331
+ sourceNodeId: string;
332
+ targetNodeId: string;
333
+ }
334
+
335
+ export interface GraphvizJson {
336
+ objects?: Array<{
337
+ _ldraw_?: Array<{ size?: number }>;
338
+ // ... other properties
339
+ }>;
340
+ edges?: Array<any>;
341
+ // ... other properties
342
+ }
343
+
344
+ export interface LayoutConfig {
345
+ edgeSpacing: number;
346
+ cornerRadius: number;
347
+ }
348
+ ```
349
+
350
+ ## Configuration Options
351
+
352
+ ### Layout Configuration
353
+
354
+ ```typescript
355
+ import { createLayoutConfig } from 'quixotic-gol';
356
+
357
+ const config = createLayoutConfig({
358
+ edgeSpacing: 100, // Increase spacing between parallel edges
359
+ cornerRadius: 20, // Smoother corners
360
+ });
361
+ ```
362
+
363
+ ### Depth Filtering
364
+
365
+ ```typescript
366
+ // Show only nodes at depth 0-2
367
+ renderCustomGraph(
368
+ container,
369
+ json,
370
+ nodeMapping,
371
+ 2, // maxDepth
372
+ true, // includeLesserDepths - show depth 0, 1, and 2
373
+ config,
374
+ true,
375
+ 14
376
+ );
377
+ ```
378
+
379
+ ## Advanced Usage
380
+
381
+ ### Direct Worker Access
382
+
383
+ For advanced use cases, you can import worker types:
384
+
385
+ ```typescript
386
+ import type { WorkerRequest, WorkerResponse } from 'quixotic-gol/worker';
387
+ ```
388
+
389
+ ## Demo Application
390
+
391
+ The package includes a full Next.js demo application. To run it:
392
+
393
+ ```bash
394
+ git clone <repository-url>
395
+ cd graphviz-layouter
396
+ pnpm install
397
+ pnpm dev
398
+ ```
399
+
400
+ Visit `http://localhost:3000` to see the demo.
401
+
402
+ ## Building from Source
403
+
404
+ ```bash
405
+ # Install dependencies
406
+ pnpm install
407
+
408
+ # Build library
409
+ pnpm run build:lib
410
+
411
+ # Build Next.js demo
412
+ pnpm run build:next
413
+
414
+ # Build both
415
+ pnpm run build
416
+ ```
417
+
418
+ ## License
419
+
420
+ MIT
421
+
422
+ ## Contributing
423
+
424
+ Contributions are welcome! Please feel free to submit a Pull Request.