diff --git a/resources/js/components/ui/stacked-bar-chart.test.tsx b/resources/js/components/ui/stacked-bar-chart.test.tsx new file mode 100644 index 00000000..2d4e7d2a --- /dev/null +++ b/resources/js/components/ui/stacked-bar-chart.test.tsx @@ -0,0 +1,51 @@ +import { render } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; + +import { StackedBarShape } from './stacked-bar-chart'; + +describe('StackedBarShape', () => { + it('uses card background for the rounded edge stroke', () => { + const { container } = render( + + + , + ); + + const path = container.querySelector('path'); + + expect(path?.getAttribute('stroke')).toBe('var(--card)'); + expect(path?.getAttribute('stroke-width')).toBe('1'); + expect(path?.getAttribute('stroke-linejoin')).toBe('round'); + }); + + it('clamps radius for very short bar segments', () => { + const { container } = render( + + + , + ); + + const path = container.querySelector('path'); + + expect(path?.getAttribute('d')).toContain('M 1 0'); + expect(path?.getAttribute('d')).toContain('H 19'); + }); +}); diff --git a/resources/js/components/ui/stacked-bar-chart.tsx b/resources/js/components/ui/stacked-bar-chart.tsx index b9ab20e6..d51583ba 100644 --- a/resources/js/components/ui/stacked-bar-chart.tsx +++ b/resources/js/components/ui/stacked-bar-chart.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useRef } from 'react'; -import { Bar, BarChart, Rectangle, XAxis } from 'recharts'; +import { Bar, BarChart, Rectangle, XAxis, type BarShapeProps } from 'recharts'; import { ChartConfig, @@ -31,13 +31,13 @@ interface StackedBarShapeProps { y: number; width: number; height: number; - fill: string; - payload: Record; + fill?: string; + payload?: Record; dataKey: string; dataKeys: string[]; } -function StackedBarShape({ +export function StackedBarShape({ x, y, width, @@ -49,8 +49,10 @@ function StackedBarShape({ }: StackedBarShapeProps) { if (height <= 0) return null; + const segmentPayload = payload ?? {}; + const radius = Math.min(BORDER_RADIUS, width / 2, height / 2); const visibleKeys = dataKeys.filter((key) => { - const value = payload[key]; + const value = segmentPayload[key]; return typeof value === 'number' && value > 0; }); @@ -61,36 +63,36 @@ function StackedBarShape({ if (isFirstVisible && isLastVisible) { path = ` - M ${x + BORDER_RADIUS} ${y} - H ${x + width - BORDER_RADIUS} - Q ${x + width} ${y} ${x + width} ${y + BORDER_RADIUS} - V ${y + height - BORDER_RADIUS} - Q ${x + width} ${y + height} ${x + width - BORDER_RADIUS} ${y + height} - H ${x + BORDER_RADIUS} - Q ${x} ${y + height} ${x} ${y + height - BORDER_RADIUS} - V ${y + BORDER_RADIUS} - Q ${x} ${y} ${x + BORDER_RADIUS} ${y} + M ${x + radius} ${y} + H ${x + width - radius} + Q ${x + width} ${y} ${x + width} ${y + radius} + V ${y + height - radius} + Q ${x + width} ${y + height} ${x + width - radius} ${y + height} + H ${x + radius} + Q ${x} ${y + height} ${x} ${y + height - radius} + V ${y + radius} + Q ${x} ${y} ${x + radius} ${y} Z `; } else if (isLastVisible) { path = ` - M ${x + BORDER_RADIUS} ${y} - H ${x + width - BORDER_RADIUS} - Q ${x + width} ${y} ${x + width} ${y + BORDER_RADIUS} + M ${x + radius} ${y} + H ${x + width - radius} + Q ${x + width} ${y} ${x + width} ${y + radius} V ${y + height} H ${x} - V ${y + BORDER_RADIUS} - Q ${x} ${y} ${x + BORDER_RADIUS} ${y} + V ${y + radius} + Q ${x} ${y} ${x + radius} ${y} Z `; } else if (isFirstVisible) { path = ` M ${x} ${y} H ${x + width} - V ${y + height - BORDER_RADIUS} - Q ${x + width} ${y + height} ${x + width - BORDER_RADIUS} ${y + height} - H ${x + BORDER_RADIUS} - Q ${x} ${y + height} ${x} ${y + height - BORDER_RADIUS} + V ${y + height - radius} + Q ${x + width} ${y + height} ${x + width - radius} ${y + height} + H ${x + radius} + Q ${x} ${y + height} ${x} ${y + height - radius} V ${y} Z `; @@ -105,15 +107,19 @@ function StackedBarShape({ `; } - return ; + return ( + + ); } -const CustomCursor = (props) => ( - +const CustomCursor = (props: React.ComponentProps) => ( + ); export interface StackedBarChartProps> { @@ -169,16 +175,20 @@ export function StackedBarChart>({ const shapeRenderers = useMemo(() => { return dataKeys.reduce( (acc, key) => { - acc[key] = (props: Record) => ( + acc[key] = (props: BarShapeProps) => ( )} + {...props} dataKey={key} dataKeys={dataKeys} /> ); + return acc; }, - {} as Record) => React.ReactNode>, + {} as Record< + string, + (props: BarShapeProps) => React.ReactElement | null + >, ); }, [dataKeys]); @@ -201,7 +211,7 @@ export function StackedBarChart>({ tickFormatter={xAxisFormatter} /> } + cursor={} content={