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}
/>