diff --git a/src/sections/analysis/panelBody/graphTab/index.tsx b/src/sections/analysis/panelBody/graphTab/index.tsx index 89c4475..24e2ecb 100644 --- a/src/sections/analysis/panelBody/graphTab/index.tsx +++ b/src/sections/analysis/panelBody/graphTab/index.tsx @@ -9,13 +9,17 @@ import { XAxis, YAxis, } from "recharts"; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import type { DotProps } from "recharts"; import { currentPositionAtom, gameEvalAtom } from "../../states"; import { useMemo } from "react"; +import type { ReactElement } from "react"; import CustomTooltip from "./tooltip"; import { ChartItemData } from "./types"; import { PositionEval } from "@/types/eval"; import { moveClassificationColors } from "@/lib/chess"; import CustomDot from "./dot"; +import { MoveClassification } from "@/types/enums"; export default function GraphTab(props: GridProps) { const gameEval = useAtomValue(gameEvalAtom); @@ -26,10 +30,44 @@ export default function GraphTab(props: GridProps) { [gameEval] ); + const bestDotIndices = useMemo(() => { + const bestItems = chartData.filter( + (item) => item.moveClassification === MoveClassification.Best + ); + const count = Math.ceil(bestItems.length * 0.15); + const indices = bestItems.map((item) => item.moveNb); + for (let i = indices.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [indices[i], indices[j]] = [indices[j], indices[i]]; + } + return new Set(indices.slice(0, count)); + }, [chartData]); + const boardMoveColor = currentPosition.eval?.moveClassification ? moveClassificationColors[currentPosition.eval.moveClassification] : "grey"; + // Render a dot only on selected classifications (always returns an element) + const renderDot = ( + props: DotProps & { payload?: ChartItemData } + ): ReactElement => { + const payload: ChartItemData | undefined = props.payload; + if (!payload) { + return ; + } + const c = payload.moveClassification; + if ( + c === MoveClassification.Brilliant || + c === MoveClassification.Great || + c === MoveClassification.Blunder || + c === MoveClassification.Mistake || + (c === MoveClassification.Best && bestDotIndices.has(payload.moveNb)) + ) { + return ; + } + return ; + }; + if (!gameEval) return null; return ( @@ -80,6 +118,7 @@ export default function GraphTab(props: GridProps) { stroke="none" fill="#ffffff" fillOpacity={1} + dot={renderDot} activeDot={} isAnimationActive={false} />