Skip to content

Commit

Permalink
feat(bump): Add mesh to bump chart (#2496)
Browse files Browse the repository at this point in the history
* add mes to bump chart

* use margin from nivo core

* pass active point ids state to custom layers

* fix lockfile

* remove unnecessary castings

* set active point behaviour back to active series

* change tooltipAnchor for useMesh

* unify handler props

* fix webpage example

* upload lockfile
  • Loading branch information
santiperone committed Apr 29, 2024
1 parent 3f090bc commit 43db298
Show file tree
Hide file tree
Showing 15 changed files with 26,179 additions and 34,297 deletions.
1 change: 1 addition & 0 deletions packages/bump/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@nivo/legends": "workspace:*",
"@nivo/scales": "workspace:*",
"@nivo/tooltip": "workspace:*",
"@nivo/voronoi": "workspace:*",
"@react-spring/web": "9.4.5 || ^9.7.2",
"@types/d3-scale": "^4.0.8",
"@types/d3-shape": "^2.0.0",
Expand Down
121 changes: 81 additions & 40 deletions packages/bump/src/bump/Bump.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import {
BumpCustomLayerProps,
BumpDatum,
BumpLayerId,
BumpPointMouseHandler,
BumpSerieExtraProps,
BumpSerieMouseHandler,
BumpSvgProps,
DefaultBumpDatum,
} from './types'
import { useBump } from './hooks'
import { bumpSvgDefaultProps } from './defaults'
import { Line } from './Line'
import { LinesLabels } from './LinesLabels'
import { Points } from './Points'
import { Mesh } from './Mesh'

type InnerBumpProps<Datum extends BumpDatum, ExtraProps extends BumpSerieExtraProps> = Omit<
BumpSvgProps<Datum, ExtraProps>,
Expand Down Expand Up @@ -86,8 +88,13 @@ const InnerBump = <Datum extends BumpDatum, ExtraProps extends BumpSerieExtraPro
onMouseMove,
onMouseLeave,
onClick,
tooltip = bumpSvgDefaultProps.tooltip as NonNullable<
BumpSvgProps<Datum, ExtraProps>['tooltip']
useMesh = bumpSvgDefaultProps.useMesh,
debugMesh = bumpSvgDefaultProps.debugMesh,
lineTooltip = bumpSvgDefaultProps.lineTooltip as NonNullable<
BumpSvgProps<Datum, ExtraProps>['lineTooltip']
>,
pointTooltip = bumpSvgDefaultProps.pointTooltip as NonNullable<
BumpSvgProps<Datum, ExtraProps>['pointTooltip']
>,
role = bumpSvgDefaultProps.role,
}: InnerBumpProps<Datum, ExtraProps>) => {
Expand All @@ -97,40 +104,50 @@ const InnerBump = <Datum extends BumpDatum, ExtraProps extends BumpSerieExtraPro
partialMargin
)

const { series, points, xScale, yScale, lineGenerator, activeSerieIds, setActiveSerieIds } =
useBump<Datum, ExtraProps>({
width: innerWidth,
height: innerHeight,
data,
interpolation,
xPadding,
xOuterPadding,
yOuterPadding,
lineWidth,
activeLineWidth,
inactiveLineWidth,
colors,
opacity,
activeOpacity,
inactiveOpacity,
pointSize,
activePointSize,
inactivePointSize,
pointColor,
pointBorderWidth,
activePointBorderWidth,
inactivePointBorderWidth,
pointBorderColor,
isInteractive,
defaultActiveSerieIds,
})
const {
series,
points,
xScale,
yScale,
lineGenerator,
activePointIds,
activeSerieIds,
setActiveSerieIds,
setActivePointIds,
} = useBump<Datum, ExtraProps>({
width: innerWidth,
height: innerHeight,
data,
interpolation,
xPadding,
xOuterPadding,
yOuterPadding,
lineWidth,
activeLineWidth,
inactiveLineWidth,
colors,
opacity,
activeOpacity,
inactiveOpacity,
pointSize,
activePointSize,
inactivePointSize,
pointColor,
pointBorderWidth,
activePointBorderWidth,
inactivePointBorderWidth,
pointBorderColor,
isInteractive,
defaultActiveSerieIds,
})

const layerById: Record<BumpLayerId, ReactNode> = {
grid: null,
axes: null,
labels: null,
lines: null,
points: null,
mesh: null,
}

