Skip to content

Commit

Permalink
feat(radar): allow customization of legend data (#1786)
Browse files Browse the repository at this point in the history
* fix(radar): improve radar's legend data to be customizable

* fix(radar): add unit test case and stories

* fix(rader): fix bounded legend data generate bug and apply memoizing

* fix(rader): fix bounded legend data to have a color default value
  • Loading branch information
minemanemo committed Sep 29, 2021
1 parent 69aef61 commit 4a4583f
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 9 deletions.
6 changes: 3 additions & 3 deletions packages/radar/src/Radar.tsx
Expand Up @@ -68,7 +68,7 @@ const InnerRadar = <D extends Record<string, unknown>>({
centerY,
angleStep,
curveFactory,
legendData,
boundLegends,
customLayerProps,
} = useRadar<D>({
data,
Expand All @@ -80,6 +80,7 @@ const InnerRadar = <D extends Record<string, unknown>>({
width: innerWidth,
height: innerHeight,
colors,
legends,
})

const layerById: Record<RadarLayerId, ReactNode> = {
Expand Down Expand Up @@ -172,13 +173,12 @@ const InnerRadar = <D extends Record<string, unknown>>({
if (layers.includes('legends')) {
layerById.legends = (
<Fragment key="legends">
{legends.map((legend, i) => (
{boundLegends.map((legend, i) => (
<BoxLegendSvg
key={i}
{...legend}
containerWidth={width}
containerHeight={height}
data={legendData}
/>
))}
</Fragment>
Expand Down
32 changes: 26 additions & 6 deletions packages/radar/src/hooks.ts
Expand Up @@ -3,7 +3,13 @@ import { scaleLinear } from 'd3-scale'
import { useCurveInterpolation, usePropertyAccessor, useValueFormatter } from '@nivo/core'
import { useOrdinalColorScale } from '@nivo/colors'
import { svgDefaultProps } from './props'
import { RadarColorMapping, RadarCommonProps, RadarDataProps, RadarCustomLayerProps } from './types'
import {
RadarColorMapping,
RadarCommonProps,
RadarDataProps,
RadarCustomLayerProps,
BoundLegendProps,
} from './types'

export const useRadar = <D extends Record<string, unknown>>({
data,
Expand All @@ -15,6 +21,7 @@ export const useRadar = <D extends Record<string, unknown>>({
width,
height,
colors = svgDefaultProps.colors,
legends,
}: {
data: RadarDataProps<D>['data']
keys: RadarDataProps<D>['keys']
Expand All @@ -25,6 +32,7 @@ export const useRadar = <D extends Record<string, unknown>>({
width: number
height: number
colors: RadarCommonProps<D>['colors']
legends: RadarCommonProps<D>['legends']
}) => {
const getIndex = usePropertyAccessor<D, string>(indexBy)
const indices = useMemo(() => data.map(getIndex), [data, getIndex])
Expand Down Expand Up @@ -77,11 +85,22 @@ export const useRadar = <D extends Record<string, unknown>>({
[data, keys, indices, colorByKey, centerX, centerY, radiusScale, angleStep]
)

const legendData = keys.map(key => ({
id: key,
label: key,
color: colorByKey[key],
}))
const legendData = useMemo(
() => keys.map(key => ({ id: key, label: key, color: colorByKey[key] })),
[keys, colorByKey]
)

const boundLegends: BoundLegendProps[] = useMemo(
() =>
legends.map(({ data: customData, ...legend }) => {
const boundData = customData?.map(cd => {
const findData = legendData.find(ld => ld.id === cd.id) || {}
return { ...findData, ...cd }
})
return { ...legend, data: boundData || legendData }
}),
[legends, legendData]
)

return {
getIndex,
Expand All @@ -95,6 +114,7 @@ export const useRadar = <D extends Record<string, unknown>>({
angleStep,
curveFactory,
legendData,
boundLegends,
customLayerProps,
}
}
2 changes: 2 additions & 0 deletions packages/radar/src/types.ts
Expand Up @@ -135,3 +135,5 @@ export type RadarSvgProps<D extends Record<string, unknown>> = Partial<RadarComm
RadarDataProps<D> &
Dimensions &
ModernMotionProps

export type BoundLegendProps = Required<Pick<LegendProps, 'data'>> & Omit<LegendProps, 'data'>
19 changes: 19 additions & 0 deletions packages/radar/stories/radar.stories.tsx
Expand Up @@ -144,3 +144,22 @@ const LabelComponent = ({ id, x, y, anchor }: GridLabelProps) => (
)

export const CustomLabelComponent = () => <Radar {...commonProperties} gridLabel={LabelComponent} />

export const CustomLegendLabel = () => (
<Radar
{...commonProperties}
legends={[
{
data: commonProperties.keys.map(key => ({ id: key, label: `${key} base` })),
anchor: 'top-left',
direction: 'column',
itemWidth: 56,
itemHeight: 12,
itemsSpacing: 12,
itemTextColor: '#333',
symbolSize: 6,
symbolShape: 'circle',
},
]}
/>
)
49 changes: 49 additions & 0 deletions packages/radar/tests/Radar.test.tsx
Expand Up @@ -2,6 +2,7 @@ import { mount } from 'enzyme'
// @ts-ignore
import { Radar, RadarSvgProps } from '../src'
import { RadarSliceTooltipProps } from '../dist/types'
import { LegendProps } from '@nivo/legends'

type TestDatum = {
A: number
Expand All @@ -22,6 +23,13 @@ const baseProps: RadarSvgProps<TestDatum> = {
animate: false,
}

const baseLegend: LegendProps = {
anchor: 'top-left',
direction: 'column',
itemWidth: 56,
itemHeight: 24,
}

it('should render a basic radar chart', () => {
const wrapper = mount(<Radar<TestDatum> {...baseProps} />)

Expand Down Expand Up @@ -157,3 +165,44 @@ describe('accessibility', () => {
expect(svg.prop('aria-describedby')).toBe('AriaDescribedBy')
})
})

describe('legend', () => {
it('should show key when legend data is not set', () => {
const { keys } = baseProps
const legends = [baseLegend]
const wrapper = mount(<Radar {...baseProps} legends={legends} />)

expect(wrapper.find('BoxLegendSvg').find('text').at(0).text()).toBe(keys[0])
expect(wrapper.find('BoxLegendSvg').find('text').at(1).text()).toBe(keys[1])
})

it('show custom legend label when legend data size is 1', () => {
const { keys } = baseProps
const customLabels = { A: 'A is good', B: 'B is best' }
const legends = [
{ ...baseLegend, data: keys.map(key => ({ id: key, label: customLabels[key] })) },
]
const wrapper = mount(<Radar {...baseProps} legends={legends} />)

expect(wrapper.find('BoxLegendSvg').find('text').at(0).text()).toBe(customLabels.A)
expect(wrapper.find('BoxLegendSvg').find('text').at(1).text()).toBe(customLabels.B)
})

it('show custom legend label when legend data size is 2 over', () => {
const { keys } = baseProps
const customLabels = [
{ A: 'A - 0', B: 'B - 0' },
{ A: 'A - 1', B: 'B - 0' },
]
const legends = [
{ ...baseLegend, data: keys.map(key => ({ id: key, label: customLabels[0][key] })) },
{ ...baseLegend, data: keys.map(key => ({ id: key, label: customLabels[1][key] })) },
]
const wrapper = mount(<Radar {...baseProps} legends={legends} />)

expect(wrapper.find('BoxLegendSvg').find('text').at(0).text()).toBe(customLabels[0].A)
expect(wrapper.find('BoxLegendSvg').find('text').at(1).text()).toBe(customLabels[0].B)
expect(wrapper.find('BoxLegendSvg').find('text').at(2).text()).toBe(customLabels[1].A)
expect(wrapper.find('BoxLegendSvg').find('text').at(3).text()).toBe(customLabels[1].B)
})
})

0 comments on commit 4a4583f

Please sign in to comment.