/** * Copyright (c) 2023-present Plane Software, Inc. and contributors * SPDX-License-Identifier: AGPL-3.0-only * See the LICENSE file for details. */ /* eslint-disable @typescript-eslint/no-explicit-any */ import React from "react"; // plane imports import type { TBarChartShapeVariant, TBarItem, TChartData } from "@plane/types"; import { cn } from "../../utils/classname"; // Constants const MIN_BAR_HEIGHT_FOR_INTERNAL_TEXT = 14; // Minimum height required to show text inside bar const BAR_TOP_BORDER_RADIUS = 4; // Border radius for the top of bars const BAR_BOTTOM_BORDER_RADIUS = 4; // Border radius for the bottom of bars const DEFAULT_LOLLIPOP_LINE_WIDTH = 2; // Width of lollipop stick const DEFAULT_LOLLIPOP_CIRCLE_RADIUS = 8; // Radius of lollipop circle const DEFAULT_BAR_FILL_COLOR = "#000000"; // Default color when fill is a function - black // Types interface TShapeProps { x: number; y: number; width: number; height: number; dataKey: string; payload: any; opacity?: number; } interface TBarProps extends TShapeProps { fill: string; stackKeys: string[]; textClassName?: string; showPercentage?: boolean; showTopBorderRadius?: boolean; showBottomBorderRadius?: boolean; borderRadius?: number; dotted?: boolean; } // Helper Functions const calculatePercentage = ( data: TChartData, stackKeys: T[], currentKey: T ): number => { const total = stackKeys.reduce((sum, key) => sum + data[key], 0); return total === 0 ? 0 : Math.round((data[currentKey] / total) * 100); }; const getBarPath = (x: number, y: number, width: number, height: number, topRadius: number, bottomRadius: number) => ` M${x},${y + topRadius} Q${x},${y} ${x + topRadius},${y} L${x + width - topRadius},${y} Q${x + width},${y} ${x + width},${y + topRadius} L${x + width},${y + height - bottomRadius} Q${x + width},${y + height} ${x + width - bottomRadius},${y + height} L${x + bottomRadius},${y + height} Q${x},${y + height} ${x},${y + height - bottomRadius} Z `; function PercentageText({ x, y, percentage, className, }: { x: number; y: number; percentage: number; className?: string; }) { return ( {percentage}% ); } // Base Components const CustomBar = React.memo(function CustomBar(props: TBarProps) { const { opacity, fill, x, y, width, height, dataKey, stackKeys, payload, textClassName, showPercentage, showTopBorderRadius, showBottomBorderRadius, borderRadius, } = props; if (!height) return null; const currentBarPercentage = calculatePercentage(payload, stackKeys, dataKey); const TEXT_PADDING_Y = Math.min(6, Math.abs(MIN_BAR_HEIGHT_FOR_INTERNAL_TEXT - height / 2)); const textY = y + height - TEXT_PADDING_Y; const showText = showPercentage && height >= MIN_BAR_HEIGHT_FOR_INTERNAL_TEXT && currentBarPercentage !== undefined && !Number.isNaN(currentBarPercentage); const topBorderRadius = showTopBorderRadius ? (borderRadius ?? BAR_TOP_BORDER_RADIUS) : 0; const bottomBorderRadius = showBottomBorderRadius ? (borderRadius ?? BAR_BOTTOM_BORDER_RADIUS) : 0; return ( {showText && ( )} ); }); const CustomBarLollipop = React.memo(function CustomBarLollipop(props: TBarProps) { const { fill, x, y, width, height, dataKey, stackKeys, payload, textClassName, showPercentage, dotted } = props; const currentBarPercentage = calculatePercentage(payload, stackKeys, dataKey); return ( {showPercentage && ( )} ); }); // Shape Variants /** * Factory function to create shape variants with consistent props * @param Component - The base component to render * @param factoryProps - Additional props to pass to the component * @returns A function that creates the shape with proper props */ const createShapeVariant = (Component: React.ComponentType, factoryProps?: Partial) => (shapeProps: TShapeProps, bar: TBarItem, stackKeys: string[]): React.ReactNode => { const showTopBorderRadius = bar.showTopBorderRadius?.(shapeProps.dataKey, shapeProps.payload); const showBottomBorderRadius = bar.showBottomBorderRadius?.(shapeProps.dataKey, shapeProps.payload); return ( ); }; export { DEFAULT_BAR_FILL_COLOR }; export const barShapeVariants: Record< TBarChartShapeVariant, (props: TShapeProps, bar: TBarItem, stackKeys: string[]) => React.ReactNode > = { bar: createShapeVariant(CustomBar), // Standard bar with rounded corners lollipop: createShapeVariant(CustomBarLollipop), // Line with circle at top "lollipop-dotted": createShapeVariant(CustomBarLollipop, { dotted: true }), // Dotted line lollipop variant }; // Display names CustomBar.displayName = "CustomBar"; CustomBarLollipop.displayName = "CustomBarLollipop";