import { ComponentProps, useMemo, useState } from 'react' import { Bar, CartesianGrid, Cell, Legend, BarChart as RechartBarChart, Tooltip, XAxis, YAxis, } from 'recharts' import type { CategoricalChartState } from 'recharts/types/chart/types' import { ChartHeader } from './ChartHeader' import type { CommonChartProps, Datum } from './Charts.types' import { numberFormatter, useChartSize } from './Charts.utils' import NoDataPlaceholder from './NoDataPlaceholder' import { useChartHoverState } from './useChartHoverState' import { CHART_COLORS, DateTimeFormats } from '@/components/ui/Charts/Charts.constants' import { formatDateTime, useFormatDateTime } from '@/lib/datetime' export interface BarChartProps extends CommonChartProps { yAxisKey: string xAxisKey: string customDateFormat?: string displayDateInUtc?: boolean onBarClick?: (datum: D, tooltipData?: CategoricalChartState) => void emptyStateMessage?: string showLegend?: boolean xAxisIsDate?: boolean XAxisProps?: ComponentProps YAxisProps?: ComponentProps showGrid?: boolean syncId?: string } function BarChart({ data, yAxisKey, xAxisKey, format, customDateFormat = DateTimeFormats.FULL, title, highlightedValue, highlightedLabel, displayDateInUtc, minimalHeader, valuePrecision, className = '', size = 'normal', emptyStateMessage, onBarClick, showLegend = false, xAxisIsDate = true, XAxisProps, YAxisProps, showGrid = false, syncId, }: BarChartProps) { const { hoveredIndex, isHovered, isCurrentChart, setHover, clearHover } = useChartHoverState('default') const { Container } = useChartSize(size) const [focusDataIndex, setFocusDataIndex] = useState(null) // Transform data to ensure yAxisKey values are numbers const transformedData = useMemo(() => { return data.map((item) => ({ ...item, [yAxisKey]: typeof item[yAxisKey] === 'string' ? Number(item[yAxisKey]) : item[yAxisKey], })) }, [data, yAxisKey]) // Default props const _XAxisProps = XAxisProps || { interval: data.length - 2, angle: 0, tick: false, } const _YAxisProps = YAxisProps || { tickFormatter: (value) => numberFormatter(value, valuePrecision), tick: false, width: 0, } // When `displayDateInUtc` is set the chart explicitly wants UTC labels. // Otherwise honour the user's selected timezone via the picker, which // `useFormatDateTime` reads from context. const formatPickerDate = useFormatDateTime() const formatChartDate = (value: number | string) => displayDateInUtc ? formatDateTime(value, { tz: 'UTC', format: customDateFormat }) : formatPickerDate(value, customDateFormat) function getHeaderLabel() { if (!xAxisIsDate) { if (!focusDataIndex) return highlightedLabel return data[focusDataIndex]?.[xAxisKey] } return ( (focusDataIndex !== null && data && data[focusDataIndex] !== undefined && formatChartDate(data[focusDataIndex][xAxisKey] as number | string)) || highlightedLabel ) } const resolvedHighlightedLabel = getHeaderLabel() const resolvedHighlightedValue = focusDataIndex !== null ? data[focusDataIndex]?.[yAxisKey] : highlightedValue if (data.length === 0) { return ( ) } return (
{ if (e.activeTooltipIndex !== focusDataIndex) { setFocusDataIndex(e.activeTooltipIndex) } setHover(e.activeTooltipIndex) }} onMouseLeave={() => { setFocusDataIndex(null) clearHover() }} onClick={(tooltipData) => { const datum = tooltipData?.activePayload?.[0]?.payload if (onBarClick) onBarClick(datum, tooltipData) }} > {showLegend && } {showGrid && } syncId && isHovered && isCurrentChart && hoveredIndex !== null ? (
{formatChartDate(data[hoveredIndex]?.[xAxisKey] as number | string)}
{numberFormatter(Number(data[hoveredIndex]?.[yAxisKey]) || 0, valuePrecision)} {typeof format === 'string' ? format : ''}
) : null } /> {data?.map((_entry: D, index: number) => ( ))}
{data && (
{xAxisIsDate ? formatChartDate(data[0][xAxisKey] as number | string) : data[0][xAxisKey]} {xAxisIsDate ? formatChartDate(data[data?.length - 1]?.[xAxisKey] as number | string) : data[data?.length - 1]?.[xAxisKey]}
)}
) } export default BarChart