Back

88.23% Statements 15/17
66.66% Branches 14/21
100% Functions 2/2
88.23% Lines 15/17

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86                                                                                  7x 18x 18x 18x 18x 18x   18x       18x 18x 18x     18x   28x       18x       18x               18x                    
import { DataContext } from '@visx/xychart';
import { useContext } from 'react';
import styles from '../bar-chart.module.scss';
import { createGroupScale } from './band-scale';
import { computeSlotRect, getValueScaleBaseline } from './comparison-bars-geometry';
import type { BandScale } from './band-scale';
import type { DataPointDate } from '../../../types';
import type { FC } from 'react';
 
/** The slot the keyboard has selected. */
export type SelectedBar = {
	/** The data of the bars the slot draws at the selected category, which the ring encloses. */
	data: DataPointDate[];
	/** The selected slot. */
	slotKey: string;
	/** The thickness of the slot's widest bar, as a share of the slot. */
	thickness: number;
};
 
type AnyScale = ( ( input: unknown ) => number ) &
	Partial< Pick< BandScale, 'bandwidth' > > & { range: () => unknown[] };
 
/**
 * Outlines every bar in the keyboard-selected slot.
 *
 * @param props              - Component props.
 * @param props.selected     - The selected slot.
 * @param props.slotKeys     - The slots, in the order the band lays them out.
 * @param props.groupPadding - The padding between slots.
 * @param props.horizontal   - Whether the bars run horizontally.
 * @param props.xAccessor    - Reads a datum's x value.
 * @param props.yAccessor    - Reads a datum's y value.
 * @return The ring, or nothing when the slot has no position.
 */
export const SelectedBarRing: FC< {
	selected: SelectedBar;
	slotKeys: string[];
	groupPadding: number;
	horizontal: boolean;
	xAccessor: ( d: DataPointDate ) => string | number | Date | undefined;
	yAccessor: ( d: DataPointDate ) => number | undefined;
} > = ( { selected, slotKeys, groupPadding, horizontal, xAccessor, yAccessor } ) => {
	const context = useContext( DataContext );
	const bandScale = ( horizontal ? context?.yScale : context?.xScale ) as AnyScale | undefined;
	const valueScale = ( horizontal ? context?.xScale : context?.yScale ) as AnyScale | undefined;
	const bandwidth = bandScale?.bandwidth?.() ?? 0;
	const [ category ] = selected.data;
 
	Iif ( ! bandScale || ! valueScale || ! bandwidth || ! category ) {
		return null;
	}
 
	const groupScale = createGroupScale( slotKeys, bandwidth, groupPadding );
	const slotOffset = groupScale( selected.slotKey );
	const bandPosition = Number(
		bandScale( ( horizontal ? yAccessor : xAccessor )( category ) as never )
	);
	const valuePositions = selected.data
		.map( datum =>
			Number( valueScale( Number( ( horizontal ? xAccessor : yAccessor )( datum ) ) as never ) )
		)
		.filter( Number.isFinite );
 
	Iif ( slotOffset == null || ! Number.isFinite( bandPosition ) || ! valuePositions.length ) {
		return null;
	}
 
	const rect = computeSlotRect( {
		horizontal,
		bandPosition,
		slotOffset,
		slotThickness: groupScale.bandwidth(),
		valuePositions: [ getValueScaleBaseline( valueScale ), ...valuePositions ],
		widthFactor: selected.thickness,
	} );
	return (
		<rect
			{ ...rect }
			pointerEvents="none"
			aria-hidden="true"
			className={ styles[ 'selected-ring' ] }
			data-testid="bar-chart-selected-ring"
		/>
	);
};