diff --git a/packages/arcs/src/ArcShape.tsx b/packages/arcs/src/ArcShape.tsx new file mode 100644 index 0000000000..f08f0e02ff --- /dev/null +++ b/packages/arcs/src/ArcShape.tsx @@ -0,0 +1,60 @@ +import React, { useCallback } from 'react' +import { SpringValue, Interpolation, animated } from 'react-spring' +import { DatumWithArcAndColor } from './types' + +export type ArcMouseHandler = ( + datum: Datum, + event: React.MouseEvent +) => void + +export interface ArcShapeProps { + data: Datum + style: { + opacity: SpringValue + color: SpringValue + borderWidth: number + borderColor: SpringValue + path: Interpolation + } + onClick?: ArcMouseHandler + onMouseEnter?: ArcMouseHandler + onMouseMove?: ArcMouseHandler + onMouseLeave?: ArcMouseHandler +} + +/** + * A simple arc component to be used typically with an `ArcsLayer`. + * + * Please note that the component accepts `SpringValue`s instead of + * regular values to support animations. + */ +export const ArcShape = ({ + data, + style, + onClick, + onMouseEnter, + onMouseMove, + onMouseLeave, +}: ArcShapeProps) => { + const handleClick = useCallback(event => onClick?.(data, event), [onClick, data]) + + const handleMouseEnter = useCallback(event => onMouseEnter?.(data, event), [onMouseEnter, data]) + + const handleMouseMove = useCallback(event => onMouseMove?.(data, event), [onMouseMove, data]) + + const handleMouseLeave = useCallback(event => onMouseLeave?.(data, event), [onMouseLeave, data]) + + return ( + + ) +} diff --git a/packages/arcs/src/ArcsLayer.tsx b/packages/arcs/src/ArcsLayer.tsx new file mode 100644 index 0000000000..ee9765f231 --- /dev/null +++ b/packages/arcs/src/ArcsLayer.tsx @@ -0,0 +1,93 @@ +import React, { createElement } from 'react' +import { useTheme } from '@nivo/core' +import { InheritedColorConfig, useInheritedColor } from '@nivo/colors' +import { DatumWithArcAndColor, ArcGenerator } from './types' +import { useArcsTransition } from './useArcsTransition' +import { ArcTransitionMode } from './arcTransitionMode' +import { ArcMouseHandler, ArcShape, ArcShapeProps } from './ArcShape' + +type ArcComponent = (props: ArcShapeProps) => JSX.Element + +interface ArcsLayerProps { + center: [number, number] + data: Datum[] + arcGenerator: ArcGenerator + borderWidth: number + borderColor: InheritedColorConfig + onClick?: ArcMouseHandler + onMouseEnter?: ArcMouseHandler + onMouseMove?: ArcMouseHandler + onMouseLeave?: ArcMouseHandler + transitionMode: ArcTransitionMode + component?: ArcComponent +} + +export const ArcsLayer = ({ + center, + data, + arcGenerator, + borderWidth, + borderColor, + onClick, + onMouseEnter, + onMouseMove, + onMouseLeave, + transitionMode, + component = ArcShape, +}: ArcsLayerProps) => { + const theme = useTheme() + const getBorderColor = useInheritedColor(borderColor, theme) + + const { transition, interpolate } = useArcsTransition< + Datum, + { + opacity: number + color: string + borderColor: string + } + >(data, transitionMode, { + enter: datum => ({ + opacity: 0, + color: datum.color, + borderColor: getBorderColor(datum), + }), + update: datum => ({ + opacity: 1, + color: datum.color, + borderColor: getBorderColor(datum), + }), + leave: datum => ({ + opacity: 0, + color: datum.color, + borderColor: getBorderColor(datum), + }), + }) + + const Arc: ArcComponent = component + + return ( + + {transition((transitionProps, datum) => { + return createElement(Arc, { + key: datum.id, + data: datum, + style: { + ...transitionProps, + borderWidth, + path: interpolate( + transitionProps.startAngle, + transitionProps.endAngle, + transitionProps.innerRadius, + transitionProps.outerRadius, + arcGenerator + ), + }, + onClick, + onMouseEnter, + onMouseMove, + onMouseLeave, + }) + })} + + ) +} diff --git a/packages/arcs/src/index.ts b/packages/arcs/src/index.ts index 770222420b..a6116b38a8 100644 --- a/packages/arcs/src/index.ts +++ b/packages/arcs/src/index.ts @@ -1,3 +1,5 @@ +export * from './ArcShape' +export * from './ArcsLayer' export * from './arcTransitionMode' export * from './boundingBox' export * from './canvas' diff --git a/packages/pie/src/Slice.tsx b/packages/pie/src/Slice.tsx deleted file mode 100644 index 550f01cb74..0000000000 --- a/packages/pie/src/Slice.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React, { createElement, useCallback } from 'react' -import { animated, Interpolation, SpringValue } from 'react-spring' -import { useTooltip } from '@nivo/tooltip' -import { ComputedDatum, CompletePieSvgProps } from './types' - -interface SliceProps { - datum: ComputedDatum - path: string | Interpolation - color: string | SpringValue - opacity: number | SpringValue - borderWidth: number - borderColor: string - isInteractive: boolean - tooltip: CompletePieSvgProps['tooltip'] - onClick: CompletePieSvgProps['onClick'] - onMouseEnter: CompletePieSvgProps['onMouseEnter'] - onMouseMove: CompletePieSvgProps['onMouseMove'] - onMouseLeave: CompletePieSvgProps['onMouseLeave'] - setActiveId: (id: null | string | number) => void -} - -export const Slice = ({ - datum, - path, - color, - opacity, - borderWidth, - borderColor, - isInteractive, - onClick, - onMouseEnter, - onMouseMove, - onMouseLeave, - tooltip, - setActiveId, -}: SliceProps) => { - const { showTooltipFromEvent, hideTooltip } = useTooltip() - - const handleTooltip = useCallback( - event => showTooltipFromEvent(createElement(tooltip, { datum }), event), - [showTooltipFromEvent, datum, tooltip] - ) - - const handleMouseEnter = useCallback( - event => { - onMouseEnter?.(datum, event) - setActiveId(datum.id) - handleTooltip(event) - }, - [onMouseEnter, setActiveId, handleTooltip, datum] - ) - - const handleMouseMove = useCallback( - event => { - onMouseMove?.(datum, event) - handleTooltip(event) - }, - [onMouseMove, handleTooltip, datum] - ) - - const handleMouseLeave = useCallback( - event => { - onMouseLeave?.(datum, event) - setActiveId(null) - hideTooltip() - }, - [onMouseLeave, hideTooltip, datum] - ) - - const handleClick = useCallback( - event => { - onClick?.(datum, event) - }, - [onClick, datum] - ) - - return ( - - ) -} diff --git a/packages/pie/src/Slices.tsx b/packages/pie/src/Slices.tsx index 49724c14ff..d77406ccf2 100644 --- a/packages/pie/src/Slices.tsx +++ b/packages/pie/src/Slices.tsx @@ -1,10 +1,7 @@ -import React from 'react' -import { Interpolation } from 'react-spring' -import { useTheme } from '@nivo/core' -import { useInheritedColor } from '@nivo/colors' -import { ArcGenerator, useArcsTransition } from '@nivo/arcs' +import React, { createElement, useMemo } from 'react' +import { ArcGenerator, ArcsLayer } from '@nivo/arcs' +import { useTooltip } from '@nivo/tooltip' import { ComputedDatum, CompletePieSvgProps } from './types' -import { Slice } from './Slice' interface SlicesProps { center: [number, number] @@ -37,49 +34,57 @@ export const Slices = ({ tooltip, transitionMode, }: SlicesProps) => { - const theme = useTheme() - const getBorderColor = useInheritedColor>(borderColor, theme) - const { transition, interpolate } = useArcsTransition< - ComputedDatum, - { - color: string + const { showTooltipFromEvent, hideTooltip } = useTooltip() + + const handleClick = useMemo(() => { + if (!isInteractive) return undefined + + return (datum: ComputedDatum, event: any) => { + onClick?.(datum, event) + } + }, [isInteractive, onClick]) + + const handleMouseEnter = useMemo(() => { + if (!isInteractive) return undefined + + return (datum: ComputedDatum, event: any) => { + showTooltipFromEvent(createElement(tooltip, { datum }), event) + setActiveId(datum.id) + onMouseEnter?.(datum, event) + } + }, [isInteractive, showTooltipFromEvent, setActiveId, onMouseEnter]) + + const handleMouseMove = useMemo(() => { + if (!isInteractive) return undefined + + return (datum: ComputedDatum, event: any) => { + showTooltipFromEvent(createElement(tooltip, { datum }), event) + onMouseMove?.(datum, event) + } + }, [isInteractive, showTooltipFromEvent, onMouseMove]) + + const handleMouseLeave = useMemo(() => { + if (!isInteractive) return undefined + + return (datum: ComputedDatum, event: any) => { + hideTooltip() + setActiveId(null) + onMouseLeave?.(datum, event) } - >(data, transitionMode, { - enter: datum => ({ color: datum.color }), - update: datum => ({ color: datum.color }), - leave: datum => ({ color: datum.color }), - }) + }, [isInteractive, hideTooltip, setActiveId, onMouseLeave]) return ( - - {transition((transitionProps, datum) => { - return ( - - key={datum.id} - datum={datum} - path={ - interpolate( - transitionProps.startAngle, - transitionProps.endAngle, - transitionProps.innerRadius, - transitionProps.outerRadius, - arcGenerator - ) as Interpolation - } - color={transitionProps.color} - opacity={transitionProps.progress} - borderWidth={borderWidth} - borderColor={getBorderColor(datum)} - isInteractive={isInteractive} - onClick={onClick} - onMouseEnter={onMouseEnter} - onMouseMove={onMouseMove} - onMouseLeave={onMouseLeave} - setActiveId={setActiveId} - tooltip={tooltip} - /> - ) - })} - + > + center={center} + data={data} + arcGenerator={arcGenerator} + borderWidth={borderWidth} + borderColor={borderColor} + transitionMode={transitionMode} + onClick={handleClick} + onMouseEnter={handleMouseEnter} + onMouseMove={handleMouseMove} + onMouseLeave={handleMouseLeave} + /> ) }