diff --git a/src/App.tsx b/src/App.tsx index 362f284..0ac1768 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -159,8 +159,8 @@ function App() { {/* Graph and Data Display */} {!loading && csvData && (
-
-
+
+
{/* GraphvizParent component generates and displays the graph based on the CSV data */} = ({ - csvData, - filter, - selfLoops, - minVisits, -}) => { + csvData, + filter, + selfLoops, + minVisits, + }) => { const [dotString, setDotString] = useState(null); const [filteredDotString, setFilteredDotString] = useState(null); const [topDotString, setTopDotString] = useState(null); - const { selectedSequence, setSelectedSequence, top5Sequences, setTop5Sequences } = useContext(Context); + const {selectedSequence, setSelectedSequence, top5Sequences, setTop5Sequences} = useContext(Context); // Refs for rendering the Graphviz graphs const graphRefMain = useRef(null); @@ -127,88 +127,124 @@ const GraphvizParent: React.FC = ({ }, [csvData, filter, selfLoops, minVisits, selectedSequence]); // Render Graphviz graphs using d3-graphviz - const renderGraph = (dot: string | null, ref: React.RefObject) => { + const renderGraph = ( + dot: string | null, + ref: React.RefObject, + filename: string, + numberOfGraphs: number + ) => { if (dot && ref.current) { + // Dynamically adjust width based on the number of graphs + const width = numberOfGraphs === 3 ? 325 : 425; // Adjust the width for 3 graphs or 2 graphs + const height = 530; // Fixed height (or adjust dynamically if needed) + graphviz(ref.current) - .width(800) - .height(600) - .renderDot(dot); + .width(width) + .height(height) + .renderDot(dot) + .on('end', () => { + const svgElement = ref.current?.querySelector('svg'); + if (svgElement) { + exportGraphAsPNG(svgElement, filename); + } + }); } }; // Export a graph as high-quality PNG - const exportGraphAsPNG = ( - ref: React.RefObject, - fileName: string, - scale: number = 2, // Scale for higher quality - margin: number = 20 // Smaller margin in pixels - ) => { - if (ref.current) { - const svgElement = ref.current.querySelector('svg'); - if (svgElement) { - const svgData = new XMLSerializer().serializeToString(svgElement); - - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - const img = new Image(); - - img.onload = () => { - const graphWidth = img.width * scale; - const graphHeight = img.height * scale; - - // Set canvas size with smaller margins - canvas.width = graphWidth + margin * 2; - canvas.height = graphHeight + margin * 2; - - // Fill background (optional) - context!.fillStyle = 'white'; - context!.fillRect(0, 0, canvas.width, canvas.height); - - // Draw the graph centered within the canvas - const xOffset = (canvas.width - graphWidth) / 2; - const yOffset = (canvas.height - graphHeight) / 2; - context!.drawImage(img, xOffset, yOffset, graphWidth, graphHeight); - - // Export to PNG - const pngData = canvas.toDataURL('image/png'); - const link = document.createElement('a'); - link.href = pngData; - link.download = `${fileName}.png`; - link.click(); - }; - - img.src = 'data:image/svg+xml;base64,' + btoa(svgData); - } - } + const exportGraphAsPNG = (graphRef: React.RefObject, filename: string) => { + if (!graphRef.current) return; + + const svgElement = graphRef.current.querySelector('svg'); + if (!svgElement) return; + + // Get SVG dimensions + const width = svgElement.viewBox.baseVal.width || 425; + const height = svgElement.viewBox.baseVal.height || 600; + + // Clone the SVG to avoid style inheritance issues + const clonedSvg = svgElement.cloneNode(true); + + // Create a high-resolution canvas + const scaleFactor = 5; // Adjust for higher quality (e.g., 2x or 3x) + const canvas = document.createElement('canvas'); + canvas.width = (width * scaleFactor) * 1.25; + canvas.height = (height * scaleFactor) * 1.5; + const ctx = canvas.getContext('2d'); + + if (!ctx) return; + + // Serialize the SVG + const svgData = new XMLSerializer().serializeToString(clonedSvg); + + // Convert SVG to an image + const img = new Image(); + img.onload = () => { + // Scale the canvas content for higher resolution + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.scale(scaleFactor, scaleFactor); + + ctx.drawImage(img, 0, 0); + + // Export as PNG + const link = document.createElement('a'); + link.download = `${filename}.png`; + link.href = canvas.toDataURL('image/png'); + link.click(); + }; + + img.onerror = (err) => { + console.error('Failed to load SVG for export:', err); + }; + + img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgData)}`; }; - useEffect(() => renderGraph(dotString, graphRefMain), [dotString]); - useEffect(() => renderGraph(filteredDotString, graphRefFiltered), [filteredDotString]); - useEffect(() => renderGraph(topDotString, graphRefTop), [topDotString]); + const numberOfGraphs = [topDotString, dotString, filteredDotString].filter(Boolean).length; + + useEffect(() => { + renderGraph(filteredDotString, graphRefFiltered, 'filtered_graph', numberOfGraphs); + }, [topDotString]); + + useEffect(() => { + renderGraph(topDotString, graphRefTop, 'selected_sequence', numberOfGraphs); + }, [dotString]); + + useEffect(() => { + renderGraph(dotString, graphRefMain, 'all_students', numberOfGraphs); + }, [filteredDotString]); + + + return ( -
+
-
+
{/*Not sure what this does*/} {topDotString && ( -
+

Selected Sequence

-
- exportGraphAsPNG(graphRefTop, 'selected_sequence')} /> +
+ exportGraphAsPNG(graphRefTop, 'selected_sequence')}/>
)} {dotString && ( -
+

All Students, All Paths

-
- exportGraphAsPNG(graphRefMain, 'all_students')} /> -
+
+ exportGraphAsPNG(graphRefMain, 'all_students')}/>
)} {filteredDotString && ( -
+

Filtered Graph

-
- exportGraphAsPNG(graphRefFiltered, 'filtered_graph')} /> +
+ exportGraphAsPNG(graphRefFiltered, 'filtered_graph')}/>
)}
@@ -225,7 +261,7 @@ interface ExportButtonProps { label?: string; } -function ExportButton({ onClick, label = "Export Image" }: ExportButtonProps) { +function ExportButton({onClick, label = "Export Image"}: ExportButtonProps) { return (