@@ -405,6 +405,8 @@ const EvaluationChart: React.FC<{
405405 gameNodesMap,
406406 getChartClassification,
407407} ) => {
408+ const [ isHovering , setIsHovering ] = useState ( false )
409+
408410 if ( evaluationChart . length === 0 ) {
409411 return (
410412 < div className = "flex h-64 items-center justify-center rounded bg-background-2 p-3 text-secondary" >
@@ -491,12 +493,8 @@ const EvaluationChart: React.FC<{
491493 ? getChartClassification ( moveAnalysis , gameNodesMap )
492494 : ''
493495
494- // Get Stockfish depth by finding the corresponding game node
495- // Since all positions are now analyzed to minimum depth 12, we can show this
496- const stockfishDepth = 12 // Minimum depth guaranteed by the analysis system
497-
498496 return {
499- moveNumber : isWhite ? moveNumber : `... ${ moveNumber } ` ,
497+ moveNumber : isWhite ? moveNumber : `${ moveNumber } ... ` ,
500498 evaluation : point . evaluation ,
501499 isPlayerMove : point . isPlayerMove ,
502500 classification : dynamicClassification ,
@@ -505,7 +503,7 @@ const EvaluationChart: React.FC<{
505503 whiteAdvantage : point . evaluation > 0 ? point . evaluation : 0 ,
506504 blackAdvantage : point . evaluation < 0 ? point . evaluation : 0 ,
507505 zero : 0 ,
508- stockfishDepth,
506+ stockfishDepth : 12 ,
509507 }
510508 } )
511509
@@ -533,11 +531,13 @@ const EvaluationChart: React.FC<{
533531 Track how evaluation value changed throughout the drill
534532 </ p >
535533 </ div >
536- < div className = "h-64" >
534+ < div className = "relative h-64" >
537535 < ResponsiveContainer width = "100%" height = "100%" >
538536 < ComposedChart
539537 data = { chartData }
540538 margin = { { top : 15 , right : 20 , left : - 20 , bottom : 6 } }
539+ onMouseEnter = { ( ) => setIsHovering ( true ) }
540+ onMouseLeave = { ( ) => setIsHovering ( false ) }
541541 onMouseMove = { ( data ) => {
542542 if (
543543 data &&
@@ -586,7 +586,10 @@ const EvaluationChart: React.FC<{
586586 } ,
587587 } }
588588 />
589- < Tooltip content = { < CustomTooltip moveAnalyses = { moveAnalyses } /> } />
589+ < Tooltip
590+ content = { < CustomTooltip moveAnalyses = { moveAnalyses } /> }
591+ active = { isHovering }
592+ />
590593
591594 { /* White advantage area (positive evaluations only) */ }
592595 < Area
@@ -617,6 +620,17 @@ const EvaluationChart: React.FC<{
617620 ifOverflow = "extendDomain"
618621 />
619622
623+ { /* Vertical line at current move position */ }
624+ { currentMoveIndex >= 0 && currentMoveIndex < chartData . length && (
625+ < ReferenceLine
626+ x = { chartData [ currentMoveIndex ] ?. moveNumber }
627+ stroke = "rgba(244, 63, 94, 0.8)"
628+ strokeWidth = { 2 }
629+ strokeDasharray = "4 2"
630+ ifOverflow = "extendDomain"
631+ />
632+ ) }
633+
620634 < Line
621635 type = "monotone"
622636 dataKey = "evaluation"
@@ -627,6 +641,66 @@ const EvaluationChart: React.FC<{
627641 />
628642 </ ComposedChart >
629643 </ ResponsiveContainer >
644+
645+ { /* Fixed tooltip for current move position - only show when not hovering */ }
646+ { ! isHovering &&
647+ currentMoveIndex >= 0 &&
648+ currentMoveIndex < chartData . length &&
649+ ( ( ) => {
650+ // Get the filtered moveAnalyses that match the chart data
651+ const firstPlayerMoveIndex = moveAnalyses . findIndex (
652+ ( move ) => move . isPlayerMove ,
653+ )
654+ const startIndex = Math . max ( 0 , firstPlayerMoveIndex - 1 )
655+ const filteredMoveAnalyses = moveAnalyses . slice ( startIndex )
656+
657+ const currentData = chartData [ currentMoveIndex ]
658+ // Convert string move numbers (like "...5") back to numeric for the tooltip
659+ const numericMoveNumber =
660+ typeof currentData . moveNumber === 'string'
661+ ? parseInt ( currentData . moveNumber . replace ( '...' , '' ) )
662+ : currentData . moveNumber
663+
664+ // Calculate the x position of the tooltip based on the current move's position in the chart
665+ // Chart width is approximately 100% - margins, and we need to map the currentMoveIndex to x position
666+ const totalDataPoints = chartData . length
667+ const dataPointRatio =
668+ totalDataPoints > 1 ? currentMoveIndex / ( totalDataPoints - 1 ) : 0
669+
670+ // Position tooltip at the calculated x position
671+ const tooltipLeftPercent = Math . min (
672+ Math . max ( dataPointRatio * 100 , 10 ) ,
673+ 85 ,
674+ ) // Keep within 10%-85% range
675+
676+ return (
677+ < div
678+ className = "absolute top-4 z-10"
679+ style = { {
680+ left : `${ tooltipLeftPercent } %` ,
681+ transform : 'translateX(-50%)' ,
682+ } }
683+ >
684+ < CustomTooltip
685+ active = { true }
686+ payload = { [
687+ {
688+ payload : {
689+ san : currentData . san ,
690+ evaluation : currentData . evaluation ,
691+ classification : currentData . classification ,
692+ isPlayerMove : currentData . isPlayerMove ,
693+ moveNumber : numericMoveNumber ,
694+ stockfishDepth : currentData . stockfishDepth ,
695+ } ,
696+ } ,
697+ ] }
698+ label = { String ( currentData . moveNumber ) }
699+ moveAnalyses = { filteredMoveAnalyses }
700+ />
701+ </ div >
702+ )
703+ } ) ( ) }
630704 </ div >
631705
632706 { /* Legend */ }
@@ -661,6 +735,7 @@ const DesktopLayout: React.FC<{
661735 playerMoveCount : number
662736 treeController : ReturnType < typeof useTreeController >
663737 gameNodesMap : Map < string , GameNode >
738+ currentMoveIndex : number
664739 getChartClassification : (
665740 analysis : MoveAnalysis ,
666741 gameNodesMap : Map < string , GameNode > ,
@@ -677,6 +752,7 @@ const DesktopLayout: React.FC<{
677752 playerMoveCount,
678753 treeController,
679754 gameNodesMap,
755+ currentMoveIndex,
680756 getChartClassification,
681757} ) => (
682758 < div className = "relative flex h-[90vh] max-h-[800px] w-[95vw] max-w-[1200px] flex-col overflow-hidden rounded-lg bg-background-1 shadow-2xl" >
@@ -725,10 +801,17 @@ const DesktopLayout: React.FC<{
725801 < EvaluationChart
726802 evaluationChart = { filteredEvaluationChart }
727803 moveAnalyses = { performanceData . moveAnalyses }
728- currentMoveIndex = { 0 }
804+ currentMoveIndex = { currentMoveIndex }
729805 onHoverMove = { ( moveIndex ) => {
730806 // Navigate to the corresponding move position when hovering over the chart
731- const moveAnalysis = performanceData . moveAnalyses [ moveIndex ]
807+ // Adjust moveIndex back to the original moveAnalyses index
808+ const firstPlayerMoveIndex = performanceData . moveAnalyses . findIndex (
809+ ( move ) => move . isPlayerMove ,
810+ )
811+ const startIndex = Math . max ( 0 , firstPlayerMoveIndex - 1 )
812+ const actualMoveIndex = moveIndex + startIndex
813+
814+ const moveAnalysis = performanceData . moveAnalyses [ actualMoveIndex ]
732815 if ( moveAnalysis ) {
733816 // Find the node that represents the position after this move
734817 const targetFen = moveAnalysis . fen
@@ -960,6 +1043,7 @@ const MobileLayout: React.FC<{
9601043 playerMoveCount : number
9611044 treeController : ReturnType < typeof useTreeController >
9621045 gameNodesMap : Map < string , GameNode >
1046+ currentMoveIndex : number
9631047 getChartClassification : (
9641048 analysis : MoveAnalysis ,
9651049 gameNodesMap : Map < string , GameNode > ,
@@ -978,6 +1062,7 @@ const MobileLayout: React.FC<{
9781062 playerMoveCount,
9791063 treeController,
9801064 gameNodesMap,
1065+ currentMoveIndex,
9811066 getChartClassification,
9821067} ) => (
9831068 < div className = "relative flex h-[95vh] w-[95vw] flex-col overflow-hidden rounded-lg bg-background-1 shadow-2xl" >
@@ -1056,10 +1141,18 @@ const MobileLayout: React.FC<{
10561141 < EvaluationChart
10571142 evaluationChart = { filteredEvaluationChart }
10581143 moveAnalyses = { performanceData . moveAnalyses }
1059- currentMoveIndex = { 0 }
1144+ currentMoveIndex = { currentMoveIndex }
10601145 onHoverMove = { ( moveIndex ) => {
10611146 // Navigate to the corresponding move position when hovering over the chart
1062- const moveAnalysis = performanceData . moveAnalyses [ moveIndex ]
1147+ // Adjust moveIndex back to the original moveAnalyses index
1148+ const firstPlayerMoveIndex =
1149+ performanceData . moveAnalyses . findIndex (
1150+ ( move ) => move . isPlayerMove ,
1151+ )
1152+ const startIndex = Math . max ( 0 , firstPlayerMoveIndex - 1 )
1153+ const actualMoveIndex = moveIndex + startIndex
1154+
1155+ const moveAnalysis = performanceData . moveAnalyses [ actualMoveIndex ]
10631156 if ( moveAnalysis ) {
10641157 // Find the node that represents the position after this move
10651158 const targetFen = moveAnalysis . fen
@@ -1205,6 +1298,23 @@ export const DrillPerformanceModal: React.FC<Props> = ({
12051298 drill . selection . playerColor ,
12061299 )
12071300
1301+ // Calculate current move index from tree controller's current node
1302+ const currentMoveIndex = useMemo ( ( ) => {
1303+ if ( ! treeController . currentNode ) return - 1
1304+
1305+ // Find the index in moveAnalyses that corresponds to the current node's FEN
1306+ const currentFen = treeController . currentNode . fen
1307+ const moveIndex = moveAnalyses . findIndex ( ( move ) => move . fen === currentFen )
1308+
1309+ // Adjust for the filtered evaluation chart that starts earlier
1310+ const firstPlayerMoveIndex = moveAnalyses . findIndex (
1311+ ( move ) => move . isPlayerMove ,
1312+ )
1313+ const startIndex = Math . max ( 0 , firstPlayerMoveIndex - 1 )
1314+
1315+ return moveIndex >= startIndex ? moveIndex - startIndex : - 1
1316+ } , [ treeController . currentNode , moveAnalyses ] )
1317+
12081318 // Create a map of FEN -> GameNode for consistent classification
12091319 const gameNodesMap = useMemo ( ( ) => {
12101320 const map = new Map < string , GameNode > ( )
@@ -1283,6 +1393,7 @@ export const DrillPerformanceModal: React.FC<Props> = ({
12831393 playerMoveCount = { playerMoveCount }
12841394 treeController = { treeController }
12851395 gameNodesMap = { gameNodesMap }
1396+ currentMoveIndex = { currentMoveIndex }
12861397 getChartClassification = { getChartClassification }
12871398 />
12881399 ) : (
@@ -1298,6 +1409,7 @@ export const DrillPerformanceModal: React.FC<Props> = ({
12981409 playerMoveCount = { playerMoveCount }
12991410 treeController = { treeController }
13001411 gameNodesMap = { gameNodesMap }
1412+ currentMoveIndex = { currentMoveIndex }
13011413 getChartClassification = { getChartClassification }
13021414 />
13031415 ) }
0 commit comments