11// React component code
2- import React , { useContext , useEffect , useRef , useState } from 'react' ;
3- import { graphviz } from 'd3-graphviz' ;
2+ import React , { useContext , useEffect , useRef , useState } from 'react' ;
3+ import { graphviz } from 'd3-graphviz' ;
44import {
55 generateDotString ,
66 normalizeThicknesses ,
@@ -11,8 +11,8 @@ import {
1111} from './GraphvizProcessing' ;
1212import ErrorBoundary from "@/components/errorBoundary.tsx" ;
1313import '../GraphvizContainer.css' ;
14- import { Context } from "@/Context.tsx" ;
15- import { Button } from './ui/button' ;
14+ import { Context } from "@/Context.tsx" ;
15+ import { Button } from './ui/button' ;
1616
1717interface GraphvizParentProps {
1818 csvData : string ;
@@ -22,15 +22,15 @@ interface GraphvizParentProps {
2222}
2323
2424const GraphvizParent : React . FC < GraphvizParentProps > = ( {
25- csvData,
26- filter,
27- selfLoops,
28- minVisits,
29- } ) => {
25+ csvData,
26+ filter,
27+ selfLoops,
28+ minVisits,
29+ } ) => {
3030 const [ dotString , setDotString ] = useState < string | null > ( null ) ;
3131 const [ filteredDotString , setFilteredDotString ] = useState < string | null > ( null ) ;
3232 const [ topDotString , setTopDotString ] = useState < string | null > ( null ) ;
33- const { selectedSequence, setSelectedSequence, top5Sequences, setTop5Sequences } = useContext ( Context ) ;
33+ const { selectedSequence, setSelectedSequence, top5Sequences, setTop5Sequences} = useContext ( Context ) ;
3434
3535 // Refs for rendering the Graphviz graphs
3636 const graphRefMain = useRef < HTMLDivElement > ( null ) ;
@@ -127,88 +127,124 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
127127 } , [ csvData , filter , selfLoops , minVisits , selectedSequence ] ) ;
128128
129129 // Render Graphviz graphs using d3-graphviz
130- const renderGraph = ( dot : string | null , ref : React . RefObject < HTMLDivElement > ) => {
130+ const renderGraph = (
131+ dot : string | null ,
132+ ref : React . RefObject < HTMLDivElement > ,
133+ filename : string ,
134+ numberOfGraphs : number
135+ ) => {
131136 if ( dot && ref . current ) {
137+ // Dynamically adjust width based on the number of graphs
138+ const width = numberOfGraphs === 3 ? 325 : 425 ; // Adjust the width for 3 graphs or 2 graphs
139+ const height = 530 ; // Fixed height (or adjust dynamically if needed)
140+
132141 graphviz ( ref . current )
133- . width ( 800 )
134- . height ( 600 )
135- . renderDot ( dot ) ;
142+ . width ( width )
143+ . height ( height )
144+ . renderDot ( dot )
145+ . on ( 'end' , ( ) => {
146+ const svgElement = ref . current ?. querySelector ( 'svg' ) ;
147+ if ( svgElement ) {
148+ exportGraphAsPNG ( svgElement , filename ) ;
149+ }
150+ } ) ;
136151 }
137152 } ;
138153
139154 // Export a graph as high-quality PNG
140- const exportGraphAsPNG = (
141- ref : React . RefObject < HTMLDivElement > ,
142- fileName : string ,
143- scale : number = 2 , // Scale for higher quality
144- margin : number = 20 // Smaller margin in pixels
145- ) => {
146- if ( ref . current ) {
147- const svgElement = ref . current . querySelector ( 'svg' ) ;
148- if ( svgElement ) {
149- const svgData = new XMLSerializer ( ) . serializeToString ( svgElement ) ;
150-
151- const canvas = document . createElement ( 'canvas' ) ;
152- const context = canvas . getContext ( '2d' ) ;
153- const img = new Image ( ) ;
154-
155- img . onload = ( ) => {
156- const graphWidth = img . width * scale ;
157- const graphHeight = img . height * scale ;
158-
159- // Set canvas size with smaller margins
160- canvas . width = graphWidth + margin * 2 ;
161- canvas . height = graphHeight + margin * 2 ;
162-
163- // Fill background (optional)
164- context ! . fillStyle = 'white' ;
165- context ! . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
166-
167- // Draw the graph centered within the canvas
168- const xOffset = ( canvas . width - graphWidth ) / 2 ;
169- const yOffset = ( canvas . height - graphHeight ) / 2 ;
170- context ! . drawImage ( img , xOffset , yOffset , graphWidth , graphHeight ) ;
171-
172- // Export to PNG
173- const pngData = canvas . toDataURL ( 'image/png' ) ;
174- const link = document . createElement ( 'a' ) ;
175- link . href = pngData ;
176- link . download = `${ fileName } .png` ;
177- link . click ( ) ;
178- } ;
179-
180- img . src = 'data:image/svg+xml;base64,' + btoa ( svgData ) ;
181- }
182- }
155+ const exportGraphAsPNG = ( graphRef : React . RefObject < HTMLDivElement > , filename : string ) => {
156+ if ( ! graphRef . current ) return ;
157+
158+ const svgElement = graphRef . current . querySelector ( 'svg' ) ;
159+ if ( ! svgElement ) return ;
160+
161+ // Get SVG dimensions
162+ const width = svgElement . viewBox . baseVal . width || 425 ;
163+ const height = svgElement . viewBox . baseVal . height || 600 ;
164+
165+ // Clone the SVG to avoid style inheritance issues
166+ const clonedSvg = svgElement . cloneNode ( true ) ;
167+
168+ // Create a high-resolution canvas
169+ const scaleFactor = 5 ; // Adjust for higher quality (e.g., 2x or 3x)
170+ const canvas = document . createElement ( 'canvas' ) ;
171+ canvas . width = ( width * scaleFactor ) * 1.25 ;
172+ canvas . height = ( height * scaleFactor ) * 1.5 ;
173+ const ctx = canvas . getContext ( '2d' ) ;
174+
175+ if ( ! ctx ) return ;
176+
177+ // Serialize the SVG
178+ const svgData = new XMLSerializer ( ) . serializeToString ( clonedSvg ) ;
179+
180+ // Convert SVG to an image
181+ const img = new Image ( ) ;
182+ img . onload = ( ) => {
183+ // Scale the canvas content for higher resolution
184+ ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
185+ ctx . scale ( scaleFactor , scaleFactor ) ;
186+
187+ ctx . drawImage ( img , 0 , 0 ) ;
188+
189+ // Export as PNG
190+ const link = document . createElement ( 'a' ) ;
191+ link . download = `${ filename } .png` ;
192+ link . href = canvas . toDataURL ( 'image/png' ) ;
193+ link . click ( ) ;
194+ } ;
195+
196+ img . onerror = ( err ) => {
197+ console . error ( 'Failed to load SVG for export:' , err ) ;
198+ } ;
199+
200+ img . src = `data:image/svg+xml;charset=utf-8,${ encodeURIComponent ( svgData ) } ` ;
183201 } ;
184202
185- useEffect ( ( ) => renderGraph ( dotString , graphRefMain ) , [ dotString ] ) ;
186- useEffect ( ( ) => renderGraph ( filteredDotString , graphRefFiltered ) , [ filteredDotString ] ) ;
187- useEffect ( ( ) => renderGraph ( topDotString , graphRefTop ) , [ topDotString ] ) ;
203+ const numberOfGraphs = [ topDotString , dotString , filteredDotString ] . filter ( Boolean ) . length ;
204+
205+ useEffect ( ( ) => {
206+ renderGraph ( filteredDotString , graphRefFiltered , 'filtered_graph' , numberOfGraphs ) ;
207+ } , [ topDotString ] ) ;
208+
209+ useEffect ( ( ) => {
210+ renderGraph ( topDotString , graphRefTop , 'selected_sequence' , numberOfGraphs ) ;
211+ } , [ dotString ] ) ;
212+
213+ useEffect ( ( ) => {
214+ renderGraph ( dotString , graphRefMain , 'all_students' , numberOfGraphs ) ;
215+ } , [ filteredDotString ] ) ;
216+
217+
218+
188219
189220 return (
190- < div className = "graphviz-container flex flex -col gap-8 w-full items-center" >
221+ < div className = "graphviz-container flex-col w-[500px] items-center" >
191222 < ErrorBoundary >
192- < div className = "graphs flex justify-center gap-8 w-full" >
223+ < div className = "graphs flex justify-center w-[500px] h-[650px]" > { /*Not sure what this does*/ }
193224 { topDotString && (
194- < div className = "graph-item flex flex-col items-center" >
225+ < div
226+ className = { `graph-item flex flex-col items-center ${ topDotString && dotString && filteredDotString ? 'w-[400px]' : 'w-[500px]' } border-2 border-gray-700 rounded-lg p-4 bg-gray-100` } >
195227 < h2 className = "text-lg font-semibold text-center mb-2" > Selected Sequence</ h2 >
196- < div ref = { graphRefTop } className = "w-auto h-auto" > </ div >
197- < ExportButton onClick = { ( ) => exportGraphAsPNG ( graphRefTop , 'selected_sequence' ) } />
228+ < div ref = { graphRefTop }
229+ className = "w-full h-[575px] border-2 border-gray-700 rounded-lg p-4 bg-white items-center" > </ div >
230+ < ExportButton onClick = { ( ) => exportGraphAsPNG ( graphRefTop , 'selected_sequence' ) } />
198231 </ div >
199232 ) }
200233 { dotString && (
201- < div className = "graph-item flex flex-col items-center" >
234+ < div
235+ className = { `graph-item flex flex-col items-center ${ topDotString && dotString && filteredDotString ? 'w-[400px]' : 'w-[500px]' } border-2 border-gray-700 rounded-lg p-4 bg-gray-100` } >
202236 < h2 className = "text-lg font-semibold text-center mb-2" > All Students, All Paths</ h2 >
203- < div ref = { graphRefMain } className = "w-auto h-auto" > </ div >
204- < ExportButton onClick = { ( ) => exportGraphAsPNG ( graphRefMain , 'all_students' ) } / >
205- </ div >
237+ < div ref = { graphRefMain }
238+ className = "w-full h-[575px] border-2 border-gray-700 rounded-lg p-4 bg-white" > </ div >
239+ < ExportButton onClick = { ( ) => exportGraphAsPNG ( graphRefMain , 'all_students' ) } /> </ div >
206240 ) }
207241 { filteredDotString && (
208- < div className = "graph-item flex flex-col items-center" >
242+ < div
243+ className = { `graph-item flex flex-col items-center ${ topDotString && dotString && filteredDotString ? 'w-[400px]' : 'w-[500px]' } border-2 border-gray-700 rounded-lg p-4 bg-gray-100` } >
209244 < h2 className = "text-lg font-semibold text-center mb-4" > Filtered Graph</ h2 >
210- < div ref = { graphRefFiltered } className = "w-auto h-auto" > </ div >
211- < ExportButton onClick = { ( ) => exportGraphAsPNG ( graphRefFiltered , 'filtered_graph' ) } />
245+ < div ref = { graphRefFiltered }
246+ className = "w-full h-[575px] border-2 border-gray-700 rounded-lg p-4 bg-white" > </ div >
247+ < ExportButton onClick = { ( ) => exportGraphAsPNG ( graphRefFiltered , 'filtered_graph' ) } />
212248 </ div >
213249 ) }
214250 </ div >
@@ -225,7 +261,7 @@ interface ExportButtonProps {
225261 label ?: string ;
226262}
227263
228- function ExportButton ( { onClick, label = "Export Image" } : ExportButtonProps ) {
264+ function ExportButton ( { onClick, label = "Export Image" } : ExportButtonProps ) {
229265 return (
230266 < Button
231267 variant = { 'secondary' }
0 commit comments