import { BarRounded, BarStackHorizontal, BarStack as BarStackVertical } from '@visx/shape'; import { Text } from '@visx/text'; import type { ScaleOrdinal } from 'd3-scale'; import { equals, props } from 'ramda'; import { memo, useMemo } from 'react'; import { Tooltip } from '../../components'; import { getValueByUnit } from '../common/utils'; import { useGraphStyles } from './BarStack.styles'; import type { BarStackProps, BarType } from './models'; import { useGraphAndLegend } from './useGraphAndLegend'; interface Props extends Pick< BarStackProps, | 'data' | 'displayValues' | 'onSingleBarClick' | 'unit' | 'TooltipContent' | 'tooltipProps' > { width: number; height: number; isVerticalBar: boolean; colorScale: ScaleOrdinal; total: number; } interface GetFitsInBarProps { isVerticalBar: boolean; bar: { width: number; height: number }; unit?: 'percentage' | 'number'; } const getFitsInBar = ({ isVerticalBar, bar, unit }: GetFitsInBarProps): boolean => { if (isVerticalBar) { return bar.height >= 18; } return ( (equals(unit, 'number') && bar.width > 15) || (equals(unit, 'percentage') && bar.width > 35) ); }; interface GetClickProps { onSingleBarClick?: (barData: BarType) => void; bar: BarType | unknown; } const getClick = ({ onSingleBarClick, bar }: GetClickProps): ((e: MouseEvent) => void) | undefined => { if (onSingleBarClick) { return (e: MouseEvent): void => { if (!equals(e.button, 0)) { return; } onSingleBarClick(bar as BarType); }; } return undefined; }; const Graph = ({ width, height, isVerticalBar, colorScale, data, total, unit, displayValues, onSingleBarClick, tooltipProps, TooltipContent }: Props): JSX.Element => { const { classes } = useGraphStyles(); const BarStackComponent = useMemo( () => (isVerticalBar ? BarStackVertical : BarStackHorizontal), [isVerticalBar] ); const normalizedHeight = useMemo(() => height - 10, [height]); const { barStackData, xScale, yScale, keys } = useGraphAndLegend({ data, height: normalizedHeight, isVerticalBar, total, width }); return ( {/* @ts-expect-error - suppressing pre-existing type mismatch */} undefined } : { y: () => undefined })} xScale={xScale} yScale={yScale} > {(barStacks) => barStacks.map((barStack, index) => barStack.bars.map((bar) => { const isFirstBar = equals(index, 0); const isLastBar = equals(index, barStacks.length - 1); const fitsInBar = getFitsInBar({ bar, isVerticalBar, unit }); const textX = bar.x + bar.width / 2; const textY = bar.y + bar.height / 2; const click = getClick({ bar, onSingleBarClick }); return ( ) } position={isVerticalBar ? 'right' : 'bottom'} > {displayValues && fitsInBar && ( {getValueByUnit({ total, unit: unit || 'number', value: barStack.bars[0].bar.data[barStack.key] })} )} ); }) ) } ); }; const propsToMemoize = [ 'width', 'height', 'isVerticalBar', 'colorScale', 'data', 'total', 'unit', 'displayValues', 'tooltipProps' ]; export default memo(Graph, (prevProps, nextProps) => equals(props(propsToMemoize, prevProps), props(propsToMemoize, nextProps)) );