Skip to content

Commit ad7f13b

Browse files
committed
feat(xychart): allows different radius configurations to be specified for each bar
1 parent 8673817 commit ad7f13b

8 files changed

Lines changed: 94 additions & 49 deletions

File tree

packages/visx-xychart/src/components/series/AnimatedBarSeries.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,27 @@ export default function AnimatedBarSeries<
77
XScale extends AxisScale,
88
YScale extends AxisScale,
99
Datum extends object,
10-
>({ colorAccessor, ...props }: Omit<BaseBarSeriesProps<XScale, YScale, Datum>, 'BarsComponent'>) {
10+
>({
11+
colorAccessor,
12+
radius,
13+
radiusAll,
14+
radiusTop,
15+
radiusRight,
16+
radiusBottom,
17+
radiusLeft,
18+
...props
19+
}: Omit<BaseBarSeriesProps<XScale, YScale, Datum>, 'BarsComponent'>) {
1120
return (
1221
<BaseBarSeries<XScale, YScale, Datum>
1322
{...props}
1423
// @TODO currently generics for non-SeriesProps are not passed correctly in
1524
// withRegisteredData HOC
1625
colorAccessor={colorAccessor as BaseBarSeriesProps<XScale, YScale, object>['colorAccessor']}
26+
radius={radius as BaseBarSeriesProps<XScale, YScale, object>['radius']}
27+
radiusAll={radiusAll as BaseBarSeriesProps<XScale, YScale, object>['radiusAll']}
28+
radiusTop={radiusTop as BaseBarSeriesProps<XScale, YScale, object>['radiusTop']}
29+
radiusRight={radiusRight as BaseBarSeriesProps<XScale, YScale, object>['radiusRight']}
30+
radiusBottom={radiusBottom as BaseBarSeriesProps<XScale, YScale, object>['radiusBottom']}
1731
BarsComponent={AnimatedBars}
1832
/>
1933
);

packages/visx-xychart/src/components/series/BarSeries.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import Bars from './private/Bars';
55

66
function BarSeries<XScale extends AxisScale, YScale extends AxisScale, Datum extends object>({
77
colorAccessor,
8+
radius,
9+
radiusAll,
10+
radiusTop,
11+
radiusRight,
12+
radiusBottom,
13+
radiusLeft,
814
...props
915
}: Omit<BaseBarSeriesProps<XScale, YScale, Datum>, 'BarsComponent'>) {
1016
return (
@@ -13,6 +19,11 @@ function BarSeries<XScale extends AxisScale, YScale extends AxisScale, Datum ext
1319
// @TODO currently generics for non-SeriesProps are not passed correctly in
1420
// withRegisteredData HOC
1521
colorAccessor={colorAccessor as BaseBarSeriesProps<XScale, YScale, object>['colorAccessor']}
22+
radius={radius as BaseBarSeriesProps<XScale, YScale, object>['radius']}
23+
radiusAll={radiusAll as BaseBarSeriesProps<XScale, YScale, object>['radiusAll']}
24+
radiusTop={radiusTop as BaseBarSeriesProps<XScale, YScale, object>['radiusTop']}
25+
radiusRight={radiusRight as BaseBarSeriesProps<XScale, YScale, object>['radiusRight']}
26+
radiusBottom={radiusBottom as BaseBarSeriesProps<XScale, YScale, object>['radiusBottom']}
1627
BarsComponent={Bars}
1728
/>
1829
);

packages/visx-xychart/src/components/series/private/AnimatedBars.tsx

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { cleanColor, colorHasUrl } from '../../../utils/cleanColorString';
77
import getScaleBaseline from '../../../utils/getScaleBaseline';
88
import AnimatedPath from './AnimatedPath';
99

10-
function enterUpdate({ x, y, width, height, fill }: Bar) {
10+
function enterUpdate<Datum extends object>({ x, y, width, height, fill }: Bar<Datum>) {
1111
return {
1212
x,
1313
y,
@@ -23,15 +23,15 @@ type BarTransitionConfig<Scale extends AxisScale> = {
2323
horizontal?: boolean;
2424
};
2525

26-
function useBarTransitionConfig<Scale extends AxisScale>({
26+
function useBarTransitionConfig<Scale extends AxisScale, Datum extends object>({
2727
scale,
2828
horizontal,
2929
}: BarTransitionConfig<Scale>) {
3030
const shouldAnimateX = !!horizontal;
3131
return useMemo(() => {
3232
const scaleBaseline = getScaleBaseline(scale);
3333

34-
function fromLeave({ x, y, width, height, fill }: Bar) {
34+
function fromLeave({ x, y, width, height, fill }: Bar<Datum>) {
3535
return {
3636
x: shouldAnimateX ? scaleBaseline ?? 0 : x,
3737
y: shouldAnimateX ? y : scaleBaseline ?? 0,
@@ -48,12 +48,16 @@ function useBarTransitionConfig<Scale extends AxisScale>({
4848
leave: fromLeave,
4949
enter: enterUpdate,
5050
update: enterUpdate,
51-
keys: (bar: Bar) => bar.key,
51+
keys: (bar: Bar<Datum>) => bar.key,
5252
};
5353
}, [scale, shouldAnimateX]);
5454
}
5555

56-
function AnimatedBarsRounded<XScale extends AxisScale, YScale extends AxisScale>({
56+
function AnimatedBarsRounded<
57+
XScale extends AxisScale,
58+
YScale extends AxisScale,
59+
Datum extends object,
60+
>({
5761
bars,
5862
xScale,
5963
yScale,
@@ -65,29 +69,30 @@ function AnimatedBarsRounded<XScale extends AxisScale, YScale extends AxisScale>
6569
radiusBottom,
6670
radiusLeft,
6771
...pathProps
68-
}: BarsProps<XScale, YScale> & { radius: number }) {
72+
}: Omit<BarsProps<XScale, YScale, Datum>, 'radius'> &
73+
Required<Pick<BarsProps<XScale, YScale, Datum>, 'radius'>>) {
6974
return (
7075
// eslint-disable-next-line react/jsx-no-useless-fragment
7176
<>
72-
{bars.map(({ key, fill, x, y, width, height }) => (
77+
{bars.map(({ key, ...barProps }) => (
7378
<BarRounded
7479
key={key}
75-
x={x}
76-
y={y}
77-
width={width}
78-
height={height}
79-
radius={radius}
80-
all={radiusAll}
81-
top={radiusTop}
82-
right={radiusRight}
83-
bottom={radiusBottom}
84-
left={radiusLeft}
80+
x={barProps.x}
81+
y={barProps.y}
82+
width={barProps.width}
83+
height={barProps.height}
84+
radius={typeof radius === 'function' ? radius(barProps) : radius}
85+
all={typeof radiusAll === 'function' ? radiusAll(barProps) : radiusAll}
86+
top={typeof radiusTop === 'function' ? radiusTop(barProps) : radiusTop}
87+
right={typeof radiusRight === 'function' ? radiusRight(barProps) : radiusRight}
88+
bottom={typeof radiusBottom === 'function' ? radiusBottom(barProps) : radiusBottom}
89+
left={typeof radiusLeft === 'function' ? radiusLeft(barProps) : radiusLeft}
8590
>
8691
{({ path }) => (
8792
<AnimatedPath
8893
className="visx-bar visx-bar-rounded"
8994
d={path}
90-
fill={fill}
95+
fill={barProps.fill}
9196
{...pathProps}
9297
/>
9398
)}
@@ -97,7 +102,11 @@ function AnimatedBarsRounded<XScale extends AxisScale, YScale extends AxisScale>
97102
);
98103
}
99104

100-
function AnimatedBarsUnrounded<XScale extends AxisScale, YScale extends AxisScale>({
105+
function AnimatedBarsUnrounded<
106+
XScale extends AxisScale,
107+
YScale extends AxisScale,
108+
Datum extends object,
109+
>({
101110
bars,
102111
xScale,
103112
yScale,
@@ -109,7 +118,7 @@ function AnimatedBarsUnrounded<XScale extends AxisScale, YScale extends AxisScal
109118
radiusBottom,
110119
radiusLeft,
111120
...rectProps
112-
}: BarsProps<XScale, YScale>) {
121+
}: BarsProps<XScale, YScale, Datum>) {
113122
const animatedBars = useTransition(bars, {
114123
...useBarTransitionConfig({ horizontal, scale: horizontal ? xScale : yScale }),
115124
});
@@ -145,9 +154,11 @@ function AnimatedBarsUnrounded<XScale extends AxisScale, YScale extends AxisScal
145154
}
146155

147156
/** Wrapper component which renders a Bars component depending on whether it needs rounded corners. */
148-
export default function AnimatedBars<XScale extends AxisScale, YScale extends AxisScale>(
149-
props: BarsProps<XScale, YScale>,
150-
) {
157+
export default function AnimatedBars<
158+
XScale extends AxisScale,
159+
YScale extends AxisScale,
160+
Datum extends object,
161+
>(props: BarsProps<XScale, YScale, Datum>) {
151162
return props.radius == null ? (
152163
<AnimatedBarsUnrounded {...props} />
153164
) : (

packages/visx-xychart/src/components/series/private/Bars.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { BarRounded } from '@visx/shape';
44
import React from 'react';
55
import { BarsProps } from '../../../types';
66

7-
export default function Bars({
7+
export default function Bars<Datum extends object>({
88
bars,
99
horizontal,
1010
xScale,
@@ -16,7 +16,7 @@ export default function Bars({
1616
radiusBottom,
1717
radiusLeft,
1818
...restProps
19-
}: BarsProps<AxisScale, AxisScale>) {
19+
}: BarsProps<AxisScale, AxisScale, Datum>) {
2020
const isFocusable = Boolean(restProps.onFocus || restProps.onBlur);
2121
return (
2222
<>
@@ -34,12 +34,12 @@ export default function Bars({
3434
key={key}
3535
className="visx-bar"
3636
tabIndex={isFocusable ? 0 : undefined}
37-
radius={radius}
38-
all={radiusAll}
39-
top={radiusTop}
40-
right={radiusRight}
41-
bottom={radiusBottom}
42-
left={radiusLeft}
37+
radius={typeof radius === 'function' ? radius(barProps) : radius}
38+
all={typeof radiusAll === 'function' ? radiusAll(barProps) : radiusAll}
39+
top={typeof radiusTop === 'function' ? radiusTop(barProps) : radiusTop}
40+
right={typeof radiusRight === 'function' ? radiusRight(barProps) : radiusRight}
41+
bottom={typeof radiusBottom === 'function' ? radiusBottom(barProps) : radiusBottom}
42+
left={typeof radiusLeft === 'function' ? radiusLeft(barProps) : radiusLeft}
4343
{...barProps}
4444
{...restProps}
4545
/>

packages/visx-xychart/src/components/series/private/BaseBarGroup.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export type BaseBarGroupProps<
3131
/** Comparator function to sort `dataKeys` within a bar group. By default the DOM rendering order of `BarGroup`s `children` is used. */
3232
sortBars?: (dataKeyA: string, dataKeyB: string) => number;
3333
/** Rendered component which is passed BarsProps by BaseBarGroup after processing. */
34-
BarsComponent: React.FC<BarsProps<XScale, YScale>>;
34+
BarsComponent: React.FC<BarsProps<XScale, YScale, Datum>>;
3535
} & Pick<
3636
SeriesProps<XScale, YScale, Datum>,
3737
| 'onPointerMove'
@@ -186,7 +186,7 @@ export default function BaseBarGroup<
186186
fill: colorAccessor?.(bar, index) ?? colorScale(key),
187187
};
188188
})
189-
.filter((bar) => bar) as Bar[],
189+
.filter((bar) => bar) as Bar<Datum>[],
190190
};
191191
});
192192

packages/visx-xychart/src/components/series/private/BaseBarSeries.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export type BaseBarSeriesProps<
1616
Datum extends object,
1717
> = SeriesProps<XScale, YScale, Datum> & {
1818
/** Rendered component which is passed BarsProps by BaseBarSeries after processing. */
19-
BarsComponent: React.FC<BarsProps<XScale, YScale>>;
19+
BarsComponent: React.FC<BarsProps<XScale, YScale, Datum>>;
2020
/**
2121
* Specify bar padding when bar thickness does not come from a `band` scale.
2222
* Accepted values are [0, 1], 0 = no padding, 1 = no bar, defaults to 0.1.
@@ -25,7 +25,7 @@ export type BaseBarSeriesProps<
2525
/** Given a Datum, returns its color. Falls back to theme color if unspecified or if a null-ish value is returned. */
2626
colorAccessor?: (d: Datum, index: number) => string | null | undefined;
2727
} & Pick<
28-
BarsProps<XScale, YScale>,
28+
BarsProps<XScale, YScale, Datum>,
2929
'radius' | 'radiusAll' | 'radiusTop' | 'radiusRight' | 'radiusBottom' | 'radiusLeft'
3030
>;
3131

@@ -85,6 +85,7 @@ function BaseBarSeries<XScale extends AxisScale, YScale extends AxisScale, Datum
8585
if (!isValidNumber(barLength)) return null;
8686

8787
return {
88+
datum,
8889
key: `${index}`,
8990
x: horizontal ? xZeroPosition + Math.min(0, barLength) : x,
9091
y: horizontal ? y : yZeroPosition + Math.min(0, barLength),
@@ -93,7 +94,7 @@ function BaseBarSeries<XScale extends AxisScale, YScale extends AxisScale, Datum
9394
fill: colorAccessor?.(datum, index) ?? color,
9495
};
9596
})
96-
.filter((bar) => bar) as Bar[];
97+
.filter((bar) => bar) as Bar<Datum>[];
9798
}, [
9899
barThickness,
99100
color,

packages/visx-xychart/src/components/series/private/BaseBarStack.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export type BaseBarStackProps<
3939
| React.ReactElement<BarStackChildProps<XScale, YScale, Datum>>
4040
| React.ReactElement<BarStackChildProps<XScale, YScale, Datum>>[];
4141
/** Rendered component which is passed BarsProps by BaseBarStack after processing. */
42-
BarsComponent: React.FC<BarsProps<XScale, YScale>>;
42+
BarsComponent: React.FC<BarsProps<XScale, YScale, Datum>>;
4343
} & Pick<StackPathConfig<Datum, string>, 'offset' | 'order'> &
4444
Pick<
4545
SeriesProps<XScale, YScale, Datum>,
@@ -192,7 +192,7 @@ function BaseBarStack<
192192
: colorScale(barStack.key),
193193
};
194194
})
195-
.filter((bar) => bar) as Bar[],
195+
.filter((bar) => bar) as Bar<Datum>[],
196196
};
197197
})
198198
.filter((series) => series);

packages/visx-xychart/src/types/series.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ export type SeriesProps<
117117
};
118118

119119
/** Bar shape. */
120-
export type Bar = {
120+
export type Bar<Datum extends object> = {
121+
/** Datum being represented. */
122+
datum: Datum;
121123
/** Unique key for Bar (not dataKey). */
122124
key: string;
123125
/** X coordinate of Bar. */
@@ -132,27 +134,33 @@ export type Bar = {
132134
fill?: string;
133135
};
134136

137+
/** Given Bar props, returns the radius size of the bar. */
138+
export type BarsRadiusSizeFunc<Datum extends object> = (bar: Omit<Bar<Datum>, 'key'>) => number;
139+
140+
/** Given Bar props, returns true if the radius should be applied, otherwise false. */
141+
export type BarsApplyRadiusFunc<Datum extends object> = (bar: Omit<Bar<Datum>, 'key'>) => boolean;
142+
135143
/** Props for base Bars components */
136-
export type BarsProps<XScale extends AxisScale, YScale extends AxisScale> = {
137-
bars: Bar[];
144+
export type BarsProps<XScale extends AxisScale, YScale extends AxisScale, Datum extends object> = {
145+
bars: Bar<Datum>[];
138146
xScale: XScale;
139147
yScale: YScale;
140148
horizontal?: boolean;
141149
/** Optional radius to apply to bar corners. */
142-
radius?: number;
150+
radius?: number | BarsRadiusSizeFunc<Datum>;
143151
/** Whether to apply radius to all corners. */
144-
radiusAll?: boolean;
152+
radiusAll?: boolean | BarsApplyRadiusFunc<Datum>;
145153
/** Whether to apply radius to top corners. */
146-
radiusTop?: boolean;
154+
radiusTop?: boolean | BarsApplyRadiusFunc<Datum>;
147155
/** Whether to apply radius to right corners. */
148-
radiusRight?: boolean;
156+
radiusRight?: boolean | BarsApplyRadiusFunc<Datum>;
149157
/** Whether to apply radius to bottom corners. */
150-
radiusBottom?: boolean;
158+
radiusBottom?: boolean | BarsApplyRadiusFunc<Datum>;
151159
/** Whether to apply radius to left corners. */
152-
radiusLeft?: boolean;
160+
radiusLeft?: boolean | BarsApplyRadiusFunc<Datum>;
153161
} & Omit<
154162
SVGProps<SVGRectElement | SVGPathElement>,
155-
'x' | 'y' | 'width' | 'height' | 'ref' | 'children'
163+
'x' | 'y' | 'width' | 'height' | 'ref' | 'children' | 'radius'
156164
>;
157165

158166
// BarStack transforms its child series Datum into CombinedData<XScale, YScale>

0 commit comments

Comments
 (0)