1- import type { OutputColumnMetadata } from "@internal/clickhouse" ;
1+ import type { ColumnFormatType , OutputColumnMetadata } from "@internal/clickhouse" ;
2+ import { formatDurationMilliseconds } from "@trigger.dev/core/v3" ;
23import { memo , useMemo } from "react" ;
4+ import { createValueFormatter } from "~/utils/columnFormat" ;
5+ import { formatCurrencyAccurate } from "~/utils/numberFormatter" ;
36import type { ChartConfig } from "~/components/primitives/charts/Chart" ;
47import { Chart } from "~/components/primitives/charts/ChartCompound" ;
58import { Paragraph } from "../primitives/Paragraph" ;
@@ -797,8 +800,24 @@ export const QueryResultsChart = memo(function QueryResultsChart({
797800 } ;
798801 } , [ isDateBased , timeGranularity ] ) ;
799802
800- // Create dynamic Y-axis formatter based on data range
801- const yAxisFormatter = useMemo ( ( ) => createYAxisFormatter ( data , series ) , [ data , series ] ) ;
803+ // Resolve the Y-axis column format for formatting
804+ const yAxisFormat = useMemo ( ( ) => {
805+ if ( yAxisColumns . length === 0 ) return undefined ;
806+ const col = columns . find ( ( c ) => c . name === yAxisColumns [ 0 ] ) ;
807+ return ( col ?. format ?? col ?. customRenderType ) as ColumnFormatType | undefined ;
808+ } , [ yAxisColumns , columns ] ) ;
809+
810+ // Create dynamic Y-axis formatter based on data range and format
811+ const yAxisFormatter = useMemo (
812+ ( ) => createYAxisFormatter ( data , series , yAxisFormat ) ,
813+ [ data , series , yAxisFormat ]
814+ ) ;
815+
816+ // Create value formatter for tooltips and legend based on column format
817+ const tooltipValueFormatter = useMemo (
818+ ( ) => createValueFormatter ( yAxisFormat ) ,
819+ [ yAxisFormat ]
820+ ) ;
802821
803822 // Check if the group-by column has a runStatus customRenderType
804823 const groupByIsRunStatus = useMemo ( ( ) => {
@@ -1016,6 +1035,7 @@ export const QueryResultsChart = memo(function QueryResultsChart({
10161035 showLegend = { showLegend }
10171036 maxLegendItems = { fullLegend ? Infinity : 5 }
10181037 legendAggregation = { config . aggregation }
1038+ legendValueFormatter = { tooltipValueFormatter }
10191039 minHeight = "300px"
10201040 fillContainer
10211041 onViewAllLegendItems = { onViewAllLegendItems }
@@ -1027,6 +1047,7 @@ export const QueryResultsChart = memo(function QueryResultsChart({
10271047 yAxisProps = { yAxisProps }
10281048 stackId = { stacked ? "stack" : undefined }
10291049 tooltipLabelFormatter = { tooltipLabelFormatter }
1050+ tooltipValueFormatter = { tooltipValueFormatter }
10301051 />
10311052 </ Chart . Root >
10321053 ) ;
@@ -1043,6 +1064,7 @@ export const QueryResultsChart = memo(function QueryResultsChart({
10431064 showLegend = { showLegend }
10441065 maxLegendItems = { fullLegend ? Infinity : 5 }
10451066 legendAggregation = { config . aggregation }
1067+ legendValueFormatter = { tooltipValueFormatter }
10461068 minHeight = "300px"
10471069 fillContainer
10481070 onViewAllLegendItems = { onViewAllLegendItems }
@@ -1054,16 +1076,21 @@ export const QueryResultsChart = memo(function QueryResultsChart({
10541076 yAxisProps = { yAxisProps }
10551077 stacked = { stacked && sortedSeries . length > 1 }
10561078 tooltipLabelFormatter = { tooltipLabelFormatter }
1079+ tooltipValueFormatter = { tooltipValueFormatter }
10571080 lineType = "linear"
10581081 />
10591082 </ Chart . Root >
10601083 ) ;
10611084} ) ;
10621085
10631086/**
1064- * Creates a Y-axis value formatter based on the data range
1087+ * Creates a Y-axis value formatter based on the data range and optional format hint
10651088 */
1066- function createYAxisFormatter ( data : Record < string , unknown > [ ] , series : string [ ] ) {
1089+ function createYAxisFormatter (
1090+ data : Record < string , unknown > [ ] ,
1091+ series : string [ ] ,
1092+ format ?: ColumnFormatType
1093+ ) {
10671094 // Find min and max values across all series
10681095 let minVal = Infinity ;
10691096 let maxVal = - Infinity ;
@@ -1080,6 +1107,46 @@ function createYAxisFormatter(data: Record<string, unknown>[], series: string[])
10801107
10811108 const range = maxVal - minVal ;
10821109
1110+ // Format-aware formatters
1111+ if ( format === "bytes" || format === "decimalBytes" ) {
1112+ const divisor = format === "bytes" ? 1024 : 1000 ;
1113+ const units =
1114+ format === "bytes"
1115+ ? [ "B" , "KiB" , "MiB" , "GiB" , "TiB" ]
1116+ : [ "B" , "KB" , "MB" , "GB" , "TB" ] ;
1117+ return ( value : number ) : string => {
1118+ if ( value === 0 ) return "0 B" ;
1119+ // Use consistent unit for all ticks based on max value
1120+ const i = Math . min (
1121+ Math . floor ( Math . log ( Math . abs ( maxVal || 1 ) ) / Math . log ( divisor ) ) ,
1122+ units . length - 1
1123+ ) ;
1124+ const scaled = value / Math . pow ( divisor , i ) ;
1125+ return `${ scaled . toFixed ( scaled < 10 ? 1 : 0 ) } ${ units [ i ] } ` ;
1126+ } ;
1127+ }
1128+
1129+ if ( format === "percent" ) {
1130+ return ( value : number ) : string => `${ value . toFixed ( range < 1 ? 2 : 1 ) } %` ;
1131+ }
1132+
1133+ if ( format === "duration" ) {
1134+ return ( value : number ) : string => formatDurationMilliseconds ( value , { style : "short" } ) ;
1135+ }
1136+
1137+ if ( format === "durationSeconds" ) {
1138+ return ( value : number ) : string =>
1139+ formatDurationMilliseconds ( value * 1000 , { style : "short" } ) ;
1140+ }
1141+
1142+ if ( format === "costInDollars" || format === "cost" ) {
1143+ return ( value : number ) : string => {
1144+ const dollars = format === "cost" ? value / 100 : value ;
1145+ return formatCurrencyAccurate ( dollars ) ;
1146+ } ;
1147+ }
1148+
1149+ // Default formatter
10831150 return ( value : number ) : string => {
10841151 // Use abbreviations for large numbers
10851152 if ( Math . abs ( value ) >= 1_000_000 ) {
0 commit comments