UNPKG

@nivo/bar

Version:
1 lines 119 kB
{"version":3,"file":"nivo-bar.mjs","sources":["../src/BarAnnotations.tsx","../src/BarLegends.tsx","../src/BarItem.tsx","../src/BarTooltip.tsx","../src/defaults.ts","../src/renderBar.ts","../src/compute/common.ts","../src/compute/grouped.ts","../src/compute/stacked.ts","../src/compute/legends.ts","../src/compute/totals.ts","../src/hooks.ts","../src/BarTotals.tsx","../src/Bar.tsx","../src/BarCanvas.tsx","../src/ResponsiveBar.tsx","../src/ResponsiveBarCanvas.tsx"],"sourcesContent":["import { Annotation, useAnnotations } from '@nivo/annotations'\nimport { BarAnnotationsProps, BarDatum } from './types'\n\nexport const BarAnnotations = <D extends BarDatum>({\n bars,\n annotations,\n}: BarAnnotationsProps<D>) => {\n const boundAnnotations = useAnnotations({\n data: bars,\n annotations,\n getPosition: bar => ({\n x: bar.x + bar.width / 2,\n y: bar.y + bar.height / 2,\n }),\n getDimensions: ({ height, width }) => ({\n width,\n height,\n size: Math.max(width, height),\n }),\n })\n\n return (\n <>\n {boundAnnotations.map((annotation, i) => (\n <Annotation key={i} {...annotation} />\n ))}\n </>\n )\n}\n","import { BoxLegendSvg } from '@nivo/legends'\nimport { BarLegendProps, LegendData } from './types'\n\ninterface BarLegendsProps {\n width: number\n height: number\n legends: [BarLegendProps, LegendData[]][]\n toggleSerie: (id: string | number) => void\n}\n\nexport const BarLegends = ({ width, height, legends, toggleSerie }: BarLegendsProps) => (\n <>\n {legends.map(([legend, data], i) => (\n <BoxLegendSvg\n key={i}\n {...legend}\n containerWidth={width}\n containerHeight={height}\n data={legend.data ?? data}\n toggleSerie={\n legend.toggleSerie && legend.dataFrom === 'keys' ? toggleSerie : undefined\n }\n />\n ))}\n </>\n)\n","import { createElement, MouseEvent, useCallback, useMemo } from 'react'\nimport { animated, to } from '@react-spring/web'\nimport { useTheme } from '@nivo/theming'\nimport { useTooltip } from '@nivo/tooltip'\nimport { Text } from '@nivo/text'\nimport { BarDatum, BarItemProps } from './types'\n\nexport const BarItem = <D extends BarDatum>({\n bar: { data, ...bar },\n style: {\n borderColor,\n color,\n height,\n labelColor,\n labelOpacity,\n labelX,\n labelY,\n transform,\n width,\n textAnchor,\n },\n borderRadius,\n borderWidth,\n label,\n shouldRenderLabel,\n isInteractive,\n onClick,\n onMouseEnter,\n onMouseLeave,\n tooltip,\n isFocusable,\n ariaLabel,\n ariaLabelledBy,\n ariaDescribedBy,\n ariaDisabled,\n ariaHidden,\n}: BarItemProps<D>) => {\n const theme = useTheme()\n const { showTooltipFromEvent, showTooltipAt, hideTooltip } = useTooltip()\n\n const renderTooltip = useMemo(\n () => () => createElement(tooltip, { ...bar, ...data }),\n [tooltip, bar, data]\n )\n\n const handleClick = useCallback(\n (event: MouseEvent<SVGRectElement>) => {\n onClick?.({ color: bar.color, ...data }, event)\n },\n [bar, data, onClick]\n )\n const handleTooltip = useCallback(\n (event: MouseEvent<SVGRectElement>) => showTooltipFromEvent(renderTooltip(), event),\n [showTooltipFromEvent, renderTooltip]\n )\n const handleMouseEnter = useCallback(\n (event: MouseEvent<SVGRectElement>) => {\n onMouseEnter?.(data, event)\n showTooltipFromEvent(renderTooltip(), event)\n },\n [data, onMouseEnter, showTooltipFromEvent, renderTooltip]\n )\n const handleMouseLeave = useCallback(\n (event: MouseEvent<SVGRectElement>) => {\n onMouseLeave?.(data, event)\n hideTooltip()\n },\n [data, hideTooltip, onMouseLeave]\n )\n\n // extra handlers to allow keyboard navigation\n const handleFocus = useCallback(() => {\n showTooltipAt(renderTooltip(), [bar.absX + bar.width / 2, bar.absY])\n }, [showTooltipAt, renderTooltip, bar])\n const handleBlur = useCallback(() => {\n hideTooltip()\n }, [hideTooltip])\n\n return (\n <animated.g transform={transform}>\n <animated.rect\n width={to(width, value => Math.max(value, 0))}\n height={to(height, value => Math.max(value, 0))}\n rx={borderRadius}\n ry={borderRadius}\n fill={data.fill ?? color}\n strokeWidth={borderWidth}\n stroke={borderColor}\n focusable={isFocusable}\n tabIndex={isFocusable ? 0 : undefined}\n aria-label={ariaLabel ? ariaLabel(data) : undefined}\n aria-labelledby={ariaLabelledBy ? ariaLabelledBy(data) : undefined}\n aria-describedby={ariaDescribedBy ? ariaDescribedBy(data) : undefined}\n aria-disabled={ariaDisabled ? ariaDisabled(data) : undefined}\n aria-hidden={ariaHidden ? ariaHidden(data) : undefined}\n onMouseEnter={isInteractive ? handleMouseEnter : undefined}\n onMouseMove={isInteractive ? handleTooltip : undefined}\n onMouseLeave={isInteractive ? handleMouseLeave : undefined}\n onClick={isInteractive ? handleClick : undefined}\n onFocus={isInteractive && isFocusable ? handleFocus : undefined}\n onBlur={isInteractive && isFocusable ? handleBlur : undefined}\n data-testid={`bar.item.${data.id}.${data.index}`}\n />\n {shouldRenderLabel && (\n <Text\n x={labelX}\n y={labelY}\n textAnchor={textAnchor}\n dominantBaseline=\"central\"\n fillOpacity={labelOpacity}\n style={{\n ...theme.labels.text,\n // We don't want the label to intercept mouse events\n pointerEvents: 'none',\n fill: labelColor,\n }}\n >\n {label}\n </Text>\n )}\n </animated.g>\n )\n}\n","import { BasicTooltip } from '@nivo/tooltip'\nimport { BarDatum, BarTooltipProps } from './types'\n\nexport const BarTooltip = <D extends BarDatum>({ color, label, ...data }: BarTooltipProps<D>) => {\n return <BasicTooltip id={label} value={data.formattedValue} enableChip={true} color={color} />\n}\n","import { InheritedColorConfig, OrdinalColorScaleConfig } from '@nivo/colors'\nimport {\n BarCommonProps,\n BarDatum,\n ComputedDatum,\n BarSvgPropsWithDefaults,\n BarCanvasPropsWithDefaults,\n} from './types'\nimport { BarItem } from './BarItem'\nimport { BarTooltip } from './BarTooltip'\nimport { renderBar } from './renderBar'\n\nexport const commonDefaultProps: Omit<BarCommonProps<BarDatum>, 'data' | 'theme'> = {\n indexBy: 'id',\n keys: ['value'],\n groupMode: 'stacked' as const,\n layout: 'vertical' as const,\n valueScale: { type: 'linear', nice: true, round: false },\n indexScale: { type: 'band', round: false },\n padding: 0.1,\n innerPadding: 0,\n enableGridX: false,\n enableGridY: true,\n enableLabel: true,\n label: 'formattedValue',\n labelPosition: 'middle' as const,\n labelOffset: 0,\n labelSkipWidth: 0,\n labelSkipHeight: 0,\n labelTextColor: { theme: 'labels.text.fill' },\n colorBy: 'id' as const,\n colors: { scheme: 'nivo' } as OrdinalColorScaleConfig,\n borderRadius: 0,\n borderWidth: 0,\n borderColor: { from: 'color' } as InheritedColorConfig<any>,\n isInteractive: true,\n tooltip: BarTooltip,\n tooltipLabel: (datum: ComputedDatum<BarDatum>) => `${datum.id} - ${datum.indexValue}`,\n legends: [],\n initialHiddenIds: [],\n annotations: [],\n enableTotals: false,\n totalsOffset: 10,\n}\n\nexport const svgDefaultProps: Omit<\n BarSvgPropsWithDefaults<BarDatum>,\n 'data' | 'width' | 'height' | 'theme'\n> = {\n ...commonDefaultProps,\n layers: ['grid', 'axes', 'bars', 'totals', 'markers', 'legends', 'annotations'],\n axisTop: null,\n axisRight: null,\n axisBottom: {},\n axisLeft: {},\n barComponent: BarItem,\n defs: [],\n fill: [],\n markers: [],\n animate: true,\n animateOnMount: false,\n motionConfig: 'default',\n role: 'img',\n isFocusable: false,\n}\n\nexport const canvasDefaultProps: Omit<\n BarCanvasPropsWithDefaults<BarDatum>,\n 'data' | 'width' | 'height' | 'theme'\n> = {\n ...commonDefaultProps,\n layers: ['grid', 'axes', 'bars', 'totals', 'legends', 'annotations'],\n axisTop: null,\n axisRight: null,\n axisBottom: {},\n axisLeft: {},\n renderBar,\n pixelRatio: typeof window !== 'undefined' ? (window.devicePixelRatio ?? 1) : 1,\n}\n","import { roundedRect } from '@nivo/canvas'\nimport { drawCanvasText } from '@nivo/text'\nimport { BarDatum, RenderBarProps } from './types'\n\nexport const renderBar = <D extends BarDatum>(\n ctx: CanvasRenderingContext2D,\n {\n bar: { color, height, width, x, y },\n borderColor,\n borderRadius,\n borderWidth,\n label,\n shouldRenderLabel,\n labelStyle,\n labelX,\n labelY,\n textAnchor,\n }: RenderBarProps<D>\n) => {\n ctx.fillStyle = color\n if (borderWidth > 0) {\n ctx.strokeStyle = borderColor\n ctx.lineWidth = borderWidth\n }\n\n ctx.beginPath()\n roundedRect(ctx, x, y, width, height, Math.min(borderRadius, height))\n ctx.fill()\n\n if (borderWidth > 0) {\n ctx.stroke()\n }\n\n if (shouldRenderLabel) {\n ctx.textBaseline = 'middle'\n ctx.textAlign = textAnchor === 'middle' ? 'center' : textAnchor\n drawCanvasText(ctx, labelStyle, label, x + labelX, y + labelY)\n }\n}\n","import { ScaleBandSpec, ScaleBand, computeScale } from '@nivo/scales'\nimport { commonDefaultProps } from '../defaults'\nimport { BarCommonProps, BarDatum } from '../types'\n\n/**\n * Generates indexed scale.\n */\nexport const getIndexScale = <D extends BarDatum>(\n data: readonly D[],\n getIndex: (datum: D) => string,\n padding: number,\n indexScale: ScaleBandSpec,\n size: number,\n axis: 'x' | 'y'\n) => {\n return (\n computeScale(\n indexScale,\n { all: data.map(getIndex), min: 0, max: 0 },\n size,\n axis\n ) as ScaleBand<string>\n ).padding(padding)\n}\n\n/**\n * This method ensures all the provided keys exist in the entire series.\n */\nexport const normalizeData = <D extends BarDatum>(data: readonly D[], keys: readonly string[]) =>\n data.map(\n item =>\n ({\n ...keys.reduce<Record<string, unknown>>((acc, key) => {\n acc[key] = null\n return acc\n }, {}),\n ...item,\n }) as D\n )\n\nexport const filterNullValues = <D extends BarDatum>(data: D) =>\n Object.keys(data).reduce<Record<string, unknown>>((acc, key) => {\n if (data[key]) {\n acc[key] = data[key]\n }\n return acc\n }, {}) as Exclude<D, null | undefined | false | '' | 0>\n\nexport const coerceValue = <T>(value: T) => [value, Number(value)] as const\n\nexport type BarLabelLayout = {\n labelX: number\n labelY: number\n textAnchor: 'start' | 'middle' | 'end'\n}\n\n/**\n * Compute the label position and alignment based on a given position and offset.\n */\nexport function useComputeLabelLayout<D extends BarDatum>(\n layout: BarCommonProps<D>['layout'] = commonDefaultProps.layout,\n reverse: boolean,\n labelPosition: BarCommonProps<D>['labelPosition'] = commonDefaultProps.labelPosition,\n labelOffset: BarCommonProps<D>['labelOffset'] = commonDefaultProps.labelOffset\n): (width: number, height: number) => BarLabelLayout {\n return (width: number, height: number) => {\n // If the chart is reversed, we want to make sure the offset is also reversed\n const computedLabelOffset = labelOffset * (reverse ? -1 : 1)\n\n if (layout === 'horizontal') {\n let x = width / 2\n if (labelPosition === 'start') {\n x = reverse ? width : 0\n } else if (labelPosition === 'end') {\n x = reverse ? 0 : width\n }\n return {\n labelX: x + computedLabelOffset,\n labelY: height / 2,\n textAnchor: labelPosition === 'middle' ? 'middle' : reverse ? 'end' : 'start',\n }\n } else {\n let y = height / 2\n if (labelPosition === 'start') {\n y = reverse ? 0 : height\n } else if (labelPosition === 'end') {\n y = reverse ? height : 0\n }\n return {\n labelX: width / 2,\n labelY: y - computedLabelOffset,\n textAnchor: 'middle',\n }\n }\n }\n}\n","import { Margin } from '@nivo/core'\nimport { OrdinalColorScale } from '@nivo/colors'\nimport { Scale, ScaleBand, computeScale } from '@nivo/scales'\nimport { BarDatum, BarSvgProps, ComputedBarDatum, ComputedDatum } from '../types'\nimport { coerceValue, filterNullValues, getIndexScale, normalizeData } from './common'\n\ntype Params<D extends BarDatum, XScaleInput, YScaleInput> = {\n data: readonly D[]\n formatValue: (value: number) => string\n getColor: OrdinalColorScale<ComputedDatum<D>>\n getIndex: (datum: D) => string\n getTooltipLabel: (datum: ComputedDatum<D>) => string\n innerPadding: number\n keys: string[]\n xScale: XScaleInput extends string ? ScaleBand<XScaleInput> : Scale<XScaleInput, number>\n yScale: YScaleInput extends string ? ScaleBand<YScaleInput> : Scale<YScaleInput, number>\n margin: Margin\n}\n\nconst gt = (value: number, other: number) => value > other\nconst lt = (value: number, other: number) => value < other\n\nconst range = (start: number, end: number) =>\n Array.from(' '.repeat(end - start), (_, index) => start + index)\n\nconst clampToZero = (value: number) => (gt(value, 0) ? 0 : value)\nconst zeroIfNotFinite = (value: number) => (isFinite(value) ? value : 0)\n\n/**\n * Generates x/y scales & bars for vertical grouped bar chart.\n */\nconst generateVerticalGroupedBars = <D extends BarDatum>(\n {\n data,\n formatValue,\n getColor,\n getIndex,\n getTooltipLabel,\n innerPadding = 0,\n keys,\n xScale,\n yScale,\n margin,\n }: Params<D, string, number>,\n barWidth: number,\n reverse: boolean,\n yRef: number\n): ComputedBarDatum<D>[] => {\n const compare = reverse ? lt : gt\n const getY = (d: number) => (compare(d, 0) ? (yScale(d) ?? 0) : yRef)\n const getHeight = (d: number, y: number) => (compare(d, 0) ? yRef - y : (yScale(d) ?? 0) - yRef)\n const cleanedData = data.map(filterNullValues)\n\n const bars: ComputedBarDatum<D>[] = []\n keys.forEach((key, i) =>\n range(0, xScale.domain().length).forEach(index => {\n const [rawValue, value] = coerceValue(data[index][key])\n const indexValue = getIndex(data[index])\n const x = (xScale(indexValue) ?? 0) + barWidth * i + innerPadding * i\n const y = getY(value)\n const barHeight = getHeight(value, y)\n const barData: ComputedDatum<D> = {\n id: key,\n value: rawValue === null ? rawValue : value,\n formattedValue: formatValue(value),\n hidden: false,\n index,\n indexValue,\n data: cleanedData[index],\n }\n\n bars.push({\n key: `${key}.${barData.indexValue}`,\n index: bars.length,\n data: barData,\n x,\n y,\n absX: margin.left + x,\n absY: margin.top + y,\n width: barWidth,\n height: barHeight,\n color: getColor(barData),\n label: getTooltipLabel(barData),\n })\n })\n )\n\n return bars\n}\n\n/**\n * Generates x/y scales & bars for horizontal grouped bar chart.\n */\nconst generateHorizontalGroupedBars = <D extends BarDatum>(\n {\n data,\n formatValue,\n getIndex,\n getColor,\n getTooltipLabel,\n keys,\n innerPadding = 0,\n xScale,\n yScale,\n margin,\n }: Params<D, number, string>,\n barHeight: number,\n reverse: boolean,\n xRef: number\n): ComputedBarDatum<D>[] => {\n const compare = reverse ? lt : gt\n const getX = (d: number) => (compare(d, 0) ? xRef : (xScale(d) ?? 0))\n const getWidth = (d: number, x: number) => (compare(d, 0) ? (xScale(d) ?? 0) - xRef : xRef - x)\n const cleanedData = data.map(filterNullValues)\n\n const bars: ComputedBarDatum<D>[] = []\n keys.forEach((key, i) =>\n range(0, yScale.domain().length).forEach(index => {\n const [rawValue, value] = coerceValue(data[index][key])\n const indexValue = getIndex(data[index])\n const x = getX(value)\n const y = (yScale(indexValue) ?? 0) + barHeight * i + innerPadding * i\n const barWidth = getWidth(value, x)\n const barData: ComputedDatum<D> = {\n id: key,\n value: rawValue === null ? rawValue : value,\n formattedValue: formatValue(value),\n hidden: false,\n index,\n indexValue,\n data: cleanedData[index],\n }\n\n bars.push({\n key: `${key}.${barData.indexValue}`,\n index: bars.length,\n data: barData,\n x,\n y,\n absX: margin.left + x,\n absY: margin.top + y,\n width: barWidth,\n height: barHeight,\n color: getColor(barData),\n label: getTooltipLabel(barData),\n })\n })\n )\n\n return bars\n}\n\n/**\n * Generates x/y scales & bars for grouped bar chart.\n */\nexport const generateGroupedBars = <D extends BarDatum>({\n layout,\n width,\n height,\n padding = 0,\n innerPadding = 0,\n valueScale,\n indexScale: indexScaleConfig,\n hiddenIds = [],\n ...props\n}: Pick<\n Required<BarSvgProps<D>>,\n | 'data'\n | 'height'\n | 'valueScale'\n | 'indexScale'\n | 'innerPadding'\n | 'keys'\n | 'layout'\n | 'padding'\n | 'width'\n> & {\n formatValue: (value: number) => string\n getColor: OrdinalColorScale<ComputedDatum<D>>\n getIndex: (datum: D) => string\n getTooltipLabel: (datum: ComputedDatum<D>) => string\n margin: Margin\n hiddenIds?: readonly (string | number)[]\n}) => {\n const keys = props.keys.filter(key => !hiddenIds.includes(key))\n const data = normalizeData(props.data, keys)\n const [axis, otherAxis, size] =\n layout === 'vertical' ? (['y', 'x', width] as const) : (['x', 'y', height] as const)\n const indexScale = getIndexScale(\n data,\n props.getIndex,\n padding,\n indexScaleConfig,\n size,\n otherAxis\n )\n\n const clampMin = valueScale.min === 'auto' ? clampToZero : (value: number) => value\n\n const values = data\n .reduce<number[]>((acc, entry) => [...acc, ...keys.map(k => entry[k] as number)], [])\n .filter(Boolean)\n const min = clampMin(Math.min(...values))\n const max = zeroIfNotFinite(Math.max(...values))\n\n const scale = computeScale(\n valueScale,\n { all: values, min, max },\n axis === 'x' ? width : height,\n axis\n )\n\n const [xScale, yScale] = layout === 'vertical' ? [indexScale, scale] : [scale, indexScale]\n\n // As we use extra inner padding between the bars, we need to adjust the bandwidth.\n const bandwidth = (indexScale.bandwidth() - innerPadding * (keys.length - 1)) / keys.length\n const params = [\n { ...props, data, keys, innerPadding, xScale, yScale } as Params<D, any, any>,\n bandwidth,\n valueScale.reverse ?? false,\n scale(0) ?? 0,\n ] as const\n\n const bars: ComputedBarDatum<D>[] =\n bandwidth > 0\n ? layout === 'vertical'\n ? generateVerticalGroupedBars(...params)\n : generateHorizontalGroupedBars(...params)\n : []\n\n return { xScale, yScale, bars }\n}\n","import { Margin } from '@nivo/core'\nimport { OrdinalColorScale } from '@nivo/colors'\nimport { Scale, ScaleBand, computeScale } from '@nivo/scales'\nimport { Series, SeriesPoint, stack, stackOffsetDiverging } from 'd3-shape'\nimport { BarDatum, BarSvgProps, ComputedBarDatum, ComputedDatum } from '../types'\nimport { coerceValue, filterNullValues, getIndexScale, normalizeData } from './common'\n\ntype StackDatum<D extends BarDatum> = SeriesPoint<D>\n\ntype Params<D extends BarDatum, XScaleInput, YScaleInput> = {\n formatValue: (value: number) => string\n getColor: OrdinalColorScale<ComputedDatum<D>>\n getIndex: (datum: D) => string\n getTooltipLabel: (datum: ComputedDatum<D>) => string\n innerPadding: number\n stackedData: Series<D, string>[]\n xScale: XScaleInput extends string ? ScaleBand<XScaleInput> : Scale<XScaleInput, number>\n yScale: YScaleInput extends string ? ScaleBand<YScaleInput> : Scale<YScaleInput, number>\n margin: Margin\n}\n\nconst flattenDeep = <T>(arr: T[]): T =>\n arr.some(Array.isArray) ? flattenDeep(([] as T[]).concat(...arr)) : (arr as unknown as T)\n\nconst filterZerosIfLog = (array: number[], type: string) =>\n type === 'log' ? array.filter(num => num !== 0) : array\n\n/**\n * Generates x/y scales & bars for vertical stacked bar chart.\n */\nconst generateVerticalStackedBars = <D extends BarDatum>(\n {\n formatValue,\n getColor,\n getIndex,\n getTooltipLabel,\n innerPadding,\n stackedData,\n xScale,\n yScale,\n margin,\n }: Params<D, string, number>,\n barWidth: number,\n reverse: boolean\n): ComputedBarDatum<D>[] => {\n const getY = (d: StackDatum<D>) => yScale(d[reverse ? 0 : 1])\n const getHeight = (d: StackDatum<D>, y: number) => (yScale(d[reverse ? 1 : 0]) ?? 0) - y\n\n const bars: ComputedBarDatum<D>[] = []\n stackedData.forEach(stackedDataItem =>\n xScale.domain().forEach((index, i) => {\n const d = stackedDataItem[i]\n const x = xScale(getIndex(d.data)) ?? 0\n const y = (getY(d) ?? 0) + innerPadding * 0.5\n const barHeight = getHeight(d, y) - innerPadding\n const [rawValue, value] = coerceValue(d.data[stackedDataItem.key])\n\n const barData: ComputedDatum<D> = {\n id: stackedDataItem.key,\n value: rawValue === null ? rawValue : value,\n formattedValue: formatValue(value),\n hidden: false,\n index: i,\n indexValue: index,\n data: filterNullValues(d.data),\n }\n\n bars.push({\n key: `${stackedDataItem.key}.${index}`,\n index: bars.length,\n data: barData,\n x,\n y,\n absX: margin.left + x,\n absY: margin.top + y,\n width: barWidth,\n height: barHeight,\n color: getColor(barData),\n label: getTooltipLabel(barData),\n })\n })\n )\n\n return bars\n}\n\n/**\n * Generates x/y scales & bars for horizontal stacked bar chart.\n */\nconst generateHorizontalStackedBars = <D extends BarDatum>(\n {\n formatValue,\n getColor,\n getIndex,\n getTooltipLabel,\n innerPadding,\n stackedData,\n xScale,\n yScale,\n margin,\n }: Params<D, number, string>,\n barHeight: number,\n reverse: boolean\n): ComputedBarDatum<D>[] => {\n const getX = (d: StackDatum<D>) => xScale(d[reverse ? 1 : 0])\n const getWidth = (d: StackDatum<D>, x: number) => (xScale(d[reverse ? 0 : 1]) ?? 0) - x\n\n const bars: ComputedBarDatum<D>[] = []\n stackedData.forEach(stackedDataItem =>\n yScale.domain().forEach((index, i) => {\n const d = stackedDataItem[i]\n const y = yScale(getIndex(d.data)) ?? 0\n const x = (getX(d) ?? 0) + innerPadding * 0.5\n const barWidth = getWidth(d, x) - innerPadding\n const [rawValue, value] = coerceValue(d.data[stackedDataItem.key])\n\n const barData: ComputedDatum<D> = {\n id: stackedDataItem.key,\n value: rawValue === null ? rawValue : value,\n formattedValue: formatValue(value),\n hidden: false,\n index: i,\n indexValue: index,\n data: filterNullValues(d.data),\n }\n\n bars.push({\n key: `${stackedDataItem.key}.${index}`,\n index: bars.length,\n data: barData,\n x,\n y,\n absX: margin.left + x,\n absY: margin.top + y,\n width: barWidth,\n height: barHeight,\n color: getColor(barData),\n label: getTooltipLabel(barData),\n })\n })\n )\n\n return bars\n}\n\n/**\n * Generates x/y scales & bars for stacked bar chart.\n */\nexport const generateStackedBars = <RawDatum extends BarDatum>({\n data,\n layout,\n width,\n height,\n padding = 0,\n valueScale,\n indexScale: indexScaleConfig,\n hiddenIds = [],\n ...props\n}: Pick<\n Required<BarSvgProps<RawDatum>>,\n | 'data'\n | 'height'\n | 'valueScale'\n | 'indexScale'\n | 'innerPadding'\n | 'keys'\n | 'layout'\n | 'padding'\n | 'width'\n> & {\n formatValue: (value: number) => string\n getColor: OrdinalColorScale<ComputedDatum<RawDatum>>\n getIndex: (datum: RawDatum) => string\n getTooltipLabel: (datum: ComputedDatum<RawDatum>) => string\n margin: Margin\n hiddenIds?: readonly (string | number)[]\n}) => {\n const keys = props.keys.filter(key => !hiddenIds.includes(key))\n const stackedData = stack<RawDatum, string>().keys(keys).offset(stackOffsetDiverging)(\n normalizeData(data, keys)\n )\n\n const [axis, otherAxis, size] =\n layout === 'vertical' ? (['y', 'x', width] as const) : (['x', 'y', height] as const)\n const indexScale = getIndexScale(\n data,\n props.getIndex,\n padding,\n indexScaleConfig,\n size,\n otherAxis\n )\n\n const values = filterZerosIfLog(\n flattenDeep(stackedData as unknown as number[][]),\n valueScale.type\n )\n const min = Math.min(...values)\n const max = Math.max(...values)\n\n const scale = computeScale(\n valueScale,\n { all: values, min, max },\n axis === 'x' ? width : height,\n axis\n )\n\n const [xScale, yScale] = layout === 'vertical' ? [indexScale, scale] : [scale, indexScale]\n\n const innerPadding = props.innerPadding > 0 ? props.innerPadding : 0\n const bandwidth = indexScale.bandwidth()\n const params = [\n { ...props, innerPadding, stackedData, xScale, yScale } as Params<RawDatum, any, any>,\n bandwidth,\n valueScale.reverse ?? false,\n ] as const\n\n const bars: ComputedBarDatum<RawDatum>[] =\n bandwidth > 0\n ? layout === 'vertical'\n ? generateVerticalStackedBars(...params)\n : generateHorizontalStackedBars(...params)\n : []\n\n return { xScale, yScale, bars }\n}\n","import {\n BarDatum,\n BarLegendProps,\n BarSvgProps,\n BarsWithHidden,\n LegendData,\n LegendLabelDatum,\n} from '../types'\nimport { getPropertyAccessor } from '@nivo/core'\nimport uniqBy from 'lodash/uniqBy.js'\n\nexport const getLegendDataForKeys = <RawDatum extends BarDatum>(\n bars: BarsWithHidden<RawDatum>,\n layout: NonNullable<BarSvgProps<RawDatum>['layout']>,\n direction: BarLegendProps['direction'],\n groupMode: NonNullable<BarSvgProps<RawDatum>['groupMode']>,\n reverse: boolean,\n getLegendLabel: (datum: LegendLabelDatum<RawDatum>) => string\n): LegendData[] => {\n const data = uniqBy(\n bars.map(bar => ({\n id: bar.data.id,\n label: getLegendLabel(bar.data),\n hidden: bar.data.hidden,\n color: bar.color ?? '#000',\n })),\n ({ id }) => id\n )\n\n if (\n (layout === 'vertical' &&\n groupMode === 'stacked' &&\n direction === 'column' &&\n reverse !== true) ||\n (layout === 'horizontal' && groupMode === 'stacked' && reverse === true)\n ) {\n data.reverse()\n }\n\n return data\n}\n\nexport const getLegendDataForIndexes = <RawDatum extends BarDatum>(\n bars: BarsWithHidden<RawDatum>,\n layout: NonNullable<BarSvgProps<RawDatum>['layout']>,\n getLegendLabel: (datum: LegendLabelDatum<RawDatum>) => string\n): LegendData[] => {\n const data = uniqBy(\n bars.map(bar => ({\n id: bar.data.indexValue ?? '',\n label: getLegendLabel(bar.data),\n hidden: bar.data.hidden,\n color: bar.color ?? '#000',\n })),\n ({ id }) => id\n )\n\n if (layout === 'horizontal') {\n data.reverse()\n }\n\n return data\n}\n\nexport const getLegendData = <RawDatum extends BarDatum>({\n bars,\n direction,\n from,\n groupMode,\n layout,\n legendLabel,\n reverse,\n}: Pick<Required<BarSvgProps<RawDatum>>, 'layout' | 'groupMode'> & {\n bars: BarsWithHidden<RawDatum>\n direction: BarLegendProps['direction']\n from: BarLegendProps['dataFrom']\n legendLabel: BarSvgProps<RawDatum>['legendLabel']\n reverse: boolean\n}) => {\n const getLegendLabel = getPropertyAccessor(\n legendLabel ?? (from === 'indexes' ? 'indexValue' : 'id')\n )\n\n if (from === 'indexes') {\n return getLegendDataForIndexes(bars, layout, getLegendLabel)\n }\n\n return getLegendDataForKeys(bars, layout, direction, groupMode, reverse, getLegendLabel)\n}\n","import { AnyScale, ScaleBand } from '@nivo/scales'\nimport { commonDefaultProps } from '../defaults'\nimport { BarCommonProps, BarDatum, ComputedBarDatum } from '../types'\n\nexport interface BarTotalsData {\n key: string\n x: number\n y: number\n value: number\n formattedValue: string\n animationOffset: number\n}\n\nexport const computeBarTotals = <D extends BarDatum>(\n bars: ComputedBarDatum<D>[],\n xScale: ScaleBand<string> | AnyScale,\n yScale: ScaleBand<string> | AnyScale,\n layout: BarCommonProps<D>['layout'] = commonDefaultProps.layout,\n groupMode: BarCommonProps<D>['groupMode'] = commonDefaultProps.groupMode,\n totalsOffset: number,\n formatValue: (value: number) => string\n) => {\n const totals = [] as BarTotalsData[]\n\n if (bars.length === 0) return totals\n\n const totalsByIndex = new Map<string | number, number>()\n\n const barWidth = bars[0].width\n const barHeight = bars[0].height\n\n if (groupMode === 'stacked') {\n const totalsPositivesByIndex = new Map<string | number, number>()\n\n bars.forEach(bar => {\n const { indexValue, value } = bar.data\n updateTotalsByIndex(totalsByIndex, indexValue, Number(value))\n updateTotalsPositivesByIndex(totalsPositivesByIndex, indexValue, Number(value))\n })\n\n totalsPositivesByIndex.forEach((totalsPositive, indexValue) => {\n const indexTotal = totalsByIndex.get(indexValue) || 0\n\n let xPosition: number\n let yPosition: number\n let animationOffset: number\n\n if (layout === 'vertical') {\n xPosition = xScale(indexValue)\n yPosition = yScale(totalsPositive)\n animationOffset = yScale(totalsPositive / 2)\n } else {\n xPosition = xScale(totalsPositive)\n yPosition = yScale(indexValue)\n animationOffset = xScale(totalsPositive / 2)\n }\n\n xPosition += layout === 'vertical' ? barWidth / 2 : totalsOffset\n yPosition += layout === 'vertical' ? -totalsOffset : barHeight / 2\n\n totals.push({\n key: 'total_' + indexValue,\n x: xPosition,\n y: yPosition,\n value: indexTotal,\n formattedValue: formatValue(indexTotal),\n animationOffset,\n })\n })\n } else if (groupMode === 'grouped') {\n const greatestValueByIndex = new Map<string | number, number>()\n const numberOfBarsByIndex = new Map()\n\n bars.forEach(bar => {\n const { indexValue, value } = bar.data\n updateTotalsByIndex(totalsByIndex, indexValue, Number(value))\n updateGreatestValueByIndex(greatestValueByIndex, indexValue, Number(value))\n updateNumberOfBarsByIndex(numberOfBarsByIndex, indexValue)\n })\n\n greatestValueByIndex.forEach((greatestValue, indexValue) => {\n const indexTotal = totalsByIndex.get(indexValue) || 0\n const numberOfBars = numberOfBarsByIndex.get(indexValue)\n\n let xPosition: number\n let yPosition: number\n let animationOffset: number\n\n if (layout === 'vertical') {\n xPosition = xScale(indexValue)\n yPosition = yScale(greatestValue)\n animationOffset = yScale(greatestValue / 2)\n } else {\n xPosition = xScale(greatestValue)\n yPosition = yScale(indexValue)\n animationOffset = xScale(greatestValue / 2)\n }\n\n const indexBarsWidth = numberOfBars * barWidth\n const indexBarsHeight = numberOfBars * barHeight\n\n xPosition += layout === 'vertical' ? indexBarsWidth / 2 : totalsOffset\n yPosition += layout === 'vertical' ? -totalsOffset : indexBarsHeight / 2\n\n totals.push({\n key: 'total_' + indexValue,\n x: xPosition,\n y: yPosition,\n value: indexTotal,\n formattedValue: formatValue(indexTotal),\n animationOffset,\n })\n })\n }\n return totals\n}\n\n// this function is used to compute the total value for the indexes. The total value is later rendered on the chart\nexport const updateTotalsByIndex = (\n totalsByIndex: Map<string | number, number>,\n indexValue: string | number,\n value: number\n) => {\n const currentIndexValue = totalsByIndex.get(indexValue) || 0\n totalsByIndex.set(indexValue, currentIndexValue + value)\n}\n\n// this function is used to compute only the positive values of the indexes. Useful to position the text right above the last stacked bar. It prevents overlapping in case of negative values\nexport const updateTotalsPositivesByIndex = (\n totalsPositivesByIndex: Map<string | number, number>,\n indexValue: string | number,\n value: number\n) => {\n const currentIndexValue = totalsPositivesByIndex.get(indexValue) || 0\n totalsPositivesByIndex.set(indexValue, currentIndexValue + (value > 0 ? value : 0))\n}\n\n// this function is used to keep track of the highest value for the indexes. Useful to position the text above the longest grouped bar\nexport const updateGreatestValueByIndex = (\n greatestValueByIndex: Map<string | number, number>,\n indexValue: string | number,\n value: number\n) => {\n const currentGreatestValue = greatestValueByIndex.get(indexValue) || 0\n greatestValueByIndex.set(indexValue, Math.max(currentGreatestValue, Number(value)))\n}\n\n// this function is used to save the number of bars for the indexes. Useful to position the text in the middle of the grouped bars\nexport const updateNumberOfBarsByIndex = (\n numberOfBarsByIndex: Map<string | number, number>,\n indexValue: string | number\n) => {\n const currentNumberOfBars = numberOfBarsByIndex.get(indexValue) || 0\n numberOfBarsByIndex.set(indexValue, currentNumberOfBars + 1)\n}\n","import { useCallback, useMemo, useState } from 'react'\nimport { useInheritedColor, useOrdinalColorScale } from '@nivo/colors'\nimport { usePropertyAccessor, useValueFormatter, Margin } from '@nivo/core'\nimport { useTheme } from '@nivo/theming'\nimport {\n DataProps,\n BarCommonProps,\n BarDatum,\n ComputedBarDatumWithValue,\n LegendData,\n BarLegendProps,\n} from './types'\nimport { commonDefaultProps } from './defaults'\nimport { generateGroupedBars, generateStackedBars, getLegendData } from './compute'\nimport { computeBarTotals } from './compute/totals'\n\nexport const useBar = <D extends BarDatum>({\n indexBy = commonDefaultProps.indexBy,\n keys = commonDefaultProps.keys,\n label = commonDefaultProps.label,\n tooltipLabel = commonDefaultProps.tooltipLabel,\n valueFormat,\n colors = commonDefaultProps.colors,\n colorBy = commonDefaultProps.colorBy,\n borderColor = commonDefaultProps.borderColor,\n labelTextColor = commonDefaultProps.labelTextColor,\n groupMode = commonDefaultProps.groupMode,\n layout = commonDefaultProps.layout,\n data,\n margin,\n width,\n height,\n padding = commonDefaultProps.padding,\n innerPadding = commonDefaultProps.innerPadding,\n valueScale = commonDefaultProps.valueScale,\n indexScale = commonDefaultProps.indexScale,\n initialHiddenIds = commonDefaultProps.initialHiddenIds,\n enableLabel = commonDefaultProps.enableLabel,\n labelSkipWidth = commonDefaultProps.labelSkipWidth,\n labelSkipHeight = commonDefaultProps.labelSkipHeight,\n legends = commonDefaultProps.legends,\n legendLabel,\n totalsOffset = commonDefaultProps.totalsOffset,\n}: Partial<\n Pick<\n BarCommonProps<D>,\n | 'indexBy'\n | 'keys'\n | 'label'\n | 'tooltipLabel'\n | 'valueFormat'\n | 'colors'\n | 'colorBy'\n | 'borderColor'\n | 'labelTextColor'\n | 'groupMode'\n | 'layout'\n | 'padding'\n | 'innerPadding'\n | 'valueScale'\n | 'indexScale'\n | 'initialHiddenIds'\n | 'enableLabel'\n | 'labelSkipWidth'\n | 'labelSkipHeight'\n | 'legends'\n | 'legendLabel'\n | 'totalsOffset'\n >\n> & {\n width: number\n height: number\n margin: Margin\n data: DataProps<D>['data']\n}) => {\n const [hiddenIds, setHiddenIds] = useState(initialHiddenIds ?? [])\n const toggleSerie = useCallback((id: string | number) => {\n setHiddenIds(state =>\n state.indexOf(id) > -1 ? state.filter(item => item !== id) : [...state, id]\n )\n }, [])\n\n const getIndex = usePropertyAccessor(indexBy)\n const getLabel = usePropertyAccessor(label)\n const getTooltipLabel = usePropertyAccessor(tooltipLabel)\n const formatValue = useValueFormatter(valueFormat)\n\n const theme = useTheme()\n const getColor = useOrdinalColorScale(colors, colorBy)\n const getBorderColor = useInheritedColor<ComputedBarDatumWithValue<D>>(borderColor, theme)\n const getLabelColor = useInheritedColor<ComputedBarDatumWithValue<D>>(labelTextColor, theme)\n\n const generateBars = groupMode === 'grouped' ? generateGroupedBars : generateStackedBars\n const { bars, xScale, yScale } = generateBars({\n layout,\n data,\n getIndex,\n keys,\n width,\n height,\n getColor,\n padding,\n innerPadding,\n valueScale,\n indexScale,\n hiddenIds,\n formatValue,\n getTooltipLabel,\n margin,\n })\n\n const barsWithValue = useMemo(\n () =>\n bars\n .filter((bar): bar is ComputedBarDatumWithValue<D> => bar.data.value !== null)\n .map((bar, index) => ({\n ...bar,\n index,\n })),\n [bars]\n )\n\n const shouldRenderBarLabel = useCallback(\n ({ width, height }: { height: number; width: number }) => {\n if (!enableLabel) return false\n if (labelSkipWidth > 0 && width < labelSkipWidth) return false\n if (labelSkipHeight > 0 && height < labelSkipHeight) return false\n return true\n },\n [enableLabel, labelSkipWidth, labelSkipHeight]\n )\n\n const legendData = useMemo(\n () =>\n keys.map(key => {\n const bar = bars.find(bar => bar.data.id === key)\n\n return { ...bar, data: { id: key, ...bar?.data, hidden: hiddenIds.includes(key) } }\n }),\n [hiddenIds, keys, bars]\n )\n\n const reverse = valueScale.reverse ?? false\n\n const legendsWithData: [BarLegendProps, LegendData[]][] = useMemo(\n () =>\n legends.map(legend => {\n const data = getLegendData({\n bars: legend.dataFrom === 'keys' ? legendData : bars,\n direction: legend.direction,\n from: legend.dataFrom,\n groupMode,\n layout,\n legendLabel,\n reverse,\n })\n\n return [legend, data]\n }),\n [legends, legendData, bars, groupMode, layout, legendLabel, reverse]\n )\n\n const barTotals = useMemo(\n () => computeBarTotals(bars, xScale, yScale, layout, groupMode, totalsOffset, formatValue),\n [bars, xScale, yScale, layout, groupMode, totalsOffset, formatValue]\n )\n\n return {\n bars,\n barsWithValue,\n xScale,\n yScale,\n getIndex,\n getLabel,\n getTooltipLabel,\n formatValue,\n getColor,\n getBorderColor,\n getLabelColor,\n shouldRenderBarLabel,\n hiddenIds,\n toggleSerie,\n legendsWithData,\n barTotals,\n }\n}\n","import { useTheme } from '@nivo/theming'\nimport { AnimationConfig, animated, useTransition } from '@react-spring/web'\nimport { BarCommonProps, BarDatum } from './types'\nimport { svgDefaultProps } from './defaults'\nimport { BarTotalsData } from './compute/totals'\n\ninterface Props<RawDatum extends BarDatum> {\n data: BarTotalsData[]\n springConfig: Partial<AnimationConfig>\n animate: boolean\n layout?: BarCommonProps<RawDatum>['layout']\n}\n\nexport const BarTotals = <RawDatum extends BarDatum>({\n data,\n springConfig,\n animate,\n layout = svgDefaultProps.layout,\n}: Props<RawDatum>) => {\n const theme = useTheme()\n const totalsTransition = useTransition<\n BarTotalsData,\n {\n x: number\n y: number\n labelOpacity: number\n }\n >(data, {\n keys: barTotal => barTotal.key,\n from: barTotal => ({\n x: layout === 'vertical' ? barTotal.x : barTotal.animationOffset,\n y: layout === 'vertical' ? barTotal.animationOffset : barTotal.y,\n labelOpacity: 0,\n }),\n enter: barTotal => ({\n x: barTotal.x,\n y: barTotal.y,\n labelOpacity: 1,\n }),\n update: barTotal => ({\n x: barTotal.x,\n y: barTotal.y,\n labelOpacity: 1,\n }),\n leave: barTotal => ({\n x: layout === 'vertical' ? barTotal.x : barTotal.animationOffset,\n y: layout === 'vertical' ? barTotal.animationOffset : barTotal.y,\n labelOpacity: 0,\n }),\n config: springConfig,\n immediate: !animate,\n initial: animate ? undefined : null,\n })\n\n return totalsTransition((style, barTotal) => (\n <animated.text\n key={barTotal.key}\n x={style.x}\n y={style.y}\n fillOpacity={style.labelOpacity}\n style={{\n ...theme.labels.text,\n pointerEvents: 'none',\n }}\n fontWeight=\"bold\"\n fontSize={theme.labels.text.fontSize}\n fontFamily={theme.labels.text.fontFamily}\n textAnchor={layout === 'vertical' ? 'middle' : 'start'}\n alignmentBaseline={layout === 'vertical' ? 'alphabetic' : 'middle'}\n >\n {barTotal.formattedValue}\n </animated.text>\n ))\n}\n","import { forwardRef, Ref, ReactElement } from 'react'\nimport { Axes, Grid } from '@nivo/axes'\nimport {\n CartesianMarkers,\n Container,\n SvgWrapper,\n // @ts-expect-error no types\n bindDefs,\n useDimensions,\n useMotionConfig,\n WithChartRef,\n} from '@nivo/core'\nimport { useTransition } from '@react-spring/web'\nimport { Fragment, ReactNode, createElement, useMemo } from 'react'\nimport { BarAnnotations } from './BarAnnotations'\nimport { BarLegends } from './BarLegends'\nimport { useBar } from './hooks'\nimport { svgDefaultProps } from './defaults'\nimport {\n BarComponent,\n BarCustomLayerProps,\n BarDatum,\n BarItemProps,\n BarLayerId,\n BarSvgProps,\n BarTooltipComponent,\n ComputedBarDatumWithValue,\n} from './types'\nimport { BarTotals } from './BarTotals'\nimport { useComputeLabelLayout } from './compute/common'\n\ntype InnerBarProps<D extends BarDatum> = Omit<\n BarSvgProps<D>,\n 'animate' | 'motionConfig' | 'renderWrapper' | 'theme'\n>\n\nconst InnerBar = <D extends BarDatum>({\n data,\n indexBy,\n keys,\n margin: partialMargin,\n width,\n height,\n groupMode,\n layout,\n valueScale,\n indexScale,\n padding,\n innerPadding,\n axisTop,\n axisRight,\n axisBottom = svgDefaultProps.axisBottom,\n axisLeft = svgDefaultProps.axisLeft,\n enableGridX = svgDefaultProps.enableGridX,\n enableGridY = svgDefaultProps.enableGridY,\n gridXValues,\n gridYValues,\n layers = svgDefaultProps.layers as BarLayerId[],\n barComponent = svgDefaultProps.barComponent as unknown as BarComponent<D>,\n enableLabel = svgDefaultProps.enableLabel,\n label,\n labelSkipWidth = svgDefaultProps.labelSkipWidth,\n labelSkipHeight = svgDefaultProps.labelSkipHeight,\n labelTextColor,\n labelPosition = svgDefaultProps.labelPosition,\n labelOffset = svgDefaultProps.labelOffset,\n markers = svgDefaultProps.markers,\n colorBy,\n colors,\n defs = svgDefaultProps.defs,\n fill = svgDefaultProps.fill,\n borderRadius = svgDefaultProps.borderRadius,\n borderWidth = svgDefaultProps.borderWidth,\n borderColor,\n annotations = svgDefaultProps.annotations,\n legendLabel,\n tooltipLabel,\n valueFormat,\n isInteractive = svgDefaultProps.isInteractive,\n tooltip = svgDefaultProps.tooltip as BarTooltipComponent<D>,\n onClick,\n onMouseEnter,\n onMouseLeave,\n legends,\n role = svgDefaultProps.role,\n ariaLabel,\n ariaLabelledBy,\n ariaDescribedBy,\n isFocusable = svgDefaultProps.isFocusable,\n barAriaLabel,\n barAriaLabelledBy,\n barAriaDescribedBy,\n barAriaHidden,\n barAriaDisabled,\n initialHiddenIds,\n enableTotals = svgDefaultProps.enableTotals,\n totalsOffset = svgDefaultProps.totalsOffset,\n forwardedRef,\n}: InnerBarProps<D> & {\n forwardedRef: Ref<SVGSVGElement>\n}) => {\n const { animate, config: springConfig } = useMotionConfig()\n const { outerWidth, outerHeight, margin, innerWidth, innerHeight } = useDimensions(\n width,\n height,\n partialMargin\n )\n\n const {\n bars,\n barsWithValue,\n xScale,\n yScale,\n getLabel,\n getTooltipLabel,\n getBorderColor,\n getLabelColor,\n shouldRenderBarLabel,\n toggleSerie,\n legendsWithData,\n barTotals,\n getColor,\n } = useBar<D>({\n indexBy,\n label,\n tooltipLabel,\n valueFormat,\n colors,\n colorBy,\n borderColor,\n labelTextColor,\n groupMode,\n layout,\n data,\n keys,\n margin,\n width: innerWidth,\n height: innerHeight,\n padding,\n innerPadding,\n valueScale,\n indexScale,\n enableLabel,\n labelSkipWidth,\n labelSkipHeight,\n legends,\n legendLabel,\n initialHiddenIds,\n totalsOffset,\n })\n\n const computeLabelLayout = useComputeLabelLayout(\n layout,\n valueScale?.reverse ?? false,\n labelPosition,\n labelOffset\n )\n\n const transition = useTransition<\n ComputedBarDatumWithValue<D>,\n {\n borderColor: string\n color: string\n height: number\n labelColor: string\n labelOpacity: number\n labelX: number\n labelY: number\n opacity: numb