From d09c05bd10899f9a1dec507d0fc3a90ede8c8dea Mon Sep 17 00:00:00 2001 From: Jean Date: Fri, 31 May 2024 17:08:55 +0200 Subject: [PATCH 1/6] feat(radar): add onClick handler --- packages/radar/src/Radar.tsx | 2 ++ packages/radar/src/RadarGrid.tsx | 2 +- packages/radar/src/RadarLayer.tsx | 3 ++- packages/radar/src/RadarSlice.tsx | 10 +++++++- packages/radar/src/RadarSlices.tsx | 5 +++- packages/radar/src/types.ts | 29 ++++++++++++++++++++++- storybook/stories/radar/Radar.stories.tsx | 11 +++++++++ 7 files changed, 57 insertions(+), 5 deletions(-) diff --git a/packages/radar/src/Radar.tsx b/packages/radar/src/Radar.tsx index dd47b47400..82c4112b0e 100644 --- a/packages/radar/src/Radar.tsx +++ b/packages/radar/src/Radar.tsx @@ -53,6 +53,7 @@ const InnerRadar = >({ ariaDescribedBy, defs = svgDefaultProps.defs, fill = svgDefaultProps.fill, + onClick, }: InnerRadarProps) => { const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions( width, @@ -154,6 +155,7 @@ const InnerRadar = >({ rotation={rotation} angleStep={angleStep} tooltip={sliceTooltip} + onClick={onClick} /> ) diff --git a/packages/radar/src/RadarGrid.tsx b/packages/radar/src/RadarGrid.tsx index c814cc4bf1..b3a8c490c6 100644 --- a/packages/radar/src/RadarGrid.tsx +++ b/packages/radar/src/RadarGrid.tsx @@ -2,7 +2,7 @@ import { SVGProps, useMemo } from 'react' import { positionFromAngle, useTheme } from '@nivo/core' import { RadarGridLabels } from './RadarGridLabels' import { RadarGridLevels } from './RadarGridLevels' -import { GridLabelComponent, RadarCommonProps } from './types' +import { ComputedDatum, GridLabelComponent, RadarCommonProps, RadarSvgProps } from './types' interface RadarGridProps> { indices: string[] diff --git a/packages/radar/src/RadarLayer.tsx b/packages/radar/src/RadarLayer.tsx index e6b1acf23d..d01499268e 100644 --- a/packages/radar/src/RadarLayer.tsx +++ b/packages/radar/src/RadarLayer.tsx @@ -4,7 +4,7 @@ import { lineRadial, CurveFactory } from 'd3-shape' import { ScaleLinear } from 'd3-scale' import { useMotionConfig, useTheme, useAnimatedPath } from '@nivo/core' import { useInheritedColor } from '@nivo/colors' -import { RadarCommonProps } from './types' +import { RadarCommonProps, RadarSvgProps } from './types' interface RadarLayerProps> { data: D[] @@ -19,6 +19,7 @@ interface RadarLayerProps> { borderColor: RadarCommonProps['borderColor'] fillOpacity: RadarCommonProps['fillOpacity'] blendMode: RadarCommonProps['blendMode'] + onClick?: RadarSvgProps['onClick'] } export const RadarLayer = >({ diff --git a/packages/radar/src/RadarSlice.tsx b/packages/radar/src/RadarSlice.tsx index 19a3c6ffd4..d42d148258 100644 --- a/packages/radar/src/RadarSlice.tsx +++ b/packages/radar/src/RadarSlice.tsx @@ -2,7 +2,7 @@ import { useMemo, useState, useCallback, createElement, MouseEvent } from 'react import { Arc } from 'd3-shape' import { positionFromAngle, useTheme } from '@nivo/core' import { useTooltip } from '@nivo/tooltip' -import { RadarCommonProps, RadarDataProps, RadarSliceTooltipDatum } from './types' +import { RadarCommonProps, RadarDataProps, RadarSliceTooltipDatum, RadarSvgProps } from './types' interface RadarSliceProps> { datum: D @@ -15,6 +15,7 @@ interface RadarSliceProps> { radius: number arcGenerator: Arc tooltip: RadarCommonProps['sliceTooltip'] + onClick?: RadarSvgProps['onClick'] } export const RadarSlice = >({ @@ -28,11 +29,17 @@ export const RadarSlice = >({ endAngle, arcGenerator, tooltip, + onClick, }: RadarSliceProps) => { const [isHover, setIsHover] = useState(false) const theme = useTheme() const { showTooltipFromEvent, hideTooltip } = useTooltip() + const handleClick = useCallback( + (event: MouseEvent) => onClick?.(datum, event), + [onClick, datum] + ) + const tooltipData = useMemo(() => { const data: RadarSliceTooltipDatum[] = keys.map(key => ({ color: colorByKey[key], @@ -88,6 +95,7 @@ export const RadarSlice = >({ onMouseEnter={showItemTooltip} onMouseMove={showItemTooltip} onMouseLeave={hideItemTooltip} + onClick={handleClick} /> ) diff --git a/packages/radar/src/RadarSlices.tsx b/packages/radar/src/RadarSlices.tsx index 888d8375d1..074ac0ee7c 100644 --- a/packages/radar/src/RadarSlices.tsx +++ b/packages/radar/src/RadarSlices.tsx @@ -1,6 +1,6 @@ import { arc as d3Arc } from 'd3-shape' import { RadarSlice } from './RadarSlice' -import { RadarColorMapping, RadarCommonProps, RadarDataProps } from './types' +import { RadarColorMapping, RadarCommonProps, RadarDataProps, RadarSvgProps } from './types' interface RadarSlicesProps> { data: RadarDataProps['data'] @@ -12,6 +12,7 @@ interface RadarSlicesProps> { rotation: number angleStep: number tooltip: RadarCommonProps['sliceTooltip'] + onClick?: RadarSvgProps['onClick'] } export const RadarSlices = >({ @@ -24,6 +25,7 @@ export const RadarSlices = >({ rotation, angleStep, tooltip, + onClick, }: RadarSlicesProps) => { const arc = d3Arc<{ startAngle: number; endAngle: number }>().outerRadius(radius).innerRadius(0) @@ -52,6 +54,7 @@ export const RadarSlices = >({ radius={radius} arcGenerator={arc} tooltip={tooltip} + onClick={onClick} /> ) })} diff --git a/packages/radar/src/types.ts b/packages/radar/src/types.ts index 4205593b0e..8e80860b66 100644 --- a/packages/radar/src/types.ts +++ b/packages/radar/src/types.ts @@ -144,6 +144,33 @@ export type RadarSvgProps> = Partial & Dimensions & MotionProps & - SvgDefsAndFill> + SvgDefsAndFill> & + RadarHandlers export type BoundLegendProps = Required> & Omit + +export type MouseEventHandler = ( + datum: RawDatum, + event: React.MouseEvent +) => void + +export type RadarHandlers = { + onClick?: MouseEventHandler +} + +export type DatumId = string | number + +export interface ComputedDatum { + id: DatumId + label: DatumId + value: number + formattedValue: string + color: string + // only defined in case gradients or patterns are used + // and the datum matches one of the rules. + fill?: string + // contains the raw datum as passed to the chart + data: RawDatum + // arc: PieArc + hidden: boolean +} diff --git a/storybook/stories/radar/Radar.stories.tsx b/storybook/stories/radar/Radar.stories.tsx index ccd23793d2..b3d47fd429 100644 --- a/storybook/stories/radar/Radar.stories.tsx +++ b/storybook/stories/radar/Radar.stories.tsx @@ -227,3 +227,14 @@ export const WithPatterns: Story = { export const WithRotation: Story = { render: args => , } + +export const WithOnClick: Story = { + render: args => ( + console.log('datum', datum)} + /> + ), +} From 8f23728b844b2c03c4b5e375d42a91469aeb83bb Mon Sep 17 00:00:00 2001 From: Jean Date: Fri, 31 May 2024 18:15:44 +0200 Subject: [PATCH 2/6] removed non used types (ComputedDatum and DatumId) in types.ts of Radar --- packages/radar/src/types.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/packages/radar/src/types.ts b/packages/radar/src/types.ts index 8e80860b66..f8a47ace83 100644 --- a/packages/radar/src/types.ts +++ b/packages/radar/src/types.ts @@ -157,20 +157,3 @@ export type MouseEventHandler = ( export type RadarHandlers = { onClick?: MouseEventHandler } - -export type DatumId = string | number - -export interface ComputedDatum { - id: DatumId - label: DatumId - value: number - formattedValue: string - color: string - // only defined in case gradients or patterns are used - // and the datum matches one of the rules. - fill?: string - // contains the raw datum as passed to the chart - data: RawDatum - // arc: PieArc - hidden: boolean -} From 504c705881a2ea084c995287cf4407a0a7232942 Mon Sep 17 00:00:00 2001 From: Jean Date: Fri, 28 Jun 2024 11:28:03 +0200 Subject: [PATCH 3/6] forgot to remove import of ComputedDatum and RadarSvgProps in RadarGrid --- packages/radar/src/RadarGrid.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/radar/src/RadarGrid.tsx b/packages/radar/src/RadarGrid.tsx index b3a8c490c6..c814cc4bf1 100644 --- a/packages/radar/src/RadarGrid.tsx +++ b/packages/radar/src/RadarGrid.tsx @@ -2,7 +2,7 @@ import { SVGProps, useMemo } from 'react' import { positionFromAngle, useTheme } from '@nivo/core' import { RadarGridLabels } from './RadarGridLabels' import { RadarGridLevels } from './RadarGridLevels' -import { ComputedDatum, GridLabelComponent, RadarCommonProps, RadarSvgProps } from './types' +import { GridLabelComponent, RadarCommonProps } from './types' interface RadarGridProps> { indices: string[] From bf04ea2626515fcfb153350ab9e4043b6e22adeb Mon Sep 17 00:00:00 2001 From: Jean Date: Thu, 4 Jul 2024 18:04:51 +0200 Subject: [PATCH 4/6] added onClick interactivity test for Radar Chart, inspired by Pie.test.tsx --- packages/radar/tests/Radar.test.tsx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/radar/tests/Radar.test.tsx b/packages/radar/tests/Radar.test.tsx index 9f47ea2f6a..a73383e5bc 100644 --- a/packages/radar/tests/Radar.test.tsx +++ b/packages/radar/tests/Radar.test.tsx @@ -2,6 +2,8 @@ import { mount } from 'enzyme' import { LegendProps, BoxLegendSvg } from '@nivo/legends' // @ts-ignore import { Radar, RadarSvgProps, RadarSliceTooltipProps } from '../src' +import { act, create } from 'react-test-renderer' +import { RadarSlice } from '../src/RadarSlice' type TestDatum = { A: number @@ -222,3 +224,22 @@ describe('legend', () => { expect(wrapper.find(BoxLegendSvg).find('text').at(3).text()).toBe(customLabels[1].B) }) }) + +describe('interactivity', () => { + it('should support onClick handler', async () => { + const onClick = jest.fn() + // const wrapper = mount( {...baseProps} />) + const instance = create( {...baseProps} onClick={onClick} />).root + + await act(() => { + instance.findAllByType(RadarSlice)[0].findByType('path').props.onClick() + }) + + expect(onClick).toHaveBeenCalledTimes(1) + const [datum] = onClick.mock.calls[0] + expect(datum).toHaveProperty('A') + expect(datum).toHaveProperty('B') + expect(datum).not.toHaveProperty('C') + expect(datum).toHaveProperty('category') + }) +}) From 0c3767a9804f9a742457e4cb9ed147aeee2b197e Mon Sep 17 00:00:00 2001 From: Jean Date: Thu, 4 Jul 2024 19:04:12 +0200 Subject: [PATCH 5/6] updated website with onClick props : onClick added as property and clicking on the chart display an action log --- website/src/data/components/radar/props.ts | 8 ++++++++ website/src/pages/radar/index.tsx | 12 +++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/website/src/data/components/radar/props.ts b/website/src/data/components/radar/props.ts index 02ec532c70..a0550697a1 100644 --- a/website/src/data/components/radar/props.ts +++ b/website/src/data/components/radar/props.ts @@ -387,6 +387,14 @@ const props: ChartProperty[] = [ help: 'Override default slice tooltip.', flavors: ['svg'], }, + { + key: 'onClick', + flavors: ['svg', 'canvas'], + group: 'Interactivity', + help: 'onClick handler, it receives target node data and mouse event.', + type: '(node, event) => void', + required: false, + }, ...motionProperties(['svg'], svgDefaultProps), ] diff --git a/website/src/pages/radar/index.tsx b/website/src/pages/radar/index.tsx index c592cf46a7..809b69dcb8 100644 --- a/website/src/pages/radar/index.tsx +++ b/website/src/pages/radar/index.tsx @@ -114,13 +114,23 @@ const Radar = () => { getTabData={data => data.data} image={image} > - {(properties, data, theme) => { + {(properties, data, theme, logAction) => { return ( + logAction({ + type: 'click', + label: `[slice] {${Object.entries(slice) + .map(([key, value]) => `${key}: ${value}`) + .join(', ')}}`, + color: slice.color, + data: slice, + }) + } /> ) }} From 709ff3f0e459c34f07019ad8c2b543744f997aaa Mon Sep 17 00:00:00 2001 From: Jean Date: Fri, 5 Jul 2024 06:53:22 +0200 Subject: [PATCH 6/6] removed unnecessary Radar onClick handler story + removed commented line in Radar.test --- packages/radar/tests/Radar.test.tsx | 3 --- storybook/stories/radar/Radar.stories.tsx | 11 ----------- 2 files changed, 14 deletions(-) diff --git a/packages/radar/tests/Radar.test.tsx b/packages/radar/tests/Radar.test.tsx index a73383e5bc..b7e9b76066 100644 --- a/packages/radar/tests/Radar.test.tsx +++ b/packages/radar/tests/Radar.test.tsx @@ -228,13 +228,10 @@ describe('legend', () => { describe('interactivity', () => { it('should support onClick handler', async () => { const onClick = jest.fn() - // const wrapper = mount( {...baseProps} />) const instance = create( {...baseProps} onClick={onClick} />).root - await act(() => { instance.findAllByType(RadarSlice)[0].findByType('path').props.onClick() }) - expect(onClick).toHaveBeenCalledTimes(1) const [datum] = onClick.mock.calls[0] expect(datum).toHaveProperty('A') diff --git a/storybook/stories/radar/Radar.stories.tsx b/storybook/stories/radar/Radar.stories.tsx index b3d47fd429..ccd23793d2 100644 --- a/storybook/stories/radar/Radar.stories.tsx +++ b/storybook/stories/radar/Radar.stories.tsx @@ -227,14 +227,3 @@ export const WithPatterns: Story = { export const WithRotation: Story = { render: args => , } - -export const WithOnClick: Story = { - render: args => ( - console.log('datum', datum)} - /> - ), -}