11"use client"
22
33import { useMemo } from "react"
4- import { ScatterChart , Scatter , XAxis , YAxis , Label , Customized , Cross } from "recharts"
4+ import { ScatterChart , Scatter , XAxis , YAxis , Label , Customized , Cross , LabelList } from "recharts"
55
66import { formatCurrency } from "@/lib"
7- import {
8- ChartContainer ,
9- ChartTooltip ,
10- ChartTooltipContent ,
11- ChartConfig ,
12- ChartLegend ,
13- ChartLegendContent ,
14- } from "@/components/ui"
7+ import { ChartContainer , ChartTooltip , ChartTooltipContent , ChartConfig } from "@/components/ui"
158
169import type { EvalRun } from "./types"
1710
@@ -20,7 +13,7 @@ type PlotProps = {
2013}
2114
2215export const Plot = ( { tableData } : PlotProps ) => {
23- const chartData = useMemo ( ( ) => tableData . filter ( ( { cost } ) => cost < 100 ) , [ tableData ] )
16+ const chartData = useMemo ( ( ) => tableData . filter ( ( { cost } ) => cost < 50 ) , [ tableData ] )
2417
2518 const chartConfig = useMemo (
2619 ( ) => chartData . reduce ( ( acc , run ) => ( { ...acc , [ run . label ] : run } ) , { } as ChartConfig ) ,
@@ -56,10 +49,15 @@ export const Plot = ({ tableData }: PlotProps) => {
5649 </ YAxis >
5750 < ChartTooltip content = { < ChartTooltipContent labelKey = "label" hideIndicator /> } />
5851 < Customized component = { renderQuadrant } />
59- { chartData . map ( ( d , i ) => (
60- < Scatter key = { d . label } name = { d . label } data = { [ d ] } fill = { `hsl(var(--chart-${ i + 1 } ))` } />
52+ { chartData . map ( ( d , index ) => (
53+ < Scatter
54+ key = { d . label }
55+ name = { d . label }
56+ data = { [ d ] }
57+ fill = { generateSpectrumColor ( index , chartData . length ) } >
58+ < LabelList dataKey = "label" position = "top" offset = { 8 } content = { renderCustomLabel } />
59+ </ Scatter >
6160 ) ) }
62- < ChartLegend content = { < ChartLegendContent /> } />
6361 </ ScatterChart >
6462 </ ChartContainer >
6563 < div className = "py-4 text-xs opacity-50" >
@@ -82,3 +80,49 @@ const renderQuadrant = (props: any) => (
8280 opacity = { 0.1 }
8381 />
8482)
83+
84+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
85+ const renderCustomLabel = ( props : any ) => {
86+ const { x, y, value } = props
87+ const maxWidth = 80 // Maximum width in pixels - adjust as needed.
88+
89+ const truncateText = ( text : string , maxChars : number = 12 ) => {
90+ if ( text . length <= maxChars ) {
91+ return text
92+ }
93+
94+ return text . substring ( 0 , maxChars - 1 ) + "…"
95+ }
96+
97+ return (
98+ < text
99+ x = { x }
100+ y = { y - 5 }
101+ fontSize = "10"
102+ fontWeight = "500"
103+ fill = "currentColor"
104+ opacity = "0.8"
105+ textAnchor = "middle"
106+ dominantBaseline = "auto"
107+ style = { {
108+ pointerEvents : "none" ,
109+ maxWidth : `${ maxWidth } px` ,
110+ overflow : "hidden" ,
111+ textOverflow : "ellipsis" ,
112+ whiteSpace : "nowrap" ,
113+ } } >
114+ { truncateText ( value ) }
115+ </ text >
116+ )
117+ }
118+
119+ const generateSpectrumColor = ( index : number , total : number ) : string => {
120+ // Distribute hues evenly across the color wheel (0-360 degrees)
121+ // Start at 0 (red) and distribute evenly.
122+ const hue = ( index * 360 ) / total
123+ // Use high saturation for vibrant colors.
124+ const saturation = 70
125+ // Use medium lightness for good visibility on both light and dark backgrounds.
126+ const lightness = 50
127+ return `hsl(${ Math . round ( hue ) } , ${ saturation } %, ${ lightness } %)`
128+ }
0 commit comments