Skip to content

Commit d57345a

Browse files
authoredMay 10, 2022
feat(radar): add support for global rotation to the radar chart (#1985)
* implementing global rotatation in radar * rename prop from 'angle' to 'rotation' + test for global rotation * reformat rotation test * format of a file in package 'line' (required for all-package lint CI)
1 parent c40006f commit d57345a

12 files changed

+79
-22
lines changed
 

‎packages/line/src/Points.js

+15-12
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,22 @@ const Points = ({ points, symbol, size, borderWidth, enableLabel, label, labelYO
1818
* We reverse the `points` array so that points from the lower lines in stacked lines
1919
* graph are drawn on top. See https://github.com/plouc/nivo/issues/1051.
2020
*/
21-
const mappedPoints = points.slice(0).reverse().map(point => {
22-
const mappedPoint = {
23-
id: point.id,
24-
x: point.x,
25-
y: point.y,
26-
datum: point.data,
27-
fill: point.color,
28-
stroke: point.borderColor,
29-
label: enableLabel ? getLabel(point.data) : null,
30-
}
21+
const mappedPoints = points
22+
.slice(0)
23+
.reverse()
24+
.map(point => {
25+
const mappedPoint = {
26+
id: point.id,
27+
x: point.x,
28+
y: point.y,
29+
datum: point.data,
30+
fill: point.color,
31+
stroke: point.borderColor,
32+
label: enableLabel ? getLabel(point.data) : null,
33+
}
3134

32-
return mappedPoint
33-
})
35+
return mappedPoint
36+
})
3437

3538
return (
3639
<g>

‎packages/radar/src/Radar.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const InnerRadar = <D extends Record<string, unknown>>({
1919
keys,
2020
indexBy,
2121
layers = svgDefaultProps.layers,
22+
rotation: rotationDegrees = svgDefaultProps.rotation,
2223
maxValue = svgDefaultProps.maxValue,
2324
valueFormat,
2425
curve = svgDefaultProps.curve,
@@ -66,6 +67,7 @@ const InnerRadar = <D extends Record<string, unknown>>({
6667
colorByKey,
6768
fillByKey,
6869
boundDefs,
70+
rotation,
6971
radius,
7072
radiusScale,
7173
centerX,
@@ -78,6 +80,7 @@ const InnerRadar = <D extends Record<string, unknown>>({
7880
data,
7981
keys,
8082
indexBy,
83+
rotationDegrees,
8184
maxValue,
8285
valueFormat,
8386
curve,
@@ -104,6 +107,7 @@ const InnerRadar = <D extends Record<string, unknown>>({
104107
levels={gridLevels}
105108
shape={gridShape}
106109
radius={radius}
110+
rotation={rotation}
107111
angleStep={angleStep}
108112
indices={indices}
109113
label={gridLabel}
@@ -124,6 +128,7 @@ const InnerRadar = <D extends Record<string, unknown>>({
124128
colorByKey={colorByKey}
125129
fillByKey={fillByKey}
126130
radiusScale={radiusScale}
131+
rotation={rotation}
127132
angleStep={angleStep}
128133
curveFactory={curveFactory}
129134
borderWidth={borderWidth}
@@ -146,6 +151,7 @@ const InnerRadar = <D extends Record<string, unknown>>({
146151
formatValue={formatValue}
147152
colorByKey={colorByKey}
148153
radius={radius}
154+
rotation={rotation}
149155
angleStep={angleStep}
150156
tooltip={sliceTooltip}
151157
/>
@@ -161,6 +167,7 @@ const InnerRadar = <D extends Record<string, unknown>>({
161167
keys={keys}
162168
getIndex={getIndex}
163169
radiusScale={radiusScale}
170+
rotation={rotation}
164171
angleStep={angleStep}
165172
symbol={dotSymbol}
166173
size={dotSize}

‎packages/radar/src/RadarDots.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface RadarDotsProps<D extends Record<string, unknown>> {
1010
radiusScale: ScaleLinear<number, number>
1111
getIndex: (d: D) => string
1212
colorByKey: RadarColorMapping
13+
rotation: number
1314
angleStep: number
1415
symbol?: RadarCommonProps<D>['dotSymbol']
1516
size: number
@@ -28,6 +29,7 @@ export const RadarDots = <D extends Record<string, unknown>>({
2829
getIndex,
2930
colorByKey,
3031
radiusScale,
32+
rotation,
3133
angleStep,
3234
symbol,
3335
size = 6,
@@ -66,7 +68,7 @@ export const RadarDots = <D extends Record<string, unknown>>({
6668
fill: fillColor(pointData),
6769
stroke: strokeColor(pointData),
6870
...positionFromAngle(
69-
angleStep * i - Math.PI / 2,
71+
rotation + angleStep * i - Math.PI / 2,
7072
radiusScale(datum[key] as number)
7173
),
7274
},
@@ -86,6 +88,7 @@ export const RadarDots = <D extends Record<string, unknown>>({
8688
formatValue,
8789
fillColor,
8890
strokeColor,
91+
rotation,
8992
angleStep,
9093
radiusScale,
9194
]

‎packages/radar/src/RadarGrid.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface RadarGridProps<D extends Record<string, unknown>> {
99
shape: RadarCommonProps<D>['gridShape']
1010
radius: number
1111
levels: number
12+
rotation: number
1213
angleStep: number
1314
label: GridLabelComponent
1415
labelOffset: number
@@ -19,6 +20,7 @@ export const RadarGrid = <D extends Record<string, unknown>>({
1920
levels,
2021
shape,
2122
radius,
23+
rotation,
2224
angleStep,
2325
label,
2426
labelOffset,
@@ -29,9 +31,11 @@ export const RadarGrid = <D extends Record<string, unknown>>({
2931
radii: Array.from({ length: levels })
3032
.map((_, i) => (radius / levels) * (i + 1))
3133
.reverse(),
32-
angles: Array.from({ length: indices.length }, (_, i) => i * angleStep - Math.PI / 2),
34+
angles: Array.from({ length: indices.length }).map(
35+
(_, i) => rotation + i * angleStep - Math.PI / 2
36+
),
3337
}
34-
}, [indices, levels, radius, angleStep])
38+
}, [indices, levels, radius, rotation, angleStep])
3539

3640
return (
3741
<>
@@ -53,6 +57,7 @@ export const RadarGrid = <D extends Record<string, unknown>>({
5357
key={`level.${i}`}
5458
shape={shape}
5559
radius={radius}
60+
rotation={rotation}
5661
angleStep={angleStep}
5762
dataLength={indices.length}
5863
/>

‎packages/radar/src/RadarGridLevels.tsx

+10-3
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,26 @@ const RadarGridLevelCircular = memo(({ radius }: RadarGridLevelCircularProps) =>
2929

3030
interface RadarGridLevelLinearProps {
3131
radius: number
32+
rotation: number
3233
angleStep: number
3334
dataLength: number
3435
}
3536

36-
const RadarGridLevelLinear = ({ radius, angleStep, dataLength }: RadarGridLevelLinearProps) => {
37+
const RadarGridLevelLinear = ({
38+
radius,
39+
rotation,
40+
angleStep,
41+
dataLength,
42+
}: RadarGridLevelLinearProps) => {
3743
const theme = useTheme()
3844

3945
const radarLineGenerator = useMemo(
4046
() =>
4147
lineRadial<number>()
42-
.angle(i => i * angleStep)
48+
.angle(i => rotation + i * angleStep)
4349
.radius(radius)
4450
.curve(curveLinearClosed),
45-
[angleStep, radius]
51+
[rotation, angleStep, radius]
4652
)
4753

4854
const points = Array.from({ length: dataLength }, (_, i) => i)
@@ -60,6 +66,7 @@ const RadarGridLevelLinear = ({ radius, angleStep, dataLength }: RadarGridLevelL
6066
interface RadarGridLevelsProps<D extends Record<string, unknown>> {
6167
shape: RadarCommonProps<D>['gridShape']
6268
radius: number
69+
rotation: number
6370
angleStep: number
6471
dataLength: number
6572
}

‎packages/radar/src/RadarLayer.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ interface RadarLayerProps<D extends Record<string, unknown>> {
1212
colorByKey: Record<string | number, string>
1313
fillByKey: Record<string, string | null>
1414
radiusScale: ScaleLinear<number, number>
15+
rotation: number
1516
angleStep: number
1617
curveFactory: CurveFactory
1718
borderWidth: RadarCommonProps<D>['borderWidth']
@@ -26,6 +27,7 @@ export const RadarLayer = <D extends Record<string, unknown>>({
2627
colorByKey,
2728
fillByKey,
2829
radiusScale,
30+
rotation,
2931
angleStep,
3032
curveFactory,
3133
borderWidth,
@@ -39,9 +41,9 @@ export const RadarLayer = <D extends Record<string, unknown>>({
3941
const lineGenerator = useMemo(() => {
4042
return lineRadial<number>()
4143
.radius(d => radiusScale(d))
42-
.angle((_, i) => i * angleStep)
44+
.angle((_, i) => rotation + i * angleStep)
4345
.curve(curveFactory)
44-
}, [radiusScale, angleStep, curveFactory])
46+
}, [radiusScale, rotation, angleStep, curveFactory])
4547

4648
const { animate, config: springConfig } = useMotionConfig()
4749
const animatedPath = useAnimatedPath(lineGenerator(data.map(d => d[key] as number)) as string)

‎packages/radar/src/RadarSlices.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface RadarSlicesProps<D extends Record<string, unknown>> {
99
formatValue: (value: number, context: string) => string
1010
colorByKey: RadarColorMapping
1111
radius: number
12+
rotation: number
1213
angleStep: number
1314
tooltip: RadarCommonProps<D>['sliceTooltip']
1415
}
@@ -20,13 +21,14 @@ export const RadarSlices = <D extends Record<string, unknown>>({
2021
formatValue,
2122
colorByKey,
2223
radius,
24+
rotation,
2325
angleStep,
2426
tooltip,
2527
}: RadarSlicesProps<D>) => {
2628
const arc = d3Arc<{ startAngle: number; endAngle: number }>().outerRadius(radius).innerRadius(0)
2729

2830
const halfAngleStep = angleStep * 0.5
29-
let rootStartAngle = -halfAngleStep
31+
let rootStartAngle = rotation - halfAngleStep
3032

3133
return (
3234
<>

‎packages/radar/src/hooks.ts

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
usePropertyAccessor,
88
useValueFormatter,
99
} from '@nivo/core'
10+
import { degreesToRadians } from '@nivo/core'
1011
import { useOrdinalColorScale } from '@nivo/colors'
1112
import { svgDefaultProps } from './props'
1213
import {
@@ -22,6 +23,7 @@ export const useRadar = <D extends Record<string, unknown>>({
2223
data,
2324
keys,
2425
indexBy,
26+
rotationDegrees,
2527
maxValue,
2628
valueFormat,
2729
curve,
@@ -35,6 +37,7 @@ export const useRadar = <D extends Record<string, unknown>>({
3537
data: RadarDataProps<D>['data']
3638
keys: RadarDataProps<D>['keys']
3739
indexBy: RadarDataProps<D>['indexBy']
40+
rotationDegrees: RadarCommonProps<D>['rotation']
3841
maxValue: RadarCommonProps<D>['maxValue']
3942
valueFormat?: RadarCommonProps<D>['valueFormat']
4043
curve: RadarCommonProps<D>['curve']
@@ -48,6 +51,7 @@ export const useRadar = <D extends Record<string, unknown>>({
4851
const getIndex = usePropertyAccessor<D, string>(indexBy)
4952
const indices = useMemo(() => data.map(getIndex), [data, getIndex])
5053
const formatValue = useValueFormatter<number, string>(valueFormat)
54+
const rotation = degreesToRadians(rotationDegrees)
5155

5256
const getColor = useOrdinalColorScale<{ key: string; index: number }>(colors, 'key')
5357
const colorByKey: RadarColorMapping = useMemo(
@@ -133,6 +137,7 @@ export const useRadar = <D extends Record<string, unknown>>({
133137
colorByKey,
134138
fillByKey,
135139
boundDefs,
140+
rotation,
136141
radius,
137142
radiusScale,
138143
centerX,

‎packages/radar/src/props.ts

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export const svgDefaultProps = {
77

88
maxValue: 'auto' as const,
99

10+
rotation: 0,
11+
1012
curve: 'linearClosed' as const,
1113

1214
borderWidth: 2,

‎packages/radar/src/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ export interface RadarCommonProps<D extends Record<string, unknown>> {
9090
// second argument passed to the formatter is the key
9191
valueFormat: ValueFormat<number, string>
9292

93+
rotation: number
94+
9395
layers: (RadarLayerId | RadarCustomLayer<D>)[]
9496

9597
margin: Box

‎packages/radar/stories/radar.stories.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default {
1414
const commonProperties = {
1515
width: 900,
1616
height: 500,
17-
margin: { top: 60, right: 80, bottom: 20, left: 80 },
17+
margin: { top: 60, right: 80, bottom: 30, left: 80 },
1818
...generateWinesTastes(),
1919
indexBy: 'taste',
2020
animate: true,
@@ -195,3 +195,5 @@ export const WithPatterns = () => (
195195
]}
196196
/>
197197
)
198+
199+
export const WithRotation = () => <Radar {...commonProperties} rotation={36} />

‎packages/radar/tests/Radar.test.tsx

+17
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,23 @@ it('should render a basic radar chart', () => {
4646
expect(layer1path.prop('fill')).toBe('rgba(244, 117, 96, 1)')
4747
})
4848

49+
describe('layout', () => {
50+
it('should support global rotation', () => {
51+
const wrapperA = mount(<Radar<TestDatum> {...baseProps} rotation={90} />)
52+
const wrapperB = mount(<Radar<TestDatum> {...baseProps} rotation={-90} />)
53+
// the two first labels in the two components should have the same text content
54+
const labelA0 = wrapperA.find('RadarGridLabels').at(0)
55+
const labelB0 = wrapperB.find('RadarGridLabels').at(0)
56+
// but positions should be opposite each other on the x axis, equal position on y axis
57+
const getPos = (transformString: string) =>
58+
transformString.replace('translate(', '').replace(')', '').split(', ')
59+
const posA0 = getPos(labelA0.find('g').first().prop('transform') as string)
60+
const posB0 = getPos(labelB0.find('g').first().prop('transform') as string)
61+
expect(Number(posB0[0])).toBeCloseTo(-Number(posA0[0]), 4)
62+
expect(Number(posB0[1])).toBeCloseTo(Number(posA0[1]), 4)
63+
})
64+
})
65+
4966
describe('data', () => {
5067
it('should support value formatting', () => {
5168
const wrapper = mount(

0 commit comments

Comments
 (0)
Please sign in to comment.