Skip to content

Commit 4929c46

Browse files
committedJan 12, 2022
feat(heatmap): init TypeScript migration
1 parent 3b43320 commit 4929c46

24 files changed

+1076
-623
lines changed
 

‎packages/heatmap/index.d.ts

-96
This file was deleted.

‎packages/heatmap/package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@
2121
],
2222
"main": "./dist/nivo-heatmap.cjs.js",
2323
"module": "./dist/nivo-heatmap.es.js",
24+
"typings": "./dist/types/index.d.ts",
2425
"files": [
2526
"README.md",
2627
"LICENSE.md",
27-
"index.d.ts",
28-
"dist/"
28+
"dist/",
29+
"!dist/tsconfig.tsbuildinfo"
2930
],
3031
"dependencies": {
3132
"@nivo/axes": "0.78.0",

‎packages/heatmap/src/HeatMap.js

-166
This file was deleted.

‎packages/heatmap/src/HeatMap.tsx

+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import { ReactNode, Fragment, createElement } from 'react'
2+
import { SvgWrapper, Container, useDimensions } from '@nivo/core'
3+
import { Axes, Grid } from '@nivo/axes'
4+
import { AnchoredContinuousColorsLegendSvg } from '@nivo/legends'
5+
import {
6+
DefaultHeatMapDatum,
7+
HeatMapDatum,
8+
HeatMapCommonProps,
9+
HeatMapSvgProps,
10+
LayerId,
11+
} from './types'
12+
import { useHeatMap } from './hooks'
13+
import { svgDefaultProps } from './defaults'
14+
import { HeatMapCells } from './HeatMapCells'
15+
16+
type InnerHeatMapProps<Datum extends HeatMapDatum, ExtraProps extends object> = Omit<
17+
HeatMapSvgProps<Datum, ExtraProps>,
18+
'animate' | 'motionConfig' | 'renderWrapper' | 'theme'
19+
>
20+
21+
const InnerHeatMap = <Datum extends HeatMapDatum, ExtraProps extends object>({
22+
data,
23+
layers = svgDefaultProps.layers,
24+
minValue: _minValue = svgDefaultProps.minValue,
25+
maxValue: _maxValue = svgDefaultProps.maxValue,
26+
valueFormat,
27+
width,
28+
height,
29+
margin: partialMargin,
30+
forceSquare = svgDefaultProps.forceSquare,
31+
xInnerPadding = svgDefaultProps.xInnerPadding,
32+
xOuterPadding = svgDefaultProps.xOuterPadding,
33+
yInnerPadding = svgDefaultProps.yInnerPadding,
34+
yOuterPadding = svgDefaultProps.yOuterPadding,
35+
sizeVariation = svgDefaultProps.sizeVariation,
36+
cellComponent = svgDefaultProps.cellComponent,
37+
opacity = svgDefaultProps.opacity,
38+
activeOpacity = svgDefaultProps.activeOpacity,
39+
inactiveOpacity = svgDefaultProps.inactiveOpacity,
40+
borderRadius = svgDefaultProps.borderRadius,
41+
borderWidth = svgDefaultProps.borderWidth,
42+
borderColor = svgDefaultProps.borderColor as HeatMapCommonProps<Datum>['borderColor'],
43+
enableGridX = svgDefaultProps.enableGridX,
44+
enableGridY = svgDefaultProps.enableGridY,
45+
axisTop = svgDefaultProps.axisTop,
46+
axisRight = svgDefaultProps.axisRight,
47+
axisBottom = svgDefaultProps.axisBottom,
48+
axisLeft = svgDefaultProps.axisLeft,
49+
enableLabels = svgDefaultProps.enableLabels,
50+
label = svgDefaultProps.label,
51+
labelTextColor = svgDefaultProps.labelTextColor,
52+
colors = svgDefaultProps.colors,
53+
nanColor = svgDefaultProps.nanColor,
54+
legends = svgDefaultProps.legends,
55+
isInteractive = svgDefaultProps.isInteractive,
56+
onMouseEnter,
57+
onMouseMove,
58+
onMouseLeave,
59+
onClick,
60+
hoverTarget = svgDefaultProps.hoverTarget,
61+
tooltip = svgDefaultProps.tooltip as HeatMapCommonProps<Datum>['tooltip'],
62+
role,
63+
ariaLabel,
64+
ariaLabelledBy,
65+
ariaDescribedBy,
66+
}: InnerHeatMapProps<Datum, ExtraProps>) => {
67+
const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions(
68+
width,
69+
height,
70+
partialMargin
71+
)
72+
73+
const { xScale, yScale, cells, colorScale } = useHeatMap<Datum, ExtraProps>({
74+
data,
75+
valueFormat,
76+
width: innerWidth,
77+
height: innerHeight,
78+
xInnerPadding,
79+
xOuterPadding,
80+
yInnerPadding,
81+
yOuterPadding,
82+
colors,
83+
opacity,
84+
activeOpacity,
85+
inactiveOpacity,
86+
borderColor,
87+
label,
88+
labelTextColor,
89+
})
90+
91+
const layerById: Record<LayerId, ReactNode> = {
92+
grid: null,
93+
axes: null,
94+
cells: null,
95+
legends: null,
96+
}
97+
98+
if (layers.includes('grid')) {
99+
layerById.grid = (
100+
<Grid
101+
key="grid"
102+
width={innerWidth} // - offsetX * 2
103+
height={innerHeight} // - offsetY * 2
104+
xScale={enableGridX ? xScale : null}
105+
yScale={enableGridY ? yScale : null}
106+
/>
107+
)
108+
}
109+
110+
if (layers.includes('axes')) {
111+
layerById.axes = (
112+
<Axes
113+
key="axes"
114+
xScale={xScale}
115+
yScale={yScale}
116+
width={innerWidth} // - offsetX * 2
117+
height={innerHeight} // - offsetY * 2
118+
top={axisTop}
119+
right={axisRight}
120+
bottom={axisBottom}
121+
left={axisLeft}
122+
/>
123+
)
124+
}
125+
126+
if (layers.includes('cells')) {
127+
layerById.cells = (
128+
<Fragment key="cells">
129+
<HeatMapCells<Datum, ExtraProps>
130+
cells={cells}
131+
borderRadius={borderRadius}
132+
borderWidth={borderWidth}
133+
isInteractive={isInteractive}
134+
onMouseEnter={onMouseEnter}
135+
onMouseMove={onMouseMove}
136+
onMouseLeave={onMouseLeave}
137+
onClick={onClick}
138+
tooltip={tooltip}
139+
enableLabels={enableLabels}
140+
/>
141+
</Fragment>
142+
)
143+
}
144+
145+
if (layers.includes('legends')) {
146+
layerById.legends = (
147+
<Fragment key="legends">
148+
{legends.map((legend, index) => (
149+
<AnchoredContinuousColorsLegendSvg
150+
{...legend}
151+
key={index}
152+
containerWidth={innerWidth}
153+
containerHeight={innerHeight}
154+
scale={colorScale}
155+
/>
156+
))}
157+
</Fragment>
158+
)
159+
}
160+
161+
return (
162+
<SvgWrapper
163+
width={outerWidth}
164+
height={outerHeight}
165+
margin={Object.assign({}, margin, {
166+
top: margin.top, //+ offsetY,
167+
left: margin.left, // + offsetX,
168+
})}
169+
role={role}
170+
ariaLabel={ariaLabel}
171+
ariaLabelledBy={ariaLabelledBy}
172+
ariaDescribedBy={ariaDescribedBy}
173+
>
174+
{layers.map((layer, i) => {
175+
if (typeof layer === 'function') {
176+
return <Fragment key={i}>{createElement(layer, {})}</Fragment>
177+
}
178+
179+
return layerById?.[layer] ?? null
180+
})}
181+
</SvgWrapper>
182+
)
183+
}
184+
185+
export const HeatMap = <
186+
Datum extends HeatMapDatum = DefaultHeatMapDatum,
187+
ExtraProps extends object = Record<string, never>
188+
>({
189+
isInteractive = svgDefaultProps.isInteractive,
190+
animate = svgDefaultProps.animate,
191+
motionConfig = svgDefaultProps.motionConfig,
192+
theme,
193+
renderWrapper,
194+
...otherProps
195+
}: HeatMapSvgProps<Datum, ExtraProps>) => (
196+
<Container
197+
{...{
198+
animate,
199+
isInteractive,
200+
motionConfig,
201+
renderWrapper,
202+
theme,
203+
}}
204+
>
205+
<InnerHeatMap<Datum, ExtraProps> isInteractive={isInteractive} {...otherProps} />
206+
</Container>
207+
)

‎packages/heatmap/src/HeatMapCanvas.js ‎packages/heatmap/src/HeatMapCanvas.tsx

+2-10
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* Copyright 2016-present, Raphaël Benitte.
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
91
import { useEffect, useRef, useCallback } from 'react'
102
import {
113
getRelativeCursor,
@@ -19,7 +11,7 @@ import { useTooltip } from '@nivo/tooltip'
1911
import { useHeatMap } from './hooks'
2012
import { HeatMapDefaultProps, HeatMapPropTypes } from './props'
2113
import { renderRect, renderCircle } from './canvas'
22-
import HeatMapCellTooltip from './HeatMapCellTooltip'
14+
import { HeatMapTooltip } from './HeatMapTooltip'
2315

2416
const HeatMapCanvas = ({
2517
data,
@@ -165,7 +157,7 @@ const HeatMapCanvas = ({
165157
if (cell !== undefined) {
166158
setCurrentCellId(cell.id)
167159
showTooltipFromEvent(
168-
<HeatMapCellTooltip cell={cell} tooltip={tooltip} format={tooltipFormat} />,
160+
<HeatMapTooltip cell={cell} tooltip={tooltip} format={tooltipFormat} />,
169161
event
170162
)
171163
} else {

‎packages/heatmap/src/HeatMapCellCircle.js ‎packages/heatmap/src/HeatMapCellCircle.tsx

-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* Copyright 2016-present, Raphaël Benitte.
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
91
import { memo } from 'react'
102
import PropTypes from 'prop-types'
113
import { useSpring, animated } from '@react-spring/web'

‎packages/heatmap/src/HeatMapCellRect.js ‎packages/heatmap/src/HeatMapCellRect.tsx

-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* Copyright 2016-present, Raphaël Benitte.
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
91
import { memo } from 'react'
102
import PropTypes from 'prop-types'
113
import { useSpring, animated } from '@react-spring/web'

‎packages/heatmap/src/HeatMapCellTooltip.js

-35
This file was deleted.

‎packages/heatmap/src/HeatMapCells.tsx

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import { createElement, MouseEvent, useCallback } from 'react'
2+
import { useTransition, animated, to } from '@react-spring/web'
3+
import { useTheme, useMotionConfig } from '@nivo/core'
4+
import { useTooltip } from '@nivo/tooltip'
5+
import { ComputedCell, HeatMapDatum, HeatMapSvgProps } from './types'
6+
7+
interface HeatMapCellsProps<Datum extends HeatMapDatum, ExtraProps extends object> {
8+
cells: ComputedCell<Datum>[]
9+
borderRadius: NonNullable<HeatMapSvgProps<Datum, ExtraProps>['borderRadius']>
10+
borderWidth: NonNullable<HeatMapSvgProps<Datum, ExtraProps>['borderWidth']>
11+
onMouseEnter: HeatMapSvgProps<Datum, ExtraProps>['onMouseEnter']
12+
onMouseMove: HeatMapSvgProps<Datum, ExtraProps>['onMouseMove']
13+
onMouseLeave: HeatMapSvgProps<Datum, ExtraProps>['onMouseLeave']
14+
onClick: HeatMapSvgProps<Datum, ExtraProps>['onClick']
15+
tooltip: NonNullable<HeatMapSvgProps<Datum, ExtraProps>['tooltip']>
16+
isInteractive: NonNullable<HeatMapSvgProps<Datum, ExtraProps>['isInteractive']>
17+
enableLabels: NonNullable<HeatMapSvgProps<Datum, ExtraProps>['enableLabels']>
18+
}
19+
20+
/*
21+
let cellComponent
22+
if (cellShape === 'rect') {
23+
cellComponent = HeatMapCellRect
24+
} else if (cellShape === 'circle') {
25+
cellComponent = HeatMapCellCircle
26+
} else {
27+
cellComponent = cellShape
28+
}
29+
*/
30+
31+
export const HeatMapCells = <Datum extends HeatMapDatum, ExtraProps extends object>({
32+
cells,
33+
borderRadius,
34+
borderWidth,
35+
onMouseEnter,
36+
onMouseMove,
37+
onMouseLeave,
38+
onClick,
39+
tooltip,
40+
isInteractive,
41+
enableLabels,
42+
}: HeatMapCellsProps<Datum, ExtraProps>) => {
43+
const theme = useTheme()
44+
const { animate, config: springConfig } = useMotionConfig()
45+
46+
const transition = useTransition<
47+
ComputedCell<Datum>,
48+
{
49+
x: number
50+
y: number
51+
width: number
52+
height: number
53+
color: string
54+
opacity: number
55+
borderColor: string
56+
labelTextColor: string
57+
}
58+
>(cells, {
59+
keys: cell => cell.id,
60+
initial: cell => ({
61+
x: cell.x,
62+
y: cell.y,
63+
width: cell.width,
64+
height: cell.height,
65+
color: cell.color,
66+
borderColor: cell.borderColor,
67+
labelTextColor: cell.labelTextColor,
68+
}),
69+
update: cell => ({
70+
x: cell.x,
71+
y: cell.y,
72+
width: cell.width,
73+
height: cell.height,
74+
color: cell.color,
75+
borderColor: cell.borderColor,
76+
labelTextColor: cell.labelTextColor,
77+
}),
78+
config: springConfig,
79+
immediate: !animate,
80+
})
81+
82+
const { showTooltipFromEvent, hideTooltip } = useTooltip()
83+
84+
const handleMouseEnter = useCallback(
85+
(cell: ComputedCell<Datum>, event: MouseEvent) => {
86+
showTooltipFromEvent(createElement(tooltip, { cell }), event)
87+
onMouseEnter?.(cell, event)
88+
},
89+
[showTooltipFromEvent, tooltip, onMouseEnter]
90+
)
91+
92+
const handleMouseMove = useCallback(
93+
(cell: ComputedCell<Datum>, event: MouseEvent) => {
94+
showTooltipFromEvent(createElement(tooltip, { cell }), event)
95+
onMouseMove?.(cell, event)
96+
},
97+
[showTooltipFromEvent, tooltip, onMouseMove]
98+
)
99+
100+
const handleMouseLeave = useCallback(
101+
(cell: ComputedCell<Datum>, event: MouseEvent) => {
102+
hideTooltip()
103+
onMouseLeave?.(cell, event)
104+
},
105+
[hideTooltip, onMouseLeave]
106+
)
107+
108+
return (
109+
<g>
110+
{transition((animatedProps, cell) => {
111+
return (
112+
<animated.g
113+
transform={to(
114+
[
115+
animatedProps.x,
116+
animatedProps.y,
117+
animatedProps.width,
118+
animatedProps.height,
119+
],
120+
(x, y, width, height) => {
121+
return `translate(${x - width / 2}, ${y - height / 2})`
122+
}
123+
)}
124+
>
125+
<animated.rect
126+
key={cell.id}
127+
fill={animatedProps.color}
128+
fillOpacity={cell.opacity}
129+
width={animatedProps.width}
130+
height={animatedProps.height}
131+
stroke={animatedProps.borderColor}
132+
strokeWidth={borderWidth}
133+
rx={borderRadius}
134+
ry={borderRadius}
135+
onMouseEnter={
136+
isInteractive ? event => handleMouseEnter(cell, event) : undefined
137+
}
138+
onMouseMove={
139+
isInteractive ? event => handleMouseMove(cell, event) : undefined
140+
}
141+
onMouseLeave={
142+
isInteractive ? event => handleMouseLeave(cell, event) : undefined
143+
}
144+
onClick={isInteractive ? event => onClick?.(cell, event) : undefined}
145+
/>
146+
{enableLabels && (
147+
<animated.text
148+
x={cell.width / 2}
149+
y={cell.height / 2}
150+
textAnchor="middle"
151+
dominantBaseline="central"
152+
fill={animatedProps.labelTextColor}
153+
style={{
154+
...theme.labels.text,
155+
fill: undefined,
156+
pointerEvents: 'none',
157+
}}
158+
>
159+
{cell.label}
160+
</animated.text>
161+
)}
162+
</animated.g>
163+
)
164+
})}
165+
</g>
166+
)
167+
}

‎packages/heatmap/src/HeatMapCells.js ‎packages/heatmap/src/HeatMapCells_old.tsx

-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* Copyright 2016-present, Raphaël Benitte.
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
91
import { createElement } from 'react'
102

113
const HeatMapCells = ({
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { memo } from 'react'
2+
import { BasicTooltip } from '@nivo/tooltip'
3+
import { HeatMapDatum, TooltipProps } from './types'
4+
5+
const NonMemoizedHeatMapTooltip = <Datum extends HeatMapDatum>({ cell }: TooltipProps<Datum>) => (
6+
<BasicTooltip
7+
id={`${cell.serieId} - ${cell.data.x}`}
8+
value={cell.formattedValue}
9+
enableChip={true}
10+
color={cell.color}
11+
/>
12+
)
13+
14+
export const HeatMapTooltip = memo(NonMemoizedHeatMapTooltip) as typeof NonMemoizedHeatMapTooltip

‎packages/heatmap/src/ResponsiveHeatMap.js

-18
This file was deleted.
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ResponsiveWrapper } from '@nivo/core'
2+
import { DefaultHeatMapDatum, HeatMapDatum, HeatMapSvgProps } from './types'
3+
import { HeatMap } from './HeatMap'
4+
5+
export const ResponsiveHeatMap = <
6+
Datum extends HeatMapDatum = DefaultHeatMapDatum,
7+
ExtraProps extends object = Record<string, never>
8+
>(
9+
props: Omit<HeatMapSvgProps<Datum, ExtraProps>, 'height' | 'width'>
10+
) => (
11+
<ResponsiveWrapper>
12+
{({ width, height }) => (
13+
<HeatMap<Datum, ExtraProps> width={width} height={height} {...props} />
14+
)}
15+
</ResponsiveWrapper>
16+
)

‎packages/heatmap/src/ResponsiveHeatMapCanvas.js ‎packages/heatmap/src/ResponsiveHeatMapCanvas.tsx

-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* Copyright 2016-present, Raphaël Benitte.
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
91
import { ResponsiveWrapper } from '@nivo/core'
102
import HeatMapCanvas from './HeatMapCanvas'
113

‎packages/heatmap/src/canvas.js ‎packages/heatmap/src/canvas.tsx

-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,3 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* Copyright 2016-present, Raphaël Benitte.
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
9-
101
/**
112
* Render heatmap rect cell.
123
*

‎packages/heatmap/src/compute.ts

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { scaleBand } from 'd3-scale'
2+
import { castBandScale } from '@nivo/scales'
3+
import { ComputedCell, HeatMapCommonProps, HeatMapDataProps, HeatMapDatum } from './types'
4+
5+
export const computeCells = <Datum extends HeatMapDatum, ExtraProps extends object>({
6+
data,
7+
width,
8+
height,
9+
xInnerPadding,
10+
xOuterPadding,
11+
yInnerPadding,
12+
yOuterPadding,
13+
}: {
14+
data: HeatMapDataProps<Datum, ExtraProps>['data']
15+
width: number
16+
height: number
17+
xInnerPadding: HeatMapCommonProps<Datum>['xInnerPadding']
18+
xOuterPadding: HeatMapCommonProps<Datum>['xOuterPadding']
19+
yInnerPadding: HeatMapCommonProps<Datum>['yInnerPadding']
20+
yOuterPadding: HeatMapCommonProps<Datum>['yOuterPadding']
21+
}) => {
22+
const xValuesSet = new Set<Datum['x']>()
23+
const serieIds: string[] = []
24+
const allValues: number[] = []
25+
26+
const cells: Pick<ComputedCell<Datum>, 'id' | 'serieId' | 'value' | 'data'>[] = []
27+
28+
data.forEach(serie => {
29+
serieIds.push(serie.id)
30+
31+
serie.data.forEach(datum => {
32+
xValuesSet.add(datum.x)
33+
allValues.push(datum.y)
34+
35+
cells.push({
36+
id: `${datum.x}.${serie.id}`,
37+
serieId: serie.id,
38+
value: datum.y,
39+
data: datum,
40+
})
41+
})
42+
})
43+
44+
const xValues = Array.from(xValuesSet)
45+
const xScale = castBandScale<Datum['x']>(
46+
scaleBand<Datum['x']>()
47+
.domain(xValues)
48+
.range([0, width])
49+
.paddingOuter(xOuterPadding)
50+
.paddingInner(xInnerPadding)
51+
)
52+
53+
const yScale = castBandScale<string>(
54+
scaleBand<string>()
55+
.domain(serieIds)
56+
.range([0, height])
57+
.paddingOuter(yOuterPadding)
58+
.paddingInner(yInnerPadding)
59+
)
60+
61+
const cellWidth = xScale.bandwidth()
62+
const cellHeight = yScale.bandwidth()
63+
64+
const cellsWithPosition: Omit<
65+
ComputedCell<Datum>,
66+
'formattedValue' | 'color' | 'opacity' | 'borderColor'
67+
>[] = cells.map(cell => ({
68+
...cell,
69+
x: xScale(cell.data.x)! + cellWidth / 2,
70+
y: yScale(cell.serieId)! + cellHeight / 2,
71+
width: cellWidth,
72+
height: cellHeight,
73+
}))
74+
75+
return {
76+
xScale,
77+
yScale,
78+
minValue: Math.min(...allValues),
79+
maxValue: Math.max(...allValues),
80+
cells: cellsWithPosition,
81+
}
82+
}

‎packages/heatmap/src/defaults.ts

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { DefaultHeatMapDatum, HeatMapCommonProps, LayerId } from './types'
2+
import { HeatMapTooltip } from './HeatMapTooltip'
3+
4+
export const commonDefaultProps: Omit<
5+
HeatMapCommonProps<DefaultHeatMapDatum>,
6+
| 'margin'
7+
| 'theme'
8+
| 'onClick'
9+
| 'renderWrapper'
10+
| 'role'
11+
| 'ariaLabel'
12+
| 'ariaLabelledBy'
13+
| 'ariaDescribedBy'
14+
> & {
15+
layers: LayerId[]
16+
} = {
17+
layers: ['grid', 'axes', 'cells', 'legends'],
18+
19+
minValue: 'auto',
20+
maxValue: 'auto',
21+
22+
forceSquare: false,
23+
xInnerPadding: 0,
24+
xOuterPadding: 0,
25+
yInnerPadding: 0,
26+
yOuterPadding: 0,
27+
sizeVariation: 0,
28+
29+
opacity: 0.85,
30+
activeOpacity: 1,
31+
inactiveOpacity: 0.35,
32+
borderWidth: 0,
33+
borderColor: { from: 'color' },
34+
35+
enableGridX: false,
36+
enableGridY: false,
37+
axisTop: {},
38+
axisRight: null,
39+
axisBottom: null,
40+
axisLeft: {},
41+
42+
enableLabels: true,
43+
label: 'formattedValue',
44+
labelTextColor: { from: 'color', modifiers: [['darker', 1.4]] },
45+
46+
colors: {
47+
type: 'sequential',
48+
scheme: 'brown_blueGreen',
49+
},
50+
nanColor: '#000000',
51+
52+
legends: [],
53+
54+
isInteractive: true,
55+
hoverTarget: 'rowColumn',
56+
tooltip: HeatMapTooltip,
57+
58+
animate: true,
59+
motionConfig: 'gentle' as const,
60+
}
61+
62+
export const svgDefaultProps = {
63+
...commonDefaultProps,
64+
borderRadius: 0,
65+
cellComponent: 'rect',
66+
}
67+
68+
export const canvasDefaultProps = {
69+
...commonDefaultProps,
70+
renderCell: 'rect',
71+
pixelRatio: typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,
72+
}

‎packages/heatmap/src/hooks.js

-233
This file was deleted.

‎packages/heatmap/src/hooks.ts

+323
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
import { useMemo, useCallback } from 'react'
2+
import {
3+
useTheme,
4+
usePropertyAccessor,
5+
useValueFormatter,
6+
// @ts-ignore
7+
getLabelGenerator,
8+
} from '@nivo/core'
9+
import { useInheritedColor, useContinuousColorScale } from '@nivo/colors'
10+
import {
11+
ComputedCell,
12+
DefaultHeatMapDatum,
13+
HeatMapCommonProps,
14+
HeatMapDataProps,
15+
HeatMapDatum,
16+
} from './types'
17+
import { commonDefaultProps } from './defaults'
18+
import { computeCells } from './compute'
19+
20+
export const useComputeCells = <Datum extends HeatMapDatum, ExtraProps extends object>({
21+
data,
22+
width,
23+
height,
24+
xInnerPadding,
25+
xOuterPadding,
26+
yInnerPadding,
27+
yOuterPadding,
28+
}: {
29+
data: HeatMapDataProps<Datum, ExtraProps>['data']
30+
width: number
31+
height: number
32+
xInnerPadding: HeatMapCommonProps<Datum>['xInnerPadding']
33+
xOuterPadding: HeatMapCommonProps<Datum>['xOuterPadding']
34+
yInnerPadding: HeatMapCommonProps<Datum>['yInnerPadding']
35+
yOuterPadding: HeatMapCommonProps<Datum>['yOuterPadding']
36+
}) =>
37+
useMemo(
38+
() =>
39+
computeCells<Datum, ExtraProps>({
40+
data,
41+
width,
42+
height,
43+
xInnerPadding,
44+
xOuterPadding,
45+
yInnerPadding,
46+
yOuterPadding,
47+
}),
48+
[data, width, height, xInnerPadding, xOuterPadding, yInnerPadding, yOuterPadding]
49+
)
50+
51+
const computeX = (column: number, cellWidth: number, padding: number) => {
52+
return column * cellWidth + cellWidth * 0.5 + padding * column + padding
53+
}
54+
const computeY = (row: number, cellHeight: number, padding: number) => {
55+
return row * cellHeight + cellHeight * 0.5 + padding * row + padding
56+
}
57+
58+
const isHoverTargetByType = {
59+
cell: (cell: ComputedCell<object>, current: ComputedCell<object>) =>
60+
cell.xKey === current.xKey && cell.yKey === current.yKey,
61+
row: (cell: ComputedCell<object>, current: ComputedCell<object>) => cell.yKey === current.yKey,
62+
column: (cell: ComputedCell<object>, current: ComputedCell<object>) =>
63+
cell.xKey === current.xKey,
64+
rowColumn: (cell: ComputedCell<object>, current: ComputedCell<object>) =>
65+
cell.xKey === current.xKey || cell.yKey === current.yKey,
66+
}
67+
68+
/*
69+
const computeCells = <Datum extends object>({
70+
data,
71+
keys,
72+
getIndex,
73+
xScale,
74+
yScale,
75+
sizeScale,
76+
cellOpacity,
77+
cellWidth,
78+
cellHeight,
79+
colorScale,
80+
nanColor,
81+
getLabel,
82+
getLabelTextColor,
83+
}: {
84+
data: HeatMapDataProps<Datum>['data']
85+
keys: HeatMapCommonProps<Datum>['keys']
86+
getIndex: (datum: Datum) => string | number
87+
}) => {
88+
const cells = []
89+
data.forEach(datum => {
90+
keys.forEach(key => {
91+
const value = datum[key]
92+
const label = getLabel(datum, key)
93+
const index = getIndex(datum)
94+
const sizeMultiplier = sizeScale ? sizeScale(value) : 1
95+
const width = sizeMultiplier * cellWidth
96+
const height = sizeMultiplier * cellHeight
97+
98+
const cell = {
99+
id: `${key}.${index}`,
100+
xKey: key,
101+
yKey: index,
102+
x: xScale(key),
103+
y: yScale(index),
104+
width,
105+
height,
106+
value,
107+
label,
108+
color: isNaN(value) ? nanColor : colorScale(value),
109+
opacity: cellOpacity,
110+
}
111+
cell.labelTextColor = getLabelTextColor(cell)
112+
113+
cells.push(cell)
114+
})
115+
})
116+
117+
return cells
118+
}
119+
*/
120+
121+
export const useHeatMap = <
122+
Datum extends HeatMapDatum = DefaultHeatMapDatum,
123+
ExtraProps extends object = Record<string, never>
124+
>({
125+
data,
126+
minValue: _minValue = commonDefaultProps.minValue,
127+
maxValue: _maxValue = commonDefaultProps.maxValue,
128+
valueFormat,
129+
width,
130+
height,
131+
forceSquare = commonDefaultProps.forceSquare,
132+
xOuterPadding = commonDefaultProps.xOuterPadding,
133+
xInnerPadding = commonDefaultProps.xInnerPadding,
134+
yOuterPadding = commonDefaultProps.yOuterPadding,
135+
yInnerPadding = commonDefaultProps.yInnerPadding,
136+
sizeVariation,
137+
colors = commonDefaultProps.colors as HeatMapCommonProps<Datum>['colors'],
138+
nanColor,
139+
opacity,
140+
activeOpacity,
141+
inactiveOpacity,
142+
borderColor = commonDefaultProps.borderColor as HeatMapCommonProps<Datum>['borderColor'],
143+
label = commonDefaultProps.label as HeatMapCommonProps<Datum>['label'],
144+
labelTextColor = commonDefaultProps.labelTextColor as HeatMapCommonProps<Datum>['labelTextColor'],
145+
hoverTarget,
146+
}: {
147+
data: HeatMapDataProps<Datum, ExtraProps>['data']
148+
minValue?: HeatMapCommonProps<Datum>['minValue']
149+
maxValue?: HeatMapCommonProps<Datum>['maxValue']
150+
valueFormat?: HeatMapCommonProps<Datum>['valueFormat']
151+
width: number
152+
height: number
153+
forceSquare?: HeatMapCommonProps<Datum>['forceSquare']
154+
xOuterPadding?: HeatMapCommonProps<Datum>['xOuterPadding']
155+
xInnerPadding?: HeatMapCommonProps<Datum>['xInnerPadding']
156+
yOuterPadding?: HeatMapCommonProps<Datum>['yOuterPadding']
157+
yInnerPadding?: HeatMapCommonProps<Datum>['yInnerPadding']
158+
sizeVariation?: HeatMapCommonProps<Datum>['sizeVariation']
159+
colors?: HeatMapCommonProps<Datum>['colors']
160+
nanColor?: HeatMapCommonProps<Datum>['nanColor']
161+
opacity?: HeatMapCommonProps<Datum>['opacity']
162+
activeOpacity?: HeatMapCommonProps<Datum>['activeOpacity']
163+
inactiveOpacity?: HeatMapCommonProps<Datum>['inactiveOpacity']
164+
borderColor?: HeatMapCommonProps<Datum>['borderColor']
165+
label?: HeatMapCommonProps<Datum>['label']
166+
labelTextColor?: HeatMapCommonProps<Datum>['labelTextColor']
167+
hoverTarget?: HeatMapCommonProps<Datum>['hoverTarget']
168+
}) => {
169+
const { cells, xScale, yScale, minValue, maxValue } = useComputeCells<Datum, ExtraProps>({
170+
data,
171+
width,
172+
height,
173+
xOuterPadding,
174+
xInnerPadding,
175+
yOuterPadding,
176+
yInnerPadding,
177+
})
178+
179+
const colorScale = useContinuousColorScale(colors, {
180+
min: minValue,
181+
max: maxValue,
182+
})
183+
184+
const getColor = useCallback(
185+
(cell: Omit<ComputedCell<Datum>, 'color' | 'opacity' | 'borderColor'>) =>
186+
colorScale(cell.value),
187+
[colorScale]
188+
)
189+
const theme = useTheme()
190+
const getBorderColor = useInheritedColor(borderColor, theme)
191+
const getLabelTextColor = useInheritedColor(labelTextColor, theme)
192+
const formatValue = useValueFormatter(valueFormat)
193+
const getLabel = usePropertyAccessor(label)
194+
195+
const computedCells = cells.map(cell => {
196+
const computedCell = {
197+
...cell,
198+
formattedValue: formatValue(cell.value),
199+
opacity: 1,
200+
} as ComputedCell<Datum>
201+
202+
computedCell.label = getLabel(computedCell)
203+
computedCell.color = getColor(computedCell)
204+
computedCell.borderColor = getBorderColor(computedCell)
205+
computedCell.labelTextColor = getLabelTextColor(computedCell)
206+
207+
return computedCell
208+
})
209+
210+
return {
211+
cells: computedCells,
212+
xScale,
213+
yScale,
214+
colorScale,
215+
}
216+
217+
/*
218+
const [currentCellId, setCurrentCellId] = useState(null)
219+
220+
const layoutConfig = useMemo(() => {
221+
const columns = keys.length
222+
const rows = data.length
223+
224+
let cellWidth = Math.max((width - padding * (columns + 1)) / columns, 0)
225+
let cellHeight = Math.max((height - padding * (rows + 1)) / rows, 0)
226+
227+
let offsetX = 0
228+
let offsetY = 0
229+
if (forceSquare === true) {
230+
const cellSize = Math.min(cellWidth, cellHeight)
231+
cellWidth = cellSize
232+
cellHeight = cellSize
233+
234+
offsetX = (width - ((cellWidth + padding) * columns + padding)) / 2
235+
offsetY = (height - ((cellHeight + padding) * rows + padding)) / 2
236+
}
237+
238+
return {
239+
cellWidth,
240+
cellHeight,
241+
offsetX,
242+
offsetY,
243+
}
244+
}, [data, keys, width, height, padding, forceSquare])
245+
246+
const sizeScale = useMemo(() => {
247+
if (sizeVariation > 0) {
248+
return scaleLinear()
249+
.range([1 - sizeVariation, 1])
250+
.domain([values.min, values.max])
251+
}
252+
}, [sizeVariation, values])
253+
254+
const getCellBorderColor = useInheritedColor(cellBorderColor, theme)
255+
const getLabelTextColor = useInheritedColor(labelTextColor, theme)
256+
257+
const cells = useMemo(
258+
() =>
259+
computeCells<Datum>({
260+
data,
261+
keys,
262+
getIndex,
263+
xScale: scales.x,
264+
yScale: scales.y,
265+
sizeScale,
266+
cellOpacity,
267+
cellWidth: layoutConfig.cellWidth,
268+
cellHeight: layoutConfig.cellHeight,
269+
colorScale,
270+
nanColor,
271+
getLabel,
272+
getLabelTextColor,
273+
}),
274+
[
275+
data,
276+
keys,
277+
getIndex,
278+
scales,
279+
sizeScale,
280+
cellOpacity,
281+
layoutConfig,
282+
colorScale,
283+
nanColor,
284+
getLabel,
285+
getLabelTextColor,
286+
]
287+
)
288+
289+
const cellsWithCurrent = useMemo(() => {
290+
if (currentCellId === null) return cells
291+
292+
const isHoverTarget = isHoverTargetByType[hoverTarget]
293+
const currentCell = cells.find(cell => cell.id === currentCellId)
294+
295+
return cells.map(cell => {
296+
const opacity = isHoverTarget(cell, currentCell)
297+
? cellHoverOpacity
298+
: cellHoverOthersOpacity
299+
300+
if (opacity === cell.opacity) return cell
301+
302+
return {
303+
...cell,
304+
opacity,
305+
}
306+
})
307+
}, [cells, currentCellId, hoverTarget, cellHoverOpacity, cellHoverOthersOpacity])
308+
309+
return {
310+
cells: cellsWithCurrent,
311+
getIndex,
312+
xScale: scales.x,
313+
yScale: scales.y,
314+
...layoutConfig,
315+
sizeScale,
316+
currentCellId,
317+
setCurrentCellId,
318+
colorScale,
319+
getCellBorderColor,
320+
getLabelTextColor,
321+
}
322+
*/
323+
}

‎packages/heatmap/src/index.js

-14
This file was deleted.

‎packages/heatmap/src/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export * from './HeatMap'
2+
export * from './ResponsiveHeatMap'
3+
export { default as HeatMapCanvas } from './HeatMapCanvas'
4+
export { default as ResponsiveHeatMapCanvas } from './ResponsiveHeatMapCanvas'
5+
export * from './hooks'
6+
export * from './props'
7+
export * from './defaults'

‎packages/heatmap/src/props.js

-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* Copyright 2016-present, Raphaël Benitte.
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
91
import PropTypes from 'prop-types'
102
import { quantizeColorScalePropType, noop } from '@nivo/core'
113
import { inheritedColorPropType } from '@nivo/colors'

‎packages/heatmap/src/types.ts

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import { AriaAttributes, MouseEvent, FunctionComponent } from 'react'
2+
import { AnimatedProps } from '@react-spring/web'
3+
import {
4+
Box,
5+
Theme,
6+
Dimensions,
7+
ModernMotionProps,
8+
PropertyAccessor,
9+
ValueFormat,
10+
} from '@nivo/core'
11+
import { AxisProps } from '@nivo/axes'
12+
import { InheritedColorConfig, ContinuousColorScaleConfig } from '@nivo/colors'
13+
import { AnchoredContinuousColorsLegendProps } from '@nivo/legends'
14+
15+
export interface HeatMapDatum {
16+
x: string | number
17+
y: number
18+
}
19+
20+
export interface DefaultHeatMapDatum {
21+
x: string
22+
y: number
23+
}
24+
25+
export type HeatMapSerie<Datum extends HeatMapDatum, ExtraProps extends object> = {
26+
id: string
27+
data: Datum[]
28+
} & ExtraProps
29+
30+
export interface ComputedCell<Datum extends HeatMapDatum> {
31+
id: string
32+
serieId: string
33+
value: number
34+
formattedValue: string
35+
data: Datum
36+
x: number
37+
y: number
38+
width: number
39+
height: number
40+
color: string
41+
opacity: number
42+
borderColor: string
43+
label: string
44+
labelTextColor: string
45+
}
46+
47+
export interface CellAnimatedProps {
48+
x: number
49+
y: number
50+
width: number
51+
height: number
52+
color: string
53+
opacity: number
54+
textColor: string
55+
borderColor: string
56+
}
57+
58+
export type CellCanvasRenderer<Datum extends HeatMapDatum> = (
59+
ctx: CanvasRenderingContext2D,
60+
cell: ComputedCell<Datum>
61+
) => void
62+
63+
export interface HeatMapDataProps<Datum extends HeatMapDatum, ExtraProps extends object> {
64+
data: HeatMapSerie<Datum, ExtraProps>[]
65+
}
66+
67+
export type LayerId = 'grid' | 'axes' | 'cells' | 'legends'
68+
export interface CustomLayerProps<Datum extends HeatMapDatum> {
69+
cells: ComputedCell<Datum>[]
70+
}
71+
export type CustomLayer<Datum extends HeatMapDatum> = FunctionComponent<CustomLayerProps<Datum>>
72+
export type CustomCanvasLayer<Datum extends HeatMapDatum> = (
73+
ctx: CanvasRenderingContext2D,
74+
props: CustomLayerProps<Datum>
75+
) => void
76+
77+
export interface TooltipProps<Datum extends HeatMapDatum> {
78+
cell: ComputedCell<Datum>
79+
}
80+
export type TooltipComponent<Datum extends HeatMapDatum> = FunctionComponent<TooltipProps<Datum>>
81+
82+
export interface CellComponentProps<Datum extends HeatMapDatum> {
83+
cell: ComputedCell<Datum>
84+
animated: AnimatedProps<CellAnimatedProps>
85+
onClick?: (cell: ComputedCell<Datum>, event: MouseEvent) => void
86+
onMouseEnter?: (cell: ComputedCell<Datum>, event: MouseEvent) => void
87+
onMouseMove?: (cell: ComputedCell<Datum>, event: MouseEvent) => void
88+
onMouseLeave?: (cell: ComputedCell<Datum>, event: MouseEvent) => void
89+
}
90+
export type CellComponent<Datum extends HeatMapDatum> = FunctionComponent<CellComponentProps<Datum>>
91+
92+
export type CellShape = 'rect' | 'circle'
93+
94+
export type HeatMapCommonProps<Datum extends HeatMapDatum> = {
95+
minValue: number | 'auto'
96+
maxValue: number | 'auto'
97+
valueFormat: ValueFormat<number>
98+
99+
margin: Box
100+
101+
forceSquare: boolean
102+
sizeVariation: number
103+
xInnerPadding: number
104+
xOuterPadding: number
105+
yInnerPadding: number
106+
yOuterPadding: number
107+
108+
opacity: number
109+
activeOpacity: number
110+
inactiveOpacity: number
111+
borderWidth: number
112+
borderColor: InheritedColorConfig<Omit<ComputedCell<Datum>, 'borderColor'>>
113+
114+
enableGridX: boolean
115+
enableGridY: boolean
116+
axisTop: AxisProps | null
117+
axisRight: AxisProps | null
118+
axisBottom: AxisProps | null
119+
axisLeft: AxisProps | null
120+
121+
theme: Theme
122+
colors:
123+
| ContinuousColorScaleConfig
124+
| ((
125+
cell: Omit<
126+
ComputedCell<Datum>,
127+
'color' | 'opacity' | 'borderColor' | 'labelTextColor'
128+
>
129+
) => string)
130+
nanColor: string
131+
132+
enableLabels: boolean
133+
label: PropertyAccessor<
134+
Omit<ComputedCell<Datum>, 'label' | 'color' | 'opacity' | 'borderColor' | 'labelTextColor'>,
135+
string
136+
>
137+
labelTextColor: InheritedColorConfig<Omit<ComputedCell<Datum>, 'labelTextColor'>>
138+
139+
legends: Omit<AnchoredContinuousColorsLegendProps, 'containerWidth' | 'containerHeight'>[]
140+
141+
isInteractive: boolean
142+
hoverTarget: 'cell' | 'row' | 'column' | 'rowColumn'
143+
tooltip: TooltipComponent<Datum>
144+
onClick: (cell: ComputedCell<Datum>, event: MouseEvent) => void
145+
146+
renderWrapper: boolean
147+
148+
role: string
149+
ariaLabel: AriaAttributes['aria-label']
150+
ariaLabelledBy: AriaAttributes['aria-labelledby']
151+
ariaDescribedBy: AriaAttributes['aria-describedby']
152+
} & Required<ModernMotionProps>
153+
154+
export type HeatMapSvgProps<Datum extends HeatMapDatum, ExtraProps extends object> = Partial<
155+
HeatMapCommonProps<Datum>
156+
> &
157+
HeatMapDataProps<Datum, ExtraProps> &
158+
Dimensions & {
159+
borderRadius?: number
160+
layers?: (LayerId | CustomLayer<Datum>)[]
161+
cellComponent?: CellShape | CellComponent<Datum>
162+
onMouseEnter?: (cell: ComputedCell<Datum>, event: MouseEvent) => void
163+
onMouseMove?: (cell: ComputedCell<Datum>, event: MouseEvent) => void
164+
onMouseLeave?: (cell: ComputedCell<Datum>, event: MouseEvent) => void
165+
}
166+
167+
export type HeatMapCanvasProps<Datum extends HeatMapDatum, ExtraProps extends object> = Partial<
168+
HeatMapCommonProps<Datum>
169+
> &
170+
HeatMapDataProps<Datum, ExtraProps> &
171+
Dimensions & {
172+
layers?: (LayerId | CustomCanvasLayer<Datum>)[]
173+
renderCell?: CellShape | CellCanvasRenderer<Datum>
174+
pixelRatio?: number
175+
}

‎packages/heatmap/tsconfig.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "../../tsconfig.types.json",
3+
"compilerOptions": {
4+
"outDir": "./dist/types",
5+
"rootDir": "./src"
6+
},
7+
"include": ["src/**/*"]
8+
}

0 commit comments

Comments
 (0)
Please sign in to comment.