From 4a4583f4f3716e9f1e33dbdaa962109301ba52c6 Mon Sep 17 00:00:00 2001 From: minhoe <49668613+minemanemo@users.noreply.github.com> Date: Wed, 29 Sep 2021 23:49:38 +0900 Subject: [PATCH] feat(radar): allow customization of legend data (#1786) * 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 --- packages/radar/src/Radar.tsx | 6 +-- packages/radar/src/hooks.ts | 32 +++++++++++++--- packages/radar/src/types.ts | 2 + packages/radar/stories/radar.stories.tsx | 19 +++++++++ packages/radar/tests/Radar.test.tsx | 49 ++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 9 deletions(-) diff --git a/packages/radar/src/Radar.tsx b/packages/radar/src/Radar.tsx index 629aa844bb..c84ce74488 100644 --- a/packages/radar/src/Radar.tsx +++ b/packages/radar/src/Radar.tsx @@ -68,7 +68,7 @@ const InnerRadar = >({ centerY, angleStep, curveFactory, - legendData, + boundLegends, customLayerProps, } = useRadar({ data, @@ -80,6 +80,7 @@ const InnerRadar = >({ width: innerWidth, height: innerHeight, colors, + legends, }) const layerById: Record = { @@ -172,13 +173,12 @@ const InnerRadar = >({ if (layers.includes('legends')) { layerById.legends = ( - {legends.map((legend, i) => ( + {boundLegends.map((legend, i) => ( ))} diff --git a/packages/radar/src/hooks.ts b/packages/radar/src/hooks.ts index a61ea36cf3..1ce3adda08 100644 --- a/packages/radar/src/hooks.ts +++ b/packages/radar/src/hooks.ts @@ -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 = >({ data, @@ -15,6 +21,7 @@ export const useRadar = >({ width, height, colors = svgDefaultProps.colors, + legends, }: { data: RadarDataProps['data'] keys: RadarDataProps['keys'] @@ -25,6 +32,7 @@ export const useRadar = >({ width: number height: number colors: RadarCommonProps['colors'] + legends: RadarCommonProps['legends'] }) => { const getIndex = usePropertyAccessor(indexBy) const indices = useMemo(() => data.map(getIndex), [data, getIndex]) @@ -77,11 +85,22 @@ export const useRadar = >({ [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, @@ -95,6 +114,7 @@ export const useRadar = >({ angleStep, curveFactory, legendData, + boundLegends, customLayerProps, } } diff --git a/packages/radar/src/types.ts b/packages/radar/src/types.ts index 0d4420b761..4215cd55a2 100644 --- a/packages/radar/src/types.ts +++ b/packages/radar/src/types.ts @@ -135,3 +135,5 @@ export type RadarSvgProps> = Partial & Dimensions & ModernMotionProps + +export type BoundLegendProps = Required> & Omit diff --git a/packages/radar/stories/radar.stories.tsx b/packages/radar/stories/radar.stories.tsx index a55b0e9165..5e910ded1c 100644 --- a/packages/radar/stories/radar.stories.tsx +++ b/packages/radar/stories/radar.stories.tsx @@ -144,3 +144,22 @@ const LabelComponent = ({ id, x, y, anchor }: GridLabelProps) => ( ) export const CustomLabelComponent = () => + +export const CustomLegendLabel = () => ( + ({ id: key, label: `${key} base` })), + anchor: 'top-left', + direction: 'column', + itemWidth: 56, + itemHeight: 12, + itemsSpacing: 12, + itemTextColor: '#333', + symbolSize: 6, + symbolShape: 'circle', + }, + ]} + /> +) diff --git a/packages/radar/tests/Radar.test.tsx b/packages/radar/tests/Radar.test.tsx index fccac97bfe..82deb856f3 100644 --- a/packages/radar/tests/Radar.test.tsx +++ b/packages/radar/tests/Radar.test.tsx @@ -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 @@ -22,6 +23,13 @@ const baseProps: RadarSvgProps = { animate: false, } +const baseLegend: LegendProps = { + anchor: 'top-left', + direction: 'column', + itemWidth: 56, + itemHeight: 24, +} + it('should render a basic radar chart', () => { const wrapper = mount( {...baseProps} />) @@ -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() + + 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() + + 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() + + 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) + }) +})