@@ -2,6 +2,157 @@ import React, { useEffect, useMemo, useReducer } from "react"
22import type { BaseSolver } from "../BaseSolver"
33import { InteractiveGraphics } from "graphics-debug/react"
44import { GenericSolverToolbar } from "./GenericSolverToolbar"
5+ import type { GraphicsObject } from "graphics-debug"
6+
7+ class ErrorBoundary extends React . Component <
8+ { fallback : React . ReactNode ; children : React . ReactNode } ,
9+ { hasError : boolean }
10+ > {
11+ constructor ( props : { fallback : React . ReactNode ; children : React . ReactNode } ) {
12+ super ( props )
13+ this . state = { hasError : false }
14+ }
15+ static getDerivedStateFromError ( ) {
16+ return { hasError : true }
17+ }
18+ override componentDidCatch ( error : any ) {
19+ console . error ( "InteractiveGraphics render error:" , error )
20+ }
21+ override render ( ) {
22+ if ( this . state . hasError ) {
23+ return this . props . fallback
24+ }
25+ return this . props . children
26+ }
27+ }
28+
29+ function SimpleGraphicsSVG ( { graphics } : { graphics : GraphicsObject } ) {
30+ const points = graphics . points ?? [ ]
31+ const lines = graphics . lines ?? [ ]
32+ const rects = graphics . rects ?? [ ]
33+ const circles = graphics . circles ?? [ ]
34+ const texts = ( graphics as any ) . texts ?? [ ]
35+
36+ let minX = Number . POSITIVE_INFINITY
37+ let minY = Number . POSITIVE_INFINITY
38+ let maxX = Number . NEGATIVE_INFINITY
39+ let maxY = Number . NEGATIVE_INFINITY
40+
41+ const consider = ( x ?: number , y ?: number ) => {
42+ if ( typeof x === "number" ) {
43+ if ( x < minX ) minX = x
44+ if ( x > maxX ) maxX = x
45+ }
46+ if ( typeof y === "number" ) {
47+ if ( y < minY ) minY = y
48+ if ( y > maxY ) maxY = y
49+ }
50+ }
51+
52+ for ( const p of points ) consider ( ( p as any ) . x , ( p as any ) . y )
53+ for ( const l of lines ) {
54+ const pts = ( l as any ) . points ?? [ ]
55+ for ( const p of pts ) consider ( p . x , p . y )
56+ }
57+ for ( const r of rects ) {
58+ const x = ( r as any ) . x ?? 0
59+ const y = ( r as any ) . y ?? 0
60+ const w = ( r as any ) . width ?? 0
61+ const h = ( r as any ) . height ?? 0
62+ consider ( x , y )
63+ consider ( x + w , y + h )
64+ }
65+ for ( const c of circles ) {
66+ const x = ( c as any ) . x ?? 0
67+ const y = ( c as any ) . y ?? 0
68+ const rad = ( c as any ) . radius ?? 1
69+ consider ( x - rad , y - rad )
70+ consider ( x + rad , y + rad )
71+ }
72+ for ( const t of texts ) consider ( ( t as any ) . x , ( t as any ) . y )
73+
74+ if (
75+ ! isFinite ( minX ) ||
76+ ! isFinite ( minY ) ||
77+ ! isFinite ( maxX ) ||
78+ ! isFinite ( maxY )
79+ ) {
80+ minX = - 20
81+ minY = - 20
82+ maxX = 20
83+ maxY = 20
84+ }
85+
86+ const pad = 10
87+ const vbX = minX - pad
88+ const vbY = minY - pad
89+ const vbW = Math . max ( 1 , maxX - minX + 2 * pad )
90+ const vbH = Math . max ( 1 , maxY - minY + 2 * pad )
91+
92+ return (
93+ < svg
94+ className = "w-full h-[400px] bg-white"
95+ viewBox = { `${ vbX } ${ vbY } ${ vbW } ${ vbH } ` }
96+ role = "img"
97+ aria-label = "Graphics fallback"
98+ >
99+ { rects . map ( ( r : any , i : number ) => (
100+ < rect
101+ key = { `rect-${ i } ` }
102+ x = { r . x ?? 0 }
103+ y = { r . y ?? 0 }
104+ width = { r . width ?? 0 }
105+ height = { r . height ?? 0 }
106+ fill = "none"
107+ stroke = { r . strokeColor ?? "black" }
108+ strokeWidth = { r . strokeWidth ?? 1 }
109+ />
110+ ) ) }
111+ { lines . map ( ( l : any , i : number ) => (
112+ < polyline
113+ key = { `line-${ i } ` }
114+ fill = "none"
115+ stroke = { l . strokeColor ?? "black" }
116+ strokeWidth = { l . strokeWidth ?? 1 }
117+ points = { ( l . points ?? [ ] )
118+ . map ( ( p : any ) => `${ p . x ?? 0 } ,${ p . y ?? 0 } ` )
119+ . join ( " " ) }
120+ />
121+ ) ) }
122+ { circles . map ( ( c : any , i : number ) => (
123+ < circle
124+ key = { `circle-${ i } ` }
125+ cx = { c . x ?? 0 }
126+ cy = { c . y ?? 0 }
127+ r = { c . radius ?? 1.5 }
128+ fill = { c . fillColor ?? "none" }
129+ stroke = { c . strokeColor ?? "black" }
130+ strokeWidth = { c . strokeWidth ?? 1 }
131+ />
132+ ) ) }
133+ { points . map ( ( p : any , i : number ) => (
134+ < circle
135+ key = { `point-${ i } ` }
136+ cx = { p . x ?? 0 }
137+ cy = { p . y ?? 0 }
138+ r = { p . radius ?? 1.5 }
139+ fill = { p . color ?? "black" }
140+ />
141+ ) ) }
142+ { texts . map ( ( t : any , i : number ) => (
143+ < text
144+ key = { `text-${ i } ` }
145+ x = { t . x ?? 0 }
146+ y = { t . y ?? 0 }
147+ fontSize = { t . fontSize ?? 10 }
148+ fill = { t . color ?? "black" }
149+ >
150+ { t . text ?? "" }
151+ </ text >
152+ ) ) }
153+ </ svg >
154+ )
155+ }
5156
6157export interface GenericSolverDebuggerProps {
7158 solver : BaseSolver
@@ -63,7 +214,13 @@ export const GenericSolverDebugger = ({
63214 { graphicsAreEmpty ? (
64215 < div className = "p-4 text-gray-500" > No Graphics Yet</ div >
65216 ) : (
66- < InteractiveGraphics graphics = { visualization } />
217+ < ErrorBoundary
218+ fallback = {
219+ < SimpleGraphicsSVG graphics = { visualization as GraphicsObject } />
220+ }
221+ >
222+ < InteractiveGraphics graphics = { visualization } />
223+ </ ErrorBoundary >
67224 ) }
68225 </ div >
69226 )
0 commit comments