/* eslint-disable @typescript-eslint/no-explicit-any */ "use client"; import React, { useCallback, useMemo, useState } from "react"; import { BarChart as CoreBarChart, Bar, ResponsiveContainer, Tooltip, XAxis, YAxis, Legend, CartesianGrid, } from "recharts"; // plane imports import { AXIS_LABEL_CLASSNAME } from "@plane/constants"; import { TBarChartProps } from "@plane/types"; // local components import { getLegendProps } from "../components/legend"; import { CustomXAxisTick, CustomYAxisTick } from "../components/tick"; import { CustomTooltip } from "../components/tooltip"; import { barShapeVariants } from "./bar"; const DEFAULT_BAR_FILL_COLOR = "#000000"; export const BarChart = React.memo((props: TBarChartProps) => { const { data, bars, xAxis, yAxis, barSize = 40, className, legend, margin, tickCount = { x: undefined, y: 10, }, customTicks, showTooltip = true, customTooltipContent, } = props; // states const [activeBar, setActiveBar] = useState(null); const [activeLegend, setActiveLegend] = useState(null); // derived values const { stackKeys, stackLabels } = useMemo(() => { const keys: string[] = []; const labels: Record = {}; for (const bar of bars) { keys.push(bar.key); labels[bar.key] = bar.label; } return { stackKeys: keys, stackLabels: labels }; }, [bars]); // get bar color dynamically based on payload const getBarColor = useCallback( (payload: Record[], barKey: string) => { const bar = bars.find((b) => b.key === barKey); if (!bar) return DEFAULT_BAR_FILL_COLOR; if (typeof bar.fill === "function") { const payloadItem = payload?.find((item) => item.dataKey === barKey); if (payloadItem?.payload) { try { return bar.fill(payloadItem.payload); } catch (error) { console.error(error); return DEFAULT_BAR_FILL_COLOR; } } else { return DEFAULT_BAR_FILL_COLOR; // fallback color when no payload data } } else { return bar.fill; } }, [bars] ); // get all bar colors const getAllBarColors = useCallback( (payload: any[]) => { const colors: Record = {}; for (const bar of bars) { colors[bar.key] = getBarColor(payload, bar.key); } return colors; }, [bars, getBarColor] ); const renderBars = useMemo( () => bars.map((bar) => ( { const shapeVariant = barShapeVariants[bar.shapeVariant ?? "bar"]; const node = shapeVariant(shapeProps, bar, stackKeys); return React.isValidElement(node) ? node : <>{node}; }} className="[&_path]:transition-opacity [&_path]:duration-200" onMouseEnter={() => setActiveBar(bar.key)} onMouseLeave={() => setActiveBar(null)} fill={getBarColor(data, bar.key)} /> )), [activeLegend, stackKeys, bars, getBarColor, data] ); return (
{ const TickComponent = customTicks?.x || CustomXAxisTick; return ; }} tickLine={false} axisLine={false} label={{ value: xAxis.label, dy: xAxis.dy ?? 28, className: AXIS_LABEL_CLASSNAME, }} tickCount={tickCount.x} /> { const TickComponent = customTicks?.y || CustomYAxisTick; return ; }} tickCount={tickCount.y} allowDecimals={!!yAxis.allowDecimals} /> {legend && ( // @ts-expect-error recharts types are not up to date setActiveLegend(payload.value)} onMouseLeave={() => setActiveLegend(null)} formatter={(value) => stackLabels[value]} {...getLegendProps(legend)} /> )} {showTooltip && ( { if (customTooltipContent) return customTooltipContent({ active, label, payload }); return ( ); }} /> )} {renderBars}
); }); BarChart.displayName = "BarChart";