'use client' import { useState, ComponentProps, useEffect } from 'react' import dayjs from 'dayjs' import { Area, Bar, ComposedChart as RechartComposedChart, XAxis, YAxis, CartesianGrid, Line, ReferenceArea, Tooltip, } from 'recharts' import { CategoricalChartState } from 'recharts/types/chart/types' import { cn } from 'ui' import { useTheme } from 'next-themes' import ChartHeader from './ChartHeader' import ChartHighlightActions from './ChartHighlightActions' import { CHART_COLORS, DateTimeFormats, STACKED_CHART_COLORS, updateStackedChartColors, } from './Charts.constants' import { Datum, CommonChartProps } from './Charts.types' import { useChartSize, numberFormatter } from './Charts.utils' import { ChartHighlight } from './useChartHighlight' import NoDataPlaceholder from './NoDataPlaceholder' import { MultiAttribute } from './ComposedChartHandler' import { calculateTotalChartAggregate, CustomLabel, CustomTooltip, formatBytes, } from './ComposedChart.utils' export interface BarChartProps extends CommonChartProps { attributes: MultiAttribute[] yAxisKey: string xAxisKey: string displayDateInUtc?: boolean onBarClick?: (datum: Datum, tooltipData?: CategoricalChartState) => void emptyStateMessage?: string showLegend?: boolean xAxisIsDate?: boolean XAxisProps?: ComponentProps YAxisProps?: ComponentProps showGrid?: boolean showTooltip?: boolean showTotal?: boolean showMaxValue?: boolean chartHighlight?: ChartHighlight hideChartType?: boolean chartStyle?: string onChartStyleChange?: (style: string) => void updateDateRange: any } export default function ComposedChart({ data, attributes, 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, showTooltip = false, showTotal = true, showMaxValue = false, chartHighlight, hideChartType, chartStyle, onChartStyleChange, updateDateRange, }: BarChartProps) { const { resolvedTheme } = useTheme() const [_activePayload, setActivePayload] = useState(null) const [_showMaxValue, setShowMaxValue] = useState(showMaxValue) const [focusDataIndex, setFocusDataIndex] = useState(null) const [hoveredLabel, setHoveredLabel] = useState(null) // Update chart colors when theme changes useEffect(() => { updateStackedChartColors(resolvedTheme?.includes('dark') ?? false) }, [resolvedTheme]) const { Container } = useChartSize(size) // 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, } const day = (value: number | string) => (displayDateInUtc ? dayjs(value).utc() : dayjs(value)) function getHeaderLabel() { if (!xAxisIsDate) { if (!focusDataIndex) return highlightedLabel return data[focusDataIndex]?.timestamp } return ( (focusDataIndex !== null && data && data[focusDataIndex] !== undefined && day(data[focusDataIndex].timestamp).format(customDateFormat)) || highlightedLabel ) } const maxAttribute = attributes.find((a) => a.isMaxValue) const maxAttributeData = { name: maxAttribute?.attribute, color: '#3ECF8E', } const lastDataPoint = !!data[data.length - 1] ? Object.entries(data[data.length - 1]) .map(([key, value]) => ({ dataKey: key, value: value as number, })) .filter((entry) => entry.dataKey !== 'timestamp') : undefined const resolvedHighlightedLabel = getHeaderLabel() const resolvedHighlightedValue = focusDataIndex !== null ? showTotal ? calculateTotalChartAggregate( _activePayload, maxAttribute?.attribute ? [maxAttribute?.attribute] : [] ) : data[focusDataIndex]?.[yAxisKey] : showTotal && lastDataPoint ? calculateTotalChartAggregate( lastDataPoint, maxAttribute?.attribute ? [maxAttribute?.attribute] : [] ) : highlightedValue const showHighlightActions = chartHighlight?.coordinates.left && chartHighlight?.coordinates.right && chartHighlight?.coordinates.left !== chartHighlight?.coordinates.right const chartData = data && !!data[0] ? Object.entries(data[0]) ?.map(([key, value], index) => ({ name: key, value: value, color: STACKED_CHART_COLORS[index - (1 % STACKED_CHART_COLORS.length)], })) .filter((att) => att.name !== 'timestamp' && att.name !== maxAttribute?.attribute) : [] const stackedAttributes = chartData.filter((att) => !att.name.includes('max')) const isPercentage = format === '%' const isRamChart = chartData?.some((att: any) => att.name.toLowerCase().includes('ram_')) //* // Set the y-axis domain // to the highest value in the chart data for percentage charts // to vertically zoom in on the data // */ const yDomain = [ 0, Math.max(...chartData.map((att) => (typeof att.value === 'number' ? att.value : 0))), ] if (data.length === 0) { return ( ) } return (
{ if (e.activeTooltipIndex !== focusDataIndex) { setFocusDataIndex(e.activeTooltipIndex) setActivePayload(e.activePayload) } const activeTimestamp = data[e.activeTooltipIndex]?.timestamp chartHighlight?.handleMouseMove({ activeLabel: activeTimestamp?.toString(), coordinates: e.activeLabel, }) }} onMouseDown={(e: any) => { const activeTimestamp = data[e.activeTooltipIndex]?.timestamp chartHighlight?.handleMouseDown({ activeLabel: activeTimestamp?.toString(), coordinates: e.activeLabel, }) }} onMouseUp={chartHighlight?.handleMouseUp} onMouseLeave={(e) => { setFocusDataIndex(null) setActivePayload(null) }} onClick={(tooltipData) => { const datum = tooltipData?.activePayload?.[0]?.payload if (onBarClick) onBarClick(datum, tooltipData) }} > {showGrid && } showTooltip ? ( ) : null } /> {chartStyle === 'bar' ? stackedAttributes.map((attribute) => ( a.attribute === attribute.name)?.label || attribute.name } /> )) : stackedAttributes.map((attribute) => ( a.attribute === attribute.name)?.label || attribute.name } /> ))} {/* Max value, if available */} {maxAttribute && _showMaxValue && ( )} {/* Selection highlight */} {showHighlightActions && ( )} {data && (
{xAxisIsDate ? day(data[0]?.timestamp).format(customDateFormat) : data[0]?.timestamp} {xAxisIsDate ? day(data[data?.length - 1]?.timestamp).format(customDateFormat) : data[data?.length - 1]?.timestamp}
)} {showLegend && ( )}
) }