if (layers.includes('grid')) {
Expand Down Expand Up @@ -172,27 +189,47 @@ const InnerBump = <Datum extends BumpDatum, ExtraProps extends BumpSerieExtraPro
lineGenerator={lineGenerator}
yStep={yScale.step()}
isInteractive={isInteractive}
onMouseEnter={onMouseEnter}
onMouseMove={onMouseMove}
onMouseLeave={onMouseLeave}
onClick={onClick}
tooltip={tooltip}
onMouseEnter={onMouseEnter as BumpSerieMouseHandler<Datum, ExtraProps>}
onMouseMove={onMouseMove as BumpSerieMouseHandler<Datum, ExtraProps>}
onMouseLeave={onMouseLeave as BumpSerieMouseHandler<Datum, ExtraProps>}
onClick={onClick as BumpSerieMouseHandler<Datum, ExtraProps>}
lineTooltip={lineTooltip}
useMesh={useMesh}
/>
))}
</Fragment>
)
}

if (layers.includes('points')) {
layerById.points = (
<Points<Datum, ExtraProps>
key="points"
pointComponent={pointComponent}
if (isInteractive && useMesh && layers.includes('mesh')) {
layerById.mesh = (
<Mesh
key="mesh"
points={points}
width={innerWidth}
height={innerHeight}
margin={margin}
setActivePointIds={setActivePointIds}
setActiveSerieIds={setActiveSerieIds}
onMouseEnter={onMouseEnter as BumpPointMouseHandler<Datum, ExtraProps>}
onMouseMove={onMouseMove as BumpPointMouseHandler<Datum, ExtraProps>}
onMouseLeave={onMouseLeave as BumpPointMouseHandler<Datum, ExtraProps>}
onClick={onClick as BumpPointMouseHandler<Datum, ExtraProps>}
tooltip={pointTooltip}
debug={debugMesh}
/>
)
}

if (layers.includes('points')) {
layerById.points = points.map(point =>
createElement(pointComponent, {
key: point.id,
point,
})
)
}

if (layers.includes('labels')) {
layerById.labels = (
<Fragment key="legends">
Expand Down Expand Up @@ -228,10 +265,14 @@ const InnerBump = <Datum extends BumpDatum, ExtraProps extends BumpSerieExtraPro
xScale,
yScale,
activeSerieIds,
activePointIds,
setActiveSerieIds,
setActivePointIds,
}),
[
activePointIds,
activeSerieIds,
setActivePointIds,
setActiveSerieIds,
innerHeight,
innerWidth,
Expand Down
26 changes: 17 additions & 9 deletions packages/bump/src/bump/Line.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import { useSpring, animated } from '@react-spring/web'
import { Line as D3Line } from 'd3-shape'
import { useAnimatedPath, useMotionConfig } from '@nivo/core'
import { BumpCommonProps, BumpComputedSerie, BumpDatum, BumpSerieExtraProps } from './types'
import {
BumpCommonProps,
BumpComputedSerie,
BumpDatum,
BumpSerieExtraProps,
BumpSerieMouseHandler,
} from './types'
import { useBumpSerieHandlers } from './hooks'

interface LineProps<Datum extends BumpDatum, ExtraProps extends BumpSerieExtraProps> {
serie: BumpComputedSerie<Datum, ExtraProps>
lineGenerator: D3Line<[number, number | null]>
yStep: number
isInteractive: BumpCommonProps<Datum, ExtraProps>['isInteractive']
onMouseEnter?: BumpCommonProps<Datum, ExtraProps>['onMouseEnter']
onMouseMove?: BumpCommonProps<Datum, ExtraProps>['onMouseMove']
onMouseLeave?: BumpCommonProps<Datum, ExtraProps>['onMouseLeave']
onClick?: BumpCommonProps<Datum, ExtraProps>['onClick']
onMouseEnter?: BumpSerieMouseHandler<Datum, ExtraProps>
onMouseMove?: BumpSerieMouseHandler<Datum, ExtraProps>
onMouseLeave?: BumpSerieMouseHandler<Datum, ExtraProps>
onClick?: BumpSerieMouseHandler<Datum, ExtraProps>
setActiveSerieIds: (serieIds: string[]) => void
tooltip: BumpCommonProps<Datum, ExtraProps>['tooltip']
lineTooltip: BumpCommonProps<Datum, ExtraProps>['lineTooltip']
useMesh: BumpCommonProps<Datum, ExtraProps>['useMesh']
}

export const Line = <Datum extends BumpDatum, ExtraProps extends BumpSerieExtraProps>({
Expand All @@ -27,7 +34,8 @@ export const Line = <Datum extends BumpDatum, ExtraProps extends BumpSerieExtraP
onMouseLeave,
onClick,
setActiveSerieIds,
tooltip,
lineTooltip,
useMesh,
}: LineProps<Datum, ExtraProps>) => {
const handlers = useBumpSerieHandlers<Datum, ExtraProps>({
serie,
Expand All @@ -37,7 +45,7 @@ export const Line = <Datum extends BumpDatum, ExtraProps extends BumpSerieExtraP
onMouseLeave,
onClick,
setActiveSerieIds,
tooltip,
lineTooltip,
})

const { animate, config: springConfig } = useMotionConfig()
Expand Down Expand Up @@ -69,7 +77,7 @@ export const Line = <Datum extends BumpDatum, ExtraProps extends BumpSerieExtraP
strokeOpacity={animatedProps.opacity}
style={{ pointerEvents: 'none' }}
/>
{isInteractive && (
{isInteractive && !useMesh && (
<path
data-testid={`line.${serie.id}.interactive`}
fill="none"
Expand Down
120 changes: 120 additions & 0 deletions packages/bump/src/bump/Mesh.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { MouseEvent } from 'react'
import { createElement, memo, useCallback } from 'react'
import { Margin } from '@nivo/core'
import { useTooltip } from '@nivo/tooltip'
import { Mesh as BaseMesh } from '@nivo/voronoi'
import {
BumpCommonProps,
BumpDatum,
BumpPoint,
BumpPointMouseHandler,
BumpSerieExtraProps,
} from './types'

interface MeshProps<Datum extends BumpDatum, ExtraProps extends BumpSerieExtraProps> {
points: BumpPoint<Datum, ExtraProps>[]
width: number
height: number
margin: Margin
setActivePointIds: (ids: string[]) => void
setActiveSerieIds: (ids: string[]) => void
onMouseEnter?: BumpPointMouseHandler<Datum, ExtraProps>
onMouseMove?: BumpPointMouseHandler<Datum, ExtraProps>
onMouseLeave?: BumpPointMouseHandler<Datum, ExtraProps>
onClick?: BumpPointMouseHandler<Datum, ExtraProps>
tooltip: BumpCommonProps<Datum, ExtraProps>['pointTooltip']
debug: boolean
}

const InnerMesh = <Datum extends BumpDatum, ExtraProps extends BumpSerieExtraProps>({
points,
width,
height,
margin,
setActivePointIds,
setActiveSerieIds,
onMouseEnter,
onMouseMove,
onMouseLeave,
onClick,
tooltip,
debug,
}: MeshProps<Datum, ExtraProps>) => {
const { showTooltipAt, hideTooltip } = useTooltip()

const handleMouseEnter = useCallback(
(point: BumpPoint<Datum, ExtraProps>, event: MouseEvent) => {
showTooltipAt(
createElement(tooltip, { point }),
[point.x + margin.left, point.y ?? 0 + margin.top],
'top'
)
setActivePointIds([point.id])
setActiveSerieIds([point.serie.id])
onMouseEnter && onMouseEnter(point, event)
},
[
showTooltipAt,
tooltip,
margin.left,
margin.top,
setActivePointIds,
setActiveSerieIds,
onMouseEnter,
]
)

const handleMouseMove = useCallback(
(point: BumpPoint<Datum, ExtraProps>, event: MouseEvent) => {
showTooltipAt(
createElement(tooltip, { point }),
[point.x + margin.left, point.y ?? 0 + margin.top],
'top'
)
setActivePointIds([point.id])
setActiveSerieIds([point.serie.id])
onMouseMove && onMouseMove(point, event)
},
[
showTooltipAt,
tooltip,
margin.left,
margin.top,
setActivePointIds,
setActiveSerieIds,
onMouseMove,
]
)

const handleMouseLeave = useCallback(
(point: BumpPoint<Datum, ExtraProps>, event: MouseEvent) => {
hideTooltip()
setActivePointIds([])
setActiveSerieIds([])
onMouseLeave && onMouseLeave(point, event)
},
[hideTooltip, onMouseLeave, setActivePointIds, setActiveSerieIds]
)

const handleClick = useCallback(
(point: BumpPoint<Datum, ExtraProps>, event: MouseEvent) => {
onClick && onClick(point, event)
},
[onClick]
)

return (
<BaseMesh
nodes={points}
width={width}
height={height}
onMouseEnter={handleMouseEnter}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
onClick={handleClick}
debug={debug}
/>
)
}

export const Mesh = memo(InnerMesh) as typeof InnerMesh
6 changes: 4 additions & 2 deletions packages/bump/src/bump/Point.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { useSpring, animated, to } from '@react-spring/web'
import { useMotionConfig } from '@nivo/core'
import { BumpDatum, BumpPoint, BumpSerieExtraProps } from './types'

const pointStyle: SVGAttributes<SVGCircleElement>['style'] = { pointerEvents: 'none' }
const pointStyle: SVGAttributes<SVGCircleElement>['style'] = {
pointerEvents: 'none',
}

interface PointProps<Datum extends BumpDatum, ExtraProps extends BumpSerieExtraProps> {
export interface PointProps<Datum extends BumpDatum, ExtraProps extends BumpSerieExtraProps> {
point: BumpPoint<Datum, ExtraProps>
}

Expand Down
Loading

0 comments on commit 43db298

Please sign in to comment.