Skip to content

Commit 9f03521

Browse files
Merge pull request #169 from CSSLab/sync-moves-position-evaluation
Add vertical line to match Moves Container and Position Evaluation graph
2 parents e7c206f + 8389faa commit 9f03521

File tree

2 files changed

+125
-13
lines changed

2 files changed

+125
-13
lines changed

src/components/Openings/DrillPerformanceModal.tsx

Lines changed: 124 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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
)}

src/components/Openings/OpeningSelectionModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1137,7 +1137,7 @@ export const OpeningSelectionModal: React.FC<Props> = ({
11371137
}
11381138

11391139
return (
1140-
<ModalContainer dismiss={onClose}>
1140+
<ModalContainer className="!z-10" dismiss={onClose}>
11411141
<motion.div
11421142
initial={{ opacity: 0 }}
11431143
animate={{ opacity: 1 }}

0 commit comments

Comments
 (0)