Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(radar): add onClick handler #2601

Merged
merged 7 commits into from
Jul 5, 2024
2 changes: 2 additions & 0 deletions packages/radar/src/Radar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const InnerRadar = <D extends Record<string, unknown>>({
ariaDescribedBy,
defs = svgDefaultProps.defs,
fill = svgDefaultProps.fill,
onClick,
}: InnerRadarProps<D>) => {
const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions(
width,
Expand Down Expand Up @@ -154,6 +155,7 @@ const InnerRadar = <D extends Record<string, unknown>>({
rotation={rotation}
angleStep={angleStep}
tooltip={sliceTooltip}
onClick={onClick}
/>
</g>
)
Expand Down
3 changes: 2 additions & 1 deletion packages/radar/src/RadarLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<D extends Record<string, unknown>> {
data: D[]
Expand All @@ -19,6 +19,7 @@ interface RadarLayerProps<D extends Record<string, unknown>> {
borderColor: RadarCommonProps<D>['borderColor']
fillOpacity: RadarCommonProps<D>['fillOpacity']
blendMode: RadarCommonProps<D>['blendMode']
onClick?: RadarSvgProps<D>['onClick']
}

export const RadarLayer = <D extends Record<string, unknown>>({
Expand Down
10 changes: 9 additions & 1 deletion packages/radar/src/RadarSlice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<D extends Record<string, unknown>> {
datum: D
Expand All @@ -15,6 +15,7 @@ interface RadarSliceProps<D extends Record<string, unknown>> {
radius: number
arcGenerator: Arc<void, { startAngle: number; endAngle: number }>
tooltip: RadarCommonProps<D>['sliceTooltip']
onClick?: RadarSvgProps<D>['onClick']
}

export const RadarSlice = <D extends Record<string, unknown>>({
Expand All @@ -28,11 +29,17 @@ export const RadarSlice = <D extends Record<string, unknown>>({
endAngle,
arcGenerator,
tooltip,
onClick,
}: RadarSliceProps<D>) => {
const [isHover, setIsHover] = useState(false)
const theme = useTheme()
const { showTooltipFromEvent, hideTooltip } = useTooltip()

const handleClick = useCallback(
(event: MouseEvent<SVGPathElement>) => onClick?.(datum, event),
[onClick, datum]
)

const tooltipData = useMemo(() => {
const data: RadarSliceTooltipDatum[] = keys.map(key => ({
color: colorByKey[key],
Expand Down Expand Up @@ -88,6 +95,7 @@ export const RadarSlice = <D extends Record<string, unknown>>({
onMouseEnter={showItemTooltip}
onMouseMove={showItemTooltip}
onMouseLeave={hideItemTooltip}
onClick={handleClick}
/>
</>
)
Expand Down
5 changes: 4 additions & 1 deletion packages/radar/src/RadarSlices.tsx
Original file line number Diff line number Diff line change
@@ -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<D extends Record<string, unknown>> {
data: RadarDataProps<D>['data']
Expand All @@ -12,6 +12,7 @@ interface RadarSlicesProps<D extends Record<string, unknown>> {
rotation: number
angleStep: number
tooltip: RadarCommonProps<D>['sliceTooltip']
onClick?: RadarSvgProps<D>['onClick']
}

export const RadarSlices = <D extends Record<string, unknown>>({
Expand All @@ -24,6 +25,7 @@ export const RadarSlices = <D extends Record<string, unknown>>({
rotation,
angleStep,
tooltip,
onClick,
}: RadarSlicesProps<D>) => {
const arc = d3Arc<{ startAngle: number; endAngle: number }>().outerRadius(radius).innerRadius(0)

Expand Down Expand Up @@ -52,6 +54,7 @@ export const RadarSlices = <D extends Record<string, unknown>>({
radius={radius}
arcGenerator={arc}
tooltip={tooltip}
onClick={onClick}
/>
)
})}
Expand Down
12 changes: 11 additions & 1 deletion packages/radar/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,16 @@ export type RadarSvgProps<D extends Record<string, unknown>> = Partial<RadarComm
RadarDataProps<D> &
Dimensions &
MotionProps &
SvgDefsAndFill<RadarSvgFillMatcherDatum<D>>
SvgDefsAndFill<RadarSvgFillMatcherDatum<D>> &
RadarHandlers<D, SVGPathElement>

export type BoundLegendProps = Required<Pick<LegendProps, 'data'>> & Omit<LegendProps, 'data'>

export type MouseEventHandler<RawDatum, ElementType = HTMLCanvasElement> = (
datum: RawDatum,
event: React.MouseEvent<ElementType>
) => void

export type RadarHandlers<RawDatum, ElementType> = {
onClick?: MouseEventHandler<RawDatum, ElementType>
}
18 changes: 18 additions & 0 deletions packages/radar/tests/Radar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -222,3 +224,19 @@ 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 instance = create(<Radar<TestDatum> {...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')
})
})
8 changes: 8 additions & 0 deletions website/src/data/components/radar/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
]

Expand Down
12 changes: 11 additions & 1 deletion website/src/pages/radar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,23 @@ const Radar = () => {
getTabData={data => data.data}
image={image}
>
{(properties, data, theme) => {
{(properties, data, theme, logAction) => {
return (
<ResponsiveRadar
data={data.data}
keys={data.keys}
{...properties}
theme={theme}
onClick={slice =>
logAction({
type: 'click',
label: `[slice] {${Object.entries(slice)
.map(([key, value]) => `${key}: ${value}`)
.join(', ')}}`,
color: slice.color,
data: slice,
})
}
/>
)
}}
Expand Down
Loading