@@ -169,24 +169,26 @@ export const countEdges = (
169169 totalNodeEdges : { [ p : string ] : number } ;
170170 edgeOutcomeCounts : { [ p : string ] : { [ p : string ] : number } } ;
171171 maxEdgeCount : number ;
172- ratioEdges : { [ p : string ] : number } ;
172+ ratioEdges : { [ key : string ] : number } ;
173173 edgeCounts : { [ key : string ] : number } ;
174+ totalVisits : { [ key : string ] : number } ;
175+ repeatVisits : { [ key : string ] : { [ studentId : string ] : number } } ;
174176 topSequences : SequenceCount [ ] ;
175177} => {
176- const totalNodeEdges : { [ key : string ] : number } = { } ;
178+ const totalNodeEdges : { [ key : string ] : Set < string > } = { } ;
177179 const edgeOutcomeCounts : { [ key : string ] : { [ outcome : string ] : number } } = { } ;
178180 let maxEdgeCount = 0 ;
179181 const ratioEdges : { [ key : string ] : number } = { } ;
180182 const edgeCounts : { [ key : string ] : number } = { } ;
181- const studentEdgeCounts : { [ key : string ] : Set < string > } = { } ; // Track unique students per edge
183+ const totalVisits : { [ key : string ] : number } = { } ;
184+ const studentEdgeCounts : { [ key : string ] : Set < string > } = { } ;
185+ const repeatVisits : { [ key : string ] : { [ studentId : string ] : number } } = { } ;
182186 const top5Sequences = getTopSequences ( stepSequences , 5 ) ;
183187
184- // Iterate over first-level keys (student IDs)
185188 Object . keys ( stepSequences ) . forEach ( ( studentId ) => {
186189 const innerStepSequences = stepSequences [ studentId ] ;
187190 const innerOutcomeSequences = outcomeSequences [ studentId ] || { } ;
188191
189- // Iterate over second-level keys (problem names)
190192 Object . keys ( innerStepSequences ) . forEach ( ( problemName ) => {
191193 const steps = innerStepSequences [ problemName ] ;
192194 const outcomes = innerOutcomeSequences [ problemName ] || [ ] ;
@@ -200,20 +202,30 @@ export const countEdges = (
200202
201203 const edgeKey = `${ currentStep } ->${ nextStep } ` ;
202204
203- // Initialize the Set for this edge if it doesn't exist
204205 if ( ! studentEdgeCounts [ edgeKey ] ) {
205206 studentEdgeCounts [ edgeKey ] = new Set ( ) ;
206207 }
208+ if ( ! totalNodeEdges [ currentStep ] ) {
209+ totalNodeEdges [ currentStep ] = new Set ( ) ;
210+ }
211+ if ( ! totalVisits [ edgeKey ] ) {
212+ totalVisits [ edgeKey ] = 0 ;
213+ }
214+ if ( ! repeatVisits [ edgeKey ] ) {
215+ repeatVisits [ edgeKey ] = { } ;
216+ }
207217
208- // Add this student to the Set for this edge
209218 studentEdgeCounts [ edgeKey ] . add ( studentId ) ;
219+ totalNodeEdges [ currentStep ] . add ( studentId ) ;
220+ totalVisits [ edgeKey ] ++ ;
221+
222+ // Track repeat visits for all edges
223+ repeatVisits [ edgeKey ] [ studentId ] = ( repeatVisits [ edgeKey ] [ studentId ] || 0 ) + 1 ;
210224
211- // Update edge counts based on unique students
212225 edgeCounts [ edgeKey ] = studentEdgeCounts [ edgeKey ] . size ;
213226
214227 edgeOutcomeCounts [ edgeKey ] = edgeOutcomeCounts [ edgeKey ] || { } ;
215228 edgeOutcomeCounts [ edgeKey ] [ outcome ] = ( edgeOutcomeCounts [ edgeKey ] [ outcome ] || 0 ) + 1 ;
216- totalNodeEdges [ currentStep ] = ( totalNodeEdges [ currentStep ] || 0 ) + 1 ;
217229
218230 if ( edgeCounts [ edgeKey ] > maxEdgeCount ) {
219231 maxEdgeCount = edgeCounts [ edgeKey ] ;
@@ -222,18 +234,24 @@ export const countEdges = (
222234 } ) ;
223235 } ) ;
224236
225- // Compute ratioEdges based on totalNodeEdges
237+ const totalNodeEdgesCounts : { [ key : string ] : number } = { } ;
238+ Object . keys ( totalNodeEdges ) . forEach ( node => {
239+ totalNodeEdgesCounts [ node ] = totalNodeEdges [ node ] . size ;
240+ } ) ;
241+
226242 Object . keys ( edgeCounts ) . forEach ( ( edge ) => {
227243 const [ start ] = edge . split ( '->' ) ;
228- ratioEdges [ edge ] = edgeCounts [ edge ] / ( totalNodeEdges [ start ] || 1 ) ;
244+ ratioEdges [ edge ] = edgeCounts [ edge ] / ( totalNodeEdgesCounts [ start ] || 1 ) ;
229245 } ) ;
230246
231247 return {
232248 edgeCounts,
233- totalNodeEdges,
249+ totalNodeEdges : totalNodeEdgesCounts ,
234250 ratioEdges,
235251 edgeOutcomeCounts,
236252 maxEdgeCount,
253+ totalVisits,
254+ repeatVisits,
237255 topSequences : top5Sequences
238256 } ;
239257} ;
@@ -352,90 +370,109 @@ function calculateEdgeColors(outcomes: { [outcome: string]: number }): string {
352370 *
353371 * @param justTopSequence
354372 * @returns A string in Graphviz DOT format that represents the graph.
355- */ export function generateDotString (
373+ */
374+ export function generateDotString (
356375 normalizedThicknesses : { [ key : string ] : number } ,
357- // mostCommonSequence: string[],
358376 ratioEdges : { [ key : string ] : number } ,
359377 edgeOutcomeCounts : EdgeCounts [ 'edgeOutcomeCounts' ] ,
360378 edgeCounts : EdgeCounts [ 'edgeCounts' ] ,
361379 totalNodeEdges : EdgeCounts [ 'totalNodeEdges' ] ,
362- threshold : number , //Make a percentage to be more intuitive
380+ threshold : number ,
363381 min_visits : number ,
364382 selectedSequence : SequenceCount [ "sequence" ] ,
365- justTopSequence : boolean
383+ justTopSequence : boolean ,
384+ totalVisits : { [ key : string ] : number } ,
385+ repeatVisits : { [ key : string ] : { [ studentId : string ] : number } }
366386) : string {
367387 if ( ! selectedSequence || selectedSequence . length === 0 ) {
368388 return 'digraph G {\n"Error" [label="No valid sequences found to display."];\n}' ;
369389 }
370390
371391 let dotString = 'digraph G {\ngraph [size="8,6!", dpi=150];\n' ;
372- let totalSteps = selectedSequence . length //stepsInSelectedSequence.length ;
373- let steps = selectedSequence
392+ let totalSteps = selectedSequence . length ;
393+ let steps = selectedSequence ;
374394
375395 if ( justTopSequence ) {
376396 for ( let rank = 0 ; rank < totalSteps ; rank ++ ) {
377397 const currentStep = steps [ rank ] ;
378398 const nextStep = steps [ rank + 1 ] ;
379399 const edgeKey = `${ currentStep } ->${ nextStep } ` ;
380- const thickness = normalizedThicknesses [ edgeKey ] || 1 ; // Default thickness if not present
400+ const thickness = normalizedThicknesses [ edgeKey ] || 1 ;
381401 const outcomes = edgeOutcomeCounts [ edgeKey ] || { } ;
382402 const edgeCount = edgeCounts [ edgeKey ] || 0 ;
403+ const visits = totalVisits [ edgeKey ] || 0 ;
383404 const totalCount = totalNodeEdges [ currentStep ] || 0 ;
384- const color = calculateColor ( rank , totalSteps )
405+ const color = calculateColor ( rank , totalSteps ) ;
385406 const edgeColor = calculateEdgeColors ( outcomes ) ;
386407 const node_tooltip = `Rank:\n\t\t ${ rank + 1 } \nColor:\n\t\t ${ color } ` ;
387408
388409 dotString += ` "${ currentStep } " [rank=${ rank + 1 } , style=filled, fillcolor="${ color } ", tooltip="${ node_tooltip } "];\n` ;
389410
390411 if ( edgeCount > min_visits ) {
391- const tooltip = `${ currentStep } to ${ nextStep } \n`
412+ let tooltip = `${ currentStep } to ${ nextStep } \n`
392413 + `- Unique Students: \n\t\t ${ edgeCount } \n`
414+ + `- Total Edge Visits: \n\t\t ${ visits } \n`
393415 + `- Total Students at ${ currentStep } : \n\t\t${ totalNodeEdges [ currentStep ] || 0 } \n`
394416 + `- Ratio: \n\t\t${ ( ( ratioEdges [ edgeKey ] || 0 ) * 100 ) . toFixed ( 2 ) } % of students at ${ currentStep } go to ${ nextStep } \n`
395417 + `- Outcomes: \n\t\t ${ Object . entries ( outcomes ) . map ( ( [ outcome , count ] ) => `${ outcome } : ${ count } ` ) . join ( '\n\t\t ' ) } \n`
396418 + `- Color Codes: \n\t\t Hex: ${ color } ` ;
397419
420+ // Add repeat visit information for all edges
421+ if ( repeatVisits [ edgeKey ] ) {
422+ const repeatCounts = Object . values ( repeatVisits [ edgeKey ] ) ;
423+ const studentsWithRepeats = repeatCounts . filter ( count => count > 1 ) . length ;
424+ const maxRepeats = Math . max ( ...repeatCounts ) ;
425+ tooltip += `\n- Repeat Visits:\n\t\t ${ studentsWithRepeats } students visited this edge multiple times\n\t\t Maximum visits by a student: ${ maxRepeats } ` ;
426+ }
427+
398428 dotString += ` "${ currentStep } " -> "${ nextStep } " [penwidth=${ thickness } , color="${ edgeColor } ", tooltip="${ tooltip } "];\n` ;
399429 }
400430 }
401431 } else {
402- console . log ( totalSteps , steps )
403432 for ( let rank = 0 ; rank < totalSteps ; rank ++ ) {
404433 const step = steps [ rank ] ;
405434 const color = calculateColor ( rank , totalSteps ) ;
406- console . log ( step , color )
407435 const node_tooltip = `Rank:\n\t\t ${ rank + 1 } \nColor:\n\t\t ${ color } ` ;
408436
409437 dotString += ` "${ step } " [rank=${ rank + 1 } , style=filled, fillcolor="${ color } ", tooltip="${ node_tooltip } "];\n` ;
410438 }
411- // Create edge definitions in the DOT string based on normalized thickness and thresholds
439+
412440 for ( const edge of Object . keys ( normalizedThicknesses ) ) {
413441 if ( normalizedThicknesses [ edge ] >= threshold ) {
414442 const [ currentStep , nextStep ] = edge . split ( '->' ) ;
415443 const thickness = normalizedThicknesses [ edge ] ;
416444 const outcomes = edgeOutcomeCounts [ edge ] || { } ;
417445 const edgeCount = edgeCounts [ edge ] || 0 ;
446+ const visits = totalVisits [ edge ] || 0 ;
418447 const totalCount = totalNodeEdges [ currentStep ] || 0 ;
419448 const color = calculateEdgeColors ( outcomes ) ;
420449 const outcomesStr = Object . entries ( outcomes )
421450 . map ( ( [ outcome , count ] ) => `${ outcome } : ${ count } ` )
422451 . join ( '\n\t\t ' ) ;
423452
424453 if ( edgeCount > min_visits ) {
425- const tooltip = `${ currentStep } to ${ nextStep } \n`
426- + `- Unique Students: \n\t\t ${ edgeCount } \n`
427- + `- Total Students at ${ currentStep } : \n\t\t${ totalNodeEdges [ currentStep ] || 0 } \n`
454+ let tooltip = `- Total Students at ${ currentStep } : \n\t\t${ totalNodeEdges [ currentStep ] || 0 } \n\n`
455+ + `${ currentStep } to ${ nextStep } \n`
456+ + `- Unique Students on edge (each student only counted once): \n\t\t ${ edgeCount } \n`
457+ + `- Edge taken ${ visits } times\n`
428458 + `- Ratio: \n\t\t${ ( ( ratioEdges [ edge ] || 0 ) * 100 ) . toFixed ( 2 ) } % of students at ${ currentStep } go to ${ nextStep } \n`
429459 + `- Outcomes: \n\t\t ${ outcomesStr } \n`
430460 + `- Color Codes: \n\t\t Hex: ${ color } \n\t\t RGB: ${ [ parseInt ( color . substring ( 1 , 3 ) , 16 ) , parseInt ( color . substring ( 3 , 5 ) , 16 ) , parseInt ( color . substring ( 5 , 7 ) , 16 ) ] } ` ;
431461
462+ // Add repeat visit information for all edges
463+ if ( repeatVisits [ edge ] ) {
464+ const repeatCounts = Object . values ( repeatVisits [ edge ] ) ;
465+ const studentsWithRepeats = repeatCounts . filter ( count => count > 1 ) . length ;
466+ const maxRepeats = Math . max ( ...repeatCounts ) ;
467+ tooltip += `\n- Repeat Visits:\n\t\t ${ studentsWithRepeats } students visited this edge multiple times\n\t\t Maximum visits by a student: ${ maxRepeats } ` ;
468+ }
469+
432470 dotString += ` "${ currentStep } " -> "${ nextStep } " [penwidth=${ thickness } , color="${ color } ", tooltip="${ tooltip } "];\n` ;
433471 }
434472 }
435473 }
436474 }
437475
438-
439476 dotString += '}' ;
440477 return dotString ;
441478}
0 commit comments