@@ -73,6 +73,9 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
7373 // State for selected sequence graph filtering
7474 const [ showOnlySequenceStudents , setShowOnlySequenceStudents ] = useState < boolean > ( true ) ;
7575
76+ // State for node coloring
77+ const [ colorNodesBySequence , setColorNodesBySequence ] = useState < boolean > ( true ) ;
78+
7679 // History state management
7780 const [ activeTab , setActiveTab ] = useState < 'graphs' | 'history' > ( 'graphs' ) ;
7881 const [ historyItems , setHistoryItems ] = useState < HistoryItem [ ] > ( [ ] ) ;
@@ -188,6 +191,7 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
188191 errorMode , // Use actual errorMode setting
189192 mainGraphData . firstAttemptOutcomes ,
190193 uniqueStudentMode ,
194+ colorNodesBySequence ,
191195 ) ;
192196
193197 setDotString ( dotString ) ;
@@ -223,10 +227,11 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
223227 false , // Force errorMode to false for static top graph
224228 sequenceResults . firstAttemptOutcomes ,
225229 uniqueStudentMode ,
230+ colorNodesBySequence ,
226231 )
227232 ) ;
228233 }
229- } , [ mainGraphData , selectedSequence , setTop5Sequences , top5Sequences , onMaxEdgeCountChange , onMaxMinEdgeCountChange , uniqueStudentMode , minVisits , errorMode , minVisitsPerGraph , showOnlySequenceStudents ] ) ; // Responds to uniqueStudentMode, minVisits, errorMode, minVisitsPerGraph, showOnlySequenceStudents and selectedSequence
234+ } , [ mainGraphData , selectedSequence , setTop5Sequences , top5Sequences , onMaxEdgeCountChange , onMaxMinEdgeCountChange , uniqueStudentMode , minVisits , errorMode , minVisitsPerGraph , showOnlySequenceStudents , colorNodesBySequence ] ) ; // Responds to uniqueStudentMode, minVisits, errorMode, minVisitsPerGraph, showOnlySequenceStudents, colorNodesBySequence and selectedSequence
230235
231236 // Memoized filtered graph data for each filter
232237 const filteredGraphDataMap = useMemo ( ( ) => {
@@ -344,6 +349,7 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
344349 errorMode ,
345350 filteredGraphData . firstAttemptOutcomes ,
346351 uniqueStudentMode ,
352+ colorNodesBySequence ,
347353 ) ;
348354
349355 newFilteredDotStrings [ filter ] = filteredDotString ;
@@ -375,19 +381,23 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
375381 } , [ ] ) ;
376382
377383 // Export a graph as high-quality PNG with problem name
378- const exportGraphAsPNG = ( graphRef : React . RefObject < HTMLDivElement > , graphName : string , minStudents ?: number ) => {
384+ const exportGraphAsPNG = ( graphRef : React . RefObject < HTMLDivElement > , graphName : string , minStudents ?: number , isColored ?: boolean ) => {
379385 if ( ! graphRef . current ) return ;
380386
381387 const svgElement = graphRef . current . querySelector ( 'svg' ) as SVGSVGElement ;
382388 if ( ! svgElement ) return ;
383389
384- // Build filename: problemName_graphName_minXX
390+ // Build filename: problemName_graphName_minXX_colored
385391 const sanitizedProblemName = problemName . replace ( / [ ^ a - z A - Z 0 - 9 ] / g, '_' ) ;
386392 const sanitizedGraphName = graphName . replace ( / [ ^ a - z A - Z 0 - 9 ] / g, '_' ) ;
387393 let filename = `${ sanitizedProblemName } _${ sanitizedGraphName } ` ;
388394 if ( minStudents !== undefined ) {
389395 filename += `_min${ minStudents } ` ;
390396 }
397+ if ( isColored ) {
398+ filename += '_colored' ;
399+ }
400+ console . log ( 'Export PNG - isColored:' , isColored , 'filename:' , filename ) ;
391401
392402 // Get the actual content bounding box to capture the entire visible graph
393403 const gElement = svgElement . querySelector ( 'g' ) as SVGGElement ;
@@ -484,10 +494,10 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
484494 // Helper function to calculate progress status statistics for students at a node
485495 const calculateNodeProgressStats = ( nodeName : string ) : { graduated : number ; promoted : number ; other : number ; total : number } => {
486496 if ( ! mainGraphData ) return { graduated : 0 , promoted : 0 , other : 0 , total : 0 } ;
487-
497+
488498 const { stepSequences, sortedData } = mainGraphData ;
489499 const studentsAtNode = new Set < string > ( ) ;
490-
500+
491501 // Find all students who visited this node
492502 // stepSequences has structure: { [studentId]: { [problemName]: string[] } }
493503 if ( stepSequences && Object . keys ( stepSequences ) . length > 0 ) {
@@ -502,26 +512,30 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
502512 }
503513 } ) ;
504514 }
505-
515+
506516 // Count progress status for students who visited this node
517+ // IMPORTANT: Filter by current problemName to get accurate progress status
507518 let graduatedCount = 0 ;
508519 let promotedCount = 0 ;
509520 let otherCount = 0 ;
510-
521+
511522 if ( sortedData && sortedData . length > 0 ) {
512- // Create a map of all students and their progress status
523+ // Create a map of students and their progress status FOR THIS PROBLEM ONLY
513524 const studentProgressMap = new Map < string , string > ( ) ;
514525 sortedData . forEach ( ( row : any ) => {
515526 const studentId = row [ 'Anon Student Id' ] ;
527+ const rowProblemName = row [ 'Problem Name' ] ;
516528 const progressStatus = row [ 'CF (Workspace Progress Status)' ] ;
517- if ( studentId && progressStatus ) {
529+
530+ // Only use progress status from rows for the current problem
531+ if ( studentId && progressStatus && rowProblemName === problemName ) {
518532 studentProgressMap . set ( studentId , progressStatus ) ;
519533 }
520534 } ) ;
521-
535+
522536 studentsAtNode . forEach ( studentId => {
523537 const progressStatus = studentProgressMap . get ( studentId ) ;
524-
538+
525539 if ( progressStatus === 'GRADUATED' ) {
526540 graduatedCount ++ ;
527541 } else if ( progressStatus === 'PROMOTED' ) {
@@ -531,7 +545,7 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
531545 }
532546 } ) ;
533547 }
534-
548+
535549 return {
536550 graduated : graduatedCount ,
537551 promoted : promotedCount ,
@@ -728,21 +742,25 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
728742 }
729743
730744 // Count exact progress status for students who took this transition
745+ // IMPORTANT: Filter by current problemName to get accurate progress status
731746 let graduatedCount = 0 ;
732747 let promotedCount = 0 ;
733748 let otherCount = 0 ;
734-
749+
735750 if ( sortedData && sortedData . length > 0 && studentsOnEdge . size > 0 ) {
736- // Create a map of student progress status
751+ // Create a map of student progress status FOR THIS PROBLEM ONLY
737752 const studentProgressMap = new Map < string , string > ( ) ;
738753 sortedData . forEach ( ( row : any ) => {
739754 const studentId = row [ 'Anon Student Id' ] ;
755+ const rowProblemName = row [ 'Problem Name' ] ;
740756 const progressStatus = row [ 'CF (Workspace Progress Status)' ] ;
741- if ( studentId && progressStatus ) {
757+
758+ // Only use progress status from rows for the current problem
759+ if ( studentId && progressStatus && rowProblemName === problemName ) {
742760 studentProgressMap . set ( studentId , progressStatus ) ;
743761 }
744762 } ) ;
745-
763+
746764 studentsOnEdge . forEach ( studentId => {
747765 const progressStatus = studentProgressMap . get ( studentId ) ;
748766 if ( progressStatus === 'GRADUATED' ) {
@@ -1449,11 +1467,14 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
14491467 showSequenceFilter = { true }
14501468 showOnlySequenceStudents = { showOnlySequenceStudents }
14511469 onSequenceFilterChange = { ( value : boolean ) => setShowOnlySequenceStudents ( value ) }
1470+ showNodeColoringOption = { true }
1471+ colorNodesBySequence = { colorNodesBySequence }
1472+ onNodeColoringChange = { ( value : boolean ) => setColorNodesBySequence ( value ) }
14521473 />
14531474 < div ref = { graphRefTop } className = "w-full h-full" > </ div >
14541475 </ div >
14551476 < div className = "w-full flex justify-center mt-2" >
1456- < ExportButton onClick = { ( ) => exportGraphAsPNG ( graphRefTop , 'selected_sequence' , minVisitsPerGraph [ 'selected_sequence' ] ) } />
1477+ < ExportButton onClick = { ( ) => exportGraphAsPNG ( graphRefTop , 'selected_sequence' , minVisitsPerGraph [ 'selected_sequence' ] , colorNodesBySequence ) } />
14571478 </ div >
14581479 </ div >
14591480 ) }
@@ -1471,7 +1492,7 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
14711492 < div ref = { graphRefMain } className = "w-full h-full" > </ div >
14721493 </ div >
14731494 < div className = "w-full flex justify-center mt-2" >
1474- < ExportButton onClick = { ( ) => exportGraphAsPNG ( graphRefMain , 'all_students' , minVisitsPerGraph [ 'all_students' ] ?? Math . round ( ( mainGraphData ?. maxEdgeCount || 100 ) * 0.1 ) ) } />
1495+ < ExportButton onClick = { ( ) => exportGraphAsPNG ( graphRefMain , 'all_students' , minVisitsPerGraph [ 'all_students' ] ?? Math . round ( ( mainGraphData ?. maxEdgeCount || 100 ) * 0.1 ) , colorNodesBySequence ) } />
14751496 </ div >
14761497 </ div >
14771498 ) }
@@ -1497,7 +1518,7 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
14971518 < div ref = { ref } className = "w-full h-full" > </ div >
14981519 </ div >
14991520 < div className = "w-full flex justify-center mt-2" >
1500- < ExportButton onClick = { ( ) => exportGraphAsPNG ( ref , `filtered_graph_${ filter } ` , minVisitsPerGraph [ graphKey ] ?? Math . round ( ( filteredGraphData ?. maxEdgeCount || 100 ) * 0.1 ) ) } />
1521+ < ExportButton onClick = { ( ) => exportGraphAsPNG ( ref , `filtered_graph_${ filter } ` , minVisitsPerGraph [ graphKey ] ?? Math . round ( ( filteredGraphData ?. maxEdgeCount || 100 ) * 0.1 ) , colorNodesBySequence ) } />
15011522 </ div >
15021523 </ div >
15031524 ) ;
0 commit comments