Skip to content

Commit ac91e4e

Browse files
Fix moves-by-rating chart transitions
1 parent b1612b1 commit ac91e4e

2 files changed

Lines changed: 50 additions & 29 deletions

File tree

src/components/Analysis/AnalysisSidebar.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ export const AnalysisSidebar: React.FC<Props> = ({
141141
const movesByRatingProps = {
142142
moves: analysisEnabled ? controller.movesByRating : undefined,
143143
colorSanMapping: analysisEnabled ? controller.colorSanMapping : {},
144+
positionKey: analysisEnabled ? controller.currentNode?.fen : undefined,
144145
}
145146

146147
const renderHeader = (

src/components/Analysis/MovesByRating.tsx

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,53 @@ import {
88
CartesianGrid,
99
ResponsiveContainer,
1010
} from 'recharts'
11-
import { useContext } from 'react'
11+
import { useContext, useEffect, useMemo, useRef, useState } from 'react'
1212
import { ColorSanMapping } from 'src/types'
1313
import { WindowSizeContext } from 'src/contexts'
1414

1515
interface Props {
1616
moves: { [key: string]: number }[] | undefined
1717
colorSanMapping: ColorSanMapping
1818
isHomePage?: boolean
19+
positionKey?: string
1920
}
2021

2122
export const MovesByRating: React.FC<Props> = ({
2223
moves,
2324
colorSanMapping,
2425
isHomePage = false,
26+
positionKey,
2527
}: Props) => {
2628
const { isMobile } = useContext(WindowSizeContext)
29+
const [displayedMoves, setDisplayedMoves] = useState(moves)
30+
const [displayedColorSanMapping, setDisplayedColorSanMapping] =
31+
useState(colorSanMapping)
32+
const [displayedPositionKey, setDisplayedPositionKey] = useState(positionKey)
33+
const lastRenderedPositionKeyRef = useRef<string | undefined>(positionKey)
34+
const shouldAnimateSeries =
35+
!!displayedPositionKey &&
36+
displayedPositionKey !== lastRenderedPositionKeyRef.current
2737

28-
const maxValue = moves
38+
useEffect(() => {
39+
if (!moves?.length || !positionKey) return
40+
setDisplayedMoves(moves)
41+
setDisplayedColorSanMapping(colorSanMapping)
42+
setDisplayedPositionKey(positionKey)
43+
}, [moves, colorSanMapping, positionKey])
44+
45+
useEffect(() => {
46+
if (!displayedPositionKey) return
47+
lastRenderedPositionKeyRef.current = displayedPositionKey
48+
}, [displayedPositionKey])
49+
50+
const moveKeys = useMemo(() => {
51+
if (!displayedMoves?.length) return []
52+
return Object.keys(displayedMoves[0]).filter((move) => move !== 'rating')
53+
}, [displayedMoves])
54+
55+
const maxValue = displayedMoves
2956
? Math.max(
30-
...moves.flatMap((move) =>
57+
...displayedMoves.flatMap((move) =>
3158
Object.entries(move)
3259
.filter(([key]) => key !== 'rating')
3360
.map(([, value]) => value as number),
@@ -48,7 +75,7 @@ export const MovesByRating: React.FC<Props> = ({
4875
</h2>
4976
<ResponsiveContainer width="100%" height="90%">
5077
<AreaChart
51-
data={moves}
78+
data={displayedMoves}
5279
margin={{
5380
left: 0,
5481
right: isMobile ? 40 : 50,
@@ -99,11 +126,8 @@ export const MovesByRating: React.FC<Props> = ({
99126
tickFormatter={(value) => `${value}%`}
100127
/>
101128
<defs>
102-
{moves &&
103-
Object.keys(moves[0]).map((move, i) => {
104-
if (move === 'rating') {
105-
return null
106-
}
129+
{displayedMoves &&
130+
moveKeys.map((move) => {
107131
return (
108132
<linearGradient
109133
key={`color${move}`}
@@ -115,22 +139,22 @@ export const MovesByRating: React.FC<Props> = ({
115139
>
116140
<stop
117141
offset="5%"
118-
stopColor={colorSanMapping[move]?.color ?? '#fff'}
142+
stopColor={displayedColorSanMapping[move]?.color ?? '#fff'}
119143
stopOpacity={0.5}
120144
/>
121145
<stop
122146
offset="95%"
123-
stopColor={colorSanMapping[move]?.color ?? '#fff'}
147+
stopColor={displayedColorSanMapping[move]?.color ?? '#fff'}
124148
stopOpacity={0}
125149
/>
126150
</linearGradient>
127151
)
128152
})}
129153
</defs>
130-
{moves &&
154+
{displayedMoves &&
131155
// First, collect all the end points and sort them by y-position
132156
(() => {
133-
const lastIndex = moves.length - 1
157+
const lastIndex = displayedMoves.length - 1
134158

135159
// Define the type for end points
136160
interface EndPoint {
@@ -142,42 +166,38 @@ export const MovesByRating: React.FC<Props> = ({
142166
adjustment?: number
143167
}
144168

145-
const endPoints = Object.keys(moves[0])
146-
.filter((move) => move !== 'rating')
169+
const endPoints = moveKeys
147170
.map((move) => {
148-
const value = moves[lastIndex][move] as number
171+
const value = displayedMoves[lastIndex][move] as number
149172
return {
150173
move,
151174
value,
152-
san: colorSanMapping[move]?.san || move,
153-
color: colorSanMapping[move]?.color ?? '#fff',
175+
san: displayedColorSanMapping[move]?.san || move,
176+
color: displayedColorSanMapping[move]?.color ?? '#fff',
154177
} as EndPoint
155178
})
156179
.sort((a, b) => a.value - b.value) // Sort by value (y-position)
157180

158181
// Return the original map function with adjusted positions
159-
return Object.keys(moves[0]).map((move, i) => {
160-
if (move === 'rating') {
161-
return null
162-
}
163-
182+
return moveKeys.map((move, index) => {
164183
const endPoint = endPoints.find((ep) => ep.move === move)
165184
const san = endPoint?.san || move
166185

167186
return (
168187
<Area
169-
key={i}
188+
key={index}
170189
yAxisId="left"
171190
dataKey={move}
172191
dot={{
173192
r: isMobile ? 2 : 3,
174-
stroke: colorSanMapping[move]?.color ?? '#fff',
193+
stroke: displayedColorSanMapping[move]?.color ?? '#fff',
175194
strokeWidth: isMobile ? 2 : 3,
176195
}}
177-
stroke={colorSanMapping[move]?.color ?? '#fff'}
196+
stroke={displayedColorSanMapping[move]?.color ?? '#fff'}
178197
fill={`url(#color${move})`}
179198
strokeWidth={isMobile ? 2 : 3}
180199
animationDuration={300}
200+
isAnimationActive={shouldAnimateSeries}
181201
name={san}
182202
label={(props: {
183203
x: number
@@ -231,7 +251,7 @@ export const MovesByRating: React.FC<Props> = ({
231251
dy={isMobile ? 3 : 4}
232252
fontSize={11}
233253
fontWeight={600}
234-
fill={colorSanMapping[move]?.color ?? '#fff'}
254+
fill={displayedColorSanMapping[move]?.color ?? '#fff'}
235255
textAnchor="start"
236256
>
237257
{san}
@@ -298,8 +318,8 @@ export const MovesByRating: React.FC<Props> = ({
298318
fontSize: 14,
299319
}}
300320
iconSize={0}
301-
formatter={(value) => {
302-
return colorSanMapping[value as string]?.san ?? value
321+
formatter={(value: string) => {
322+
return displayedColorSanMapping[value as string]?.san ?? value
303323
}}
304324
/>
305325
</AreaChart>

0 commit comments

Comments
 (0)