UNPKG

@nivo/heatmap

Version:
1 lines 83.2 kB
{"version":3,"file":"nivo-heatmap.mjs","sources":["../src/HeatMapTooltip.tsx","../src/defaults.ts","../src/compute.ts","../src/hooks.ts","../src/HeatMapCellRect.tsx","../src/HeatMapCellCircle.tsx","../src/HeatMapCells.tsx","../src/HeatMapCellAnnotations.tsx","../src/HeatMap.tsx","../src/ResponsiveHeatMap.tsx","../src/canvas.tsx","../src/HeatMapCanvas.tsx","../src/ResponsiveHeatMapCanvas.tsx"],"sourcesContent":["import { memo } from 'react'\nimport { BasicTooltip } from '@nivo/tooltip'\nimport { HeatMapDatum, TooltipProps } from './types'\n\nconst NonMemoizedHeatMapTooltip = <Datum extends HeatMapDatum>({ cell }: TooltipProps<Datum>) => {\n if (cell.formattedValue === null) return null\n\n return (\n <BasicTooltip\n id={`${cell.serieId} - ${cell.data.x}`}\n value={cell.formattedValue}\n enableChip={true}\n color={cell.color}\n />\n )\n}\n\nexport const HeatMapTooltip = memo(NonMemoizedHeatMapTooltip) as typeof NonMemoizedHeatMapTooltip\n","import { DefaultHeatMapDatum, HeatMapCommonProps, LayerId } from './types'\nimport { HeatMapTooltip } from './HeatMapTooltip'\n\nexport const commonDefaultProps: Omit<\n HeatMapCommonProps<DefaultHeatMapDatum>,\n | 'margin'\n | 'theme'\n | 'valueFormat'\n | 'onClick'\n | 'renderWrapper'\n | 'role'\n | 'ariaLabel'\n | 'ariaLabelledBy'\n | 'ariaDescribedBy'\n> & {\n layers: LayerId[]\n} = {\n layers: ['grid', 'axes', 'cells', 'legends', 'annotations'],\n\n forceSquare: false,\n xInnerPadding: 0,\n xOuterPadding: 0,\n yInnerPadding: 0,\n yOuterPadding: 0,\n sizeVariation: false,\n\n opacity: 1,\n activeOpacity: 1,\n inactiveOpacity: 0.15,\n borderWidth: 0,\n borderColor: { from: 'color', modifiers: [['darker', 0.8]] },\n\n enableGridX: false,\n enableGridY: false,\n\n enableLabels: true,\n label: 'formattedValue',\n labelTextColor: { from: 'color', modifiers: [['darker', 2]] },\n\n colors: {\n type: 'sequential',\n scheme: 'brown_blueGreen',\n },\n emptyColor: '#000000',\n\n legends: [],\n annotations: [],\n\n isInteractive: true,\n hoverTarget: 'rowColumn',\n tooltip: HeatMapTooltip,\n\n animate: true,\n motionConfig: 'gentle' as const,\n}\n\nexport const svgDefaultProps = {\n ...commonDefaultProps,\n axisTop: {},\n axisRight: null,\n axisBottom: null,\n axisLeft: {},\n borderRadius: 0,\n cellComponent: 'rect' as const,\n}\n\nexport const canvasDefaultProps = {\n ...commonDefaultProps,\n axisTop: {},\n axisRight: null,\n axisBottom: null,\n axisLeft: {},\n renderCell: 'rect' as const,\n pixelRatio: typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,\n}\n","import { scaleBand, scaleLinear } from 'd3-scale'\nimport { castBandScale } from '@nivo/scales'\nimport {\n ComputedCell,\n HeatMapCommonProps,\n HeatMapDataProps,\n HeatMapDatum,\n SizeVariationConfig,\n} from './types'\n\nexport const computeLayout = ({\n width: _width,\n height: _height,\n rows,\n columns,\n forceSquare,\n}: {\n width: number\n height: number\n rows: number\n columns: number\n forceSquare: boolean\n}) => {\n let width = _width\n let height = _height\n\n let offsetX = 0\n let offsetY = 0\n\n if (forceSquare) {\n const cellWidth = Math.max(_width / columns, 0)\n const cellHeight = Math.max(_height / rows, 0)\n const cellSize = Math.min(cellWidth, cellHeight)\n\n width = cellSize * columns\n height = cellSize * rows\n\n offsetX = (_width - width) / 2\n offsetY = (_height - height) / 2\n }\n\n return {\n offsetX,\n offsetY,\n width,\n height,\n }\n}\n\nexport const computeCells = <Datum extends HeatMapDatum, ExtraProps extends object>({\n data,\n width: _width,\n height: _height,\n xInnerPadding,\n xOuterPadding,\n yInnerPadding,\n yOuterPadding,\n forceSquare,\n}: {\n data: HeatMapDataProps<Datum, ExtraProps>['data']\n width: number\n height: number\n} & Pick<\n HeatMapCommonProps<Datum>,\n 'xOuterPadding' | 'xInnerPadding' | 'yOuterPadding' | 'yInnerPadding' | 'forceSquare'\n>) => {\n const xValuesSet = new Set<Datum['x']>()\n const serieIds: string[] = []\n const allValues: number[] = []\n\n const cells: Pick<ComputedCell<Datum>, 'id' | 'serieId' | 'value' | 'data'>[] = []\n\n data.forEach(serie => {\n serieIds.push(serie.id)\n\n serie.data.forEach(datum => {\n xValuesSet.add(datum.x)\n\n let value: number | null = null\n if (datum.y !== undefined && datum.y !== null) {\n allValues.push(datum.y)\n value = datum.y\n }\n\n cells.push({\n id: `${serie.id}.${datum.x}`,\n serieId: serie.id,\n value,\n data: datum,\n })\n })\n })\n\n const xValues = Array.from(xValuesSet)\n\n const { width, height, offsetX, offsetY } = computeLayout({\n width: _width,\n height: _height,\n columns: xValues.length,\n rows: serieIds.length,\n forceSquare,\n })\n\n const xScale = castBandScale<Datum['x']>(\n scaleBand<Datum['x']>()\n .domain(xValues)\n .range([0, width])\n .paddingOuter(xOuterPadding)\n .paddingInner(xInnerPadding)\n )\n\n const yScale = castBandScale<string>(\n scaleBand<string>()\n .domain(serieIds)\n .range([0, height])\n .paddingOuter(yOuterPadding)\n .paddingInner(yInnerPadding)\n )\n\n const cellWidth = xScale.bandwidth()\n const cellHeight = yScale.bandwidth()\n\n const cellsWithPosition: Omit<\n ComputedCell<Datum>,\n 'formattedValue' | 'color' | 'opacity' | 'borderColor' | 'label' | 'labelTextColor'\n >[] = cells.map(cell => ({\n ...cell,\n x: xScale(cell.data.x)! + cellWidth / 2,\n y: yScale(cell.serieId)! + cellHeight / 2,\n width: cellWidth,\n height: cellHeight,\n }))\n\n return {\n width,\n height,\n offsetX,\n offsetY,\n xScale,\n yScale,\n minValue: Math.min(...allValues),\n maxValue: Math.max(...allValues),\n cells: cellsWithPosition,\n }\n}\n\nexport const computeSizeScale = (\n size: false | SizeVariationConfig,\n min: number,\n max: number\n): ((value: number | null) => number) => {\n if (!size) return () => 1\n\n const scale = scaleLinear()\n .domain(size.values ? size.values : [min, max])\n .range(size.sizes)\n\n return (value: number | null) => {\n if (value === null) return 1\n return scale(value)\n }\n}\n\nexport const getCellAnnotationPosition = <Datum extends HeatMapDatum>(\n cell: ComputedCell<Datum>\n) => ({\n x: cell.x,\n y: cell.y,\n})\n\nexport const getCellAnnotationDimensions = <Datum extends HeatMapDatum>(\n cell: ComputedCell<Datum>\n) => ({\n size: Math.max(cell.width, cell.height),\n width: cell.width,\n height: cell.height,\n})\n","import { useMemo, useCallback, useState } from 'react'\nimport { usePropertyAccessor, useValueFormatter } from '@nivo/core'\nimport { useTheme } from '@nivo/theming'\nimport { useInheritedColor, getContinuousColorScale } from '@nivo/colors'\nimport { AnnotationMatcher, useAnnotations } from '@nivo/annotations'\nimport {\n ComputedCell,\n DefaultHeatMapDatum,\n HeatMapCommonProps,\n HeatMapDataProps,\n HeatMapDatum,\n SizeVariationConfig,\n} from './types'\nimport { commonDefaultProps } from './defaults'\nimport {\n computeCells,\n computeSizeScale,\n getCellAnnotationPosition,\n getCellAnnotationDimensions,\n} from './compute'\n\nexport const useComputeCells = <Datum extends HeatMapDatum, ExtraProps extends object>({\n data,\n width,\n height,\n xInnerPadding,\n xOuterPadding,\n yInnerPadding,\n yOuterPadding,\n forceSquare,\n}: {\n data: HeatMapDataProps<Datum, ExtraProps>['data']\n width: number\n height: number\n} & Pick<\n HeatMapCommonProps<Datum>,\n 'xOuterPadding' | 'xInnerPadding' | 'yOuterPadding' | 'yInnerPadding' | 'forceSquare'\n>) =>\n useMemo(\n () =>\n computeCells<Datum, ExtraProps>({\n data,\n width,\n height,\n xInnerPadding,\n xOuterPadding,\n yInnerPadding,\n yOuterPadding,\n forceSquare,\n }),\n [\n data,\n width,\n height,\n xInnerPadding,\n xOuterPadding,\n yInnerPadding,\n yOuterPadding,\n forceSquare,\n ]\n )\n\nconst isHoverTargetByType = {\n cell: <Datum extends HeatMapDatum>(\n cell: Omit<\n ComputedCell<Datum>,\n 'formattedValue' | 'color' | 'opacity' | 'borderColor' | 'label' | 'labelTextColor'\n >,\n current: ComputedCell<Datum>\n ) => cell.id === current.id,\n row: <Datum extends HeatMapDatum>(\n cell: Omit<\n ComputedCell<Datum>,\n 'formattedValue' | 'color' | 'opacity' | 'borderColor' | 'label' | 'labelTextColor'\n >,\n current: ComputedCell<Datum>\n ) => cell.serieId === current.serieId,\n column: <Datum extends HeatMapDatum>(\n cell: Omit<\n ComputedCell<Datum>,\n 'formattedValue' | 'color' | 'opacity' | 'borderColor' | 'label' | 'labelTextColor'\n >,\n current: ComputedCell<Datum>\n ) => cell.data.x === current.data.x,\n rowColumn: <Datum extends HeatMapDatum>(\n cell: Omit<\n ComputedCell<Datum>,\n 'formattedValue' | 'color' | 'opacity' | 'borderColor' | 'label' | 'labelTextColor'\n >,\n current: ComputedCell<Datum>\n ) => cell.serieId === current.serieId || cell.data.x === current.data.x,\n}\n\nconst useSizeScale = (\n size: false | SizeVariationConfig,\n min: number,\n max: number\n): ((value: number | null) => number) =>\n useMemo(() => computeSizeScale(size, min, max), [size, min, max])\n\nconst useCellsStyle = <Datum extends HeatMapDatum = DefaultHeatMapDatum>({\n cells,\n minValue,\n maxValue,\n sizeVariation,\n colors,\n emptyColor,\n opacity,\n activeOpacity,\n inactiveOpacity,\n borderColor,\n label,\n labelTextColor,\n valueFormat,\n activeIds,\n}: {\n cells: Omit<\n ComputedCell<Datum>,\n 'formattedValue' | 'color' | 'opacity' | 'borderColor' | 'label' | 'labelTextColor'\n >[]\n minValue: number\n maxValue: number\n valueFormat?: HeatMapCommonProps<Datum>['valueFormat']\n activeIds: string[]\n} & Pick<\n HeatMapCommonProps<Datum>,\n | 'sizeVariation'\n | 'colors'\n | 'emptyColor'\n | 'opacity'\n | 'activeOpacity'\n | 'inactiveOpacity'\n | 'borderColor'\n | 'label'\n | 'labelTextColor'\n>) => {\n const getSize = useSizeScale(sizeVariation, minValue, maxValue)\n\n const colorScale = useMemo(() => {\n if (typeof colors === 'function') return null\n\n return getContinuousColorScale(colors, {\n min: minValue,\n max: maxValue,\n })\n }, [colors, minValue, maxValue])\n\n const getColor = useCallback(\n (cell: Omit<ComputedCell<Datum>, 'color' | 'opacity' | 'borderColor'>) => {\n if (cell.value !== null) {\n if (typeof colors === 'function') return colors(cell)\n if (colorScale !== null) return colorScale(cell.value)\n }\n\n return emptyColor\n },\n [colors, colorScale, emptyColor]\n )\n const theme = useTheme()\n const getBorderColor = useInheritedColor(borderColor, theme)\n const getLabelTextColor = useInheritedColor(labelTextColor, theme)\n\n const formatValue = useValueFormatter(valueFormat)\n const getLabel = usePropertyAccessor(label)\n\n const styledCells = useMemo(\n () =>\n cells.map(cell => {\n let computedOpacity = opacity\n if (activeIds.length > 0) {\n computedOpacity = activeIds.includes(cell.id) ? activeOpacity : inactiveOpacity\n }\n\n const sizeMultiplier = getSize(cell.value)\n\n const computedCell = {\n ...cell,\n width: cell.width * sizeMultiplier,\n height: cell.height * sizeMultiplier,\n formattedValue: cell.value !== null ? formatValue(cell.value) : null,\n opacity: computedOpacity,\n } as ComputedCell<Datum>\n\n computedCell.label = getLabel(computedCell)\n computedCell.color = getColor(computedCell)\n computedCell.borderColor = getBorderColor(computedCell)\n computedCell.labelTextColor = getLabelTextColor(computedCell)\n\n return computedCell\n }),\n [\n cells,\n getSize,\n getColor,\n getBorderColor,\n getLabelTextColor,\n formatValue,\n getLabel,\n activeIds,\n opacity,\n activeOpacity,\n inactiveOpacity,\n ]\n )\n\n return {\n cells: styledCells,\n colorScale,\n }\n}\n\nexport const useHeatMap = <\n Datum extends HeatMapDatum = DefaultHeatMapDatum,\n ExtraProps extends object = Record<string, never>,\n>({\n data,\n valueFormat,\n width: _width,\n height: _height,\n xOuterPadding = commonDefaultProps.xOuterPadding,\n xInnerPadding = commonDefaultProps.xInnerPadding,\n yOuterPadding = commonDefaultProps.yOuterPadding,\n yInnerPadding = commonDefaultProps.yInnerPadding,\n forceSquare = commonDefaultProps.forceSquare,\n sizeVariation = commonDefaultProps.sizeVariation,\n colors = commonDefaultProps.colors as HeatMapCommonProps<Datum>['colors'],\n emptyColor = commonDefaultProps.emptyColor,\n opacity = commonDefaultProps.opacity,\n activeOpacity = commonDefaultProps.activeOpacity,\n inactiveOpacity = commonDefaultProps.inactiveOpacity,\n borderColor = commonDefaultProps.borderColor as HeatMapCommonProps<Datum>['borderColor'],\n label = commonDefaultProps.label as HeatMapCommonProps<Datum>['label'],\n labelTextColor = commonDefaultProps.labelTextColor as HeatMapCommonProps<Datum>['labelTextColor'],\n hoverTarget = commonDefaultProps.hoverTarget,\n}: {\n data: HeatMapDataProps<Datum, ExtraProps>['data']\n width: number\n height: number\n} & Partial<\n Pick<\n HeatMapCommonProps<Datum>,\n | 'valueFormat'\n | 'xOuterPadding'\n | 'xInnerPadding'\n | 'yOuterPadding'\n | 'yInnerPadding'\n | 'forceSquare'\n | 'sizeVariation'\n | 'colors'\n | 'emptyColor'\n | 'opacity'\n | 'activeOpacity'\n | 'inactiveOpacity'\n | 'borderColor'\n | 'label'\n | 'labelTextColor'\n | 'hoverTarget'\n >\n>) => {\n const [activeCell, setActiveCell] = useState<ComputedCell<Datum> | null>(null)\n\n const { width, height, offsetX, offsetY, cells, xScale, yScale, minValue, maxValue } =\n useComputeCells<Datum, ExtraProps>({\n data,\n width: _width,\n height: _height,\n xOuterPadding,\n xInnerPadding,\n yOuterPadding,\n yInnerPadding,\n forceSquare,\n })\n\n const activeIds = useMemo(() => {\n if (!activeCell) return []\n\n const isHoverTarget = isHoverTargetByType[hoverTarget]\n\n return cells.filter(cell => isHoverTarget(cell, activeCell)).map(cell => cell.id)\n }, [cells, activeCell, hoverTarget])\n\n const { cells: computedCells, colorScale } = useCellsStyle<Datum>({\n cells,\n minValue,\n maxValue,\n sizeVariation,\n colors,\n emptyColor,\n opacity,\n activeOpacity,\n inactiveOpacity,\n borderColor,\n label,\n labelTextColor,\n valueFormat,\n activeIds,\n })\n\n return {\n width,\n height,\n offsetX,\n offsetY,\n cells: computedCells,\n xScale,\n yScale,\n colorScale,\n activeCell,\n setActiveCell,\n }\n}\n\nexport const useCellAnnotations = <Datum extends HeatMapDatum>(\n cells: ComputedCell<Datum>[],\n annotations: AnnotationMatcher<ComputedCell<Datum>>[]\n) =>\n useAnnotations<ComputedCell<Datum>>({\n data: cells,\n annotations,\n getPosition: getCellAnnotationPosition,\n getDimensions: getCellAnnotationDimensions,\n })\n","import { memo, useMemo } from 'react'\nimport { animated, to } from '@react-spring/web'\nimport { useTheme } from '@nivo/theming'\nimport { Text } from '@nivo/text'\nimport { CellComponentProps, HeatMapDatum } from './types'\n\nconst NonMemoizedHeatMapCellRect = <Datum extends HeatMapDatum>({\n cell,\n borderWidth,\n borderRadius,\n animatedProps,\n onMouseEnter,\n onMouseMove,\n onMouseLeave,\n onClick,\n enableLabels,\n}: CellComponentProps<Datum>) => {\n const theme = useTheme()\n\n const handlers = useMemo(\n () => ({\n onMouseEnter: onMouseEnter ? onMouseEnter(cell) : undefined,\n onMouseMove: onMouseMove ? onMouseMove(cell) : undefined,\n onMouseLeave: onMouseLeave ? onMouseLeave(cell) : undefined,\n onClick: onClick ? onClick(cell) : undefined,\n }),\n [cell, onMouseEnter, onMouseMove, onMouseLeave, onClick]\n )\n\n return (\n <animated.g\n data-testid={`cell.${cell.id}`}\n style={{ cursor: 'pointer' }}\n opacity={animatedProps.opacity}\n {...handlers}\n transform={to(\n [animatedProps.x, animatedProps.y, animatedProps.scale],\n (x, y, scale) => `translate(${x}, ${y}) scale(${scale})`\n )}\n >\n <animated.rect\n transform={to(\n [animatedProps.width, animatedProps.height],\n (width, height) => `translate(${width * -0.5}, ${height * -0.5})`\n )}\n key={cell.id}\n fill={animatedProps.color}\n width={animatedProps.width}\n height={animatedProps.height}\n stroke={animatedProps.borderColor}\n strokeWidth={borderWidth}\n rx={borderRadius}\n ry={borderRadius}\n />\n {enableLabels && (\n <Text\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n fill={animatedProps.labelTextColor}\n style={{\n ...theme.labels.text,\n fill: undefined,\n userSelect: 'none',\n }}\n >\n {cell.label}\n </Text>\n )}\n </animated.g>\n )\n}\n\nexport const HeatMapCellRect = memo(NonMemoizedHeatMapCellRect) as typeof NonMemoizedHeatMapCellRect\n","import { memo, useMemo } from 'react'\nimport { animated, to } from '@react-spring/web'\nimport { useTheme } from '@nivo/theming'\nimport { Text } from '@nivo/text'\nimport { HeatMapDatum, CellComponentProps } from './types'\n\nconst NonMemoizedHeatMapCellCircle = <Datum extends HeatMapDatum>({\n cell,\n borderWidth,\n animatedProps,\n onMouseEnter,\n onMouseMove,\n onMouseLeave,\n onClick,\n enableLabels,\n}: CellComponentProps<Datum>) => {\n const theme = useTheme()\n\n const handlers = useMemo(\n () => ({\n onMouseEnter: onMouseEnter ? onMouseEnter(cell) : undefined,\n onMouseMove: onMouseMove ? onMouseMove(cell) : undefined,\n onMouseLeave: onMouseLeave ? onMouseLeave(cell) : undefined,\n onClick: onClick ? onClick(cell) : undefined,\n }),\n [cell, onMouseEnter, onMouseMove, onMouseLeave, onClick]\n )\n\n return (\n <animated.g\n data-testid={`cell.${cell.id}`}\n style={{ cursor: 'pointer' }}\n opacity={animatedProps.opacity}\n {...handlers}\n transform={to([animatedProps.x, animatedProps.y], (x, y) => `translate(${x}, ${y})`)}\n >\n <animated.circle\n r={to(\n [animatedProps.width, animatedProps.height],\n (width, height) => Math.min(width, height) / 2\n )}\n fill={animatedProps.color}\n fillOpacity={animatedProps.opacity}\n strokeWidth={borderWidth}\n stroke={animatedProps.borderColor}\n />\n {enableLabels && (\n <Text\n dominantBaseline=\"central\"\n textAnchor=\"middle\"\n fill={animatedProps.labelTextColor}\n style={{\n ...theme.labels.text,\n fill: undefined,\n }}\n >\n {cell.label}\n </Text>\n )}\n </animated.g>\n )\n}\n\nexport const HeatMapCellCircle = memo(\n NonMemoizedHeatMapCellCircle\n) as typeof NonMemoizedHeatMapCellCircle\n","import { createElement, MouseEvent, useMemo } from 'react'\nimport { useTransition } from '@react-spring/web'\nimport { useMotionConfig } from '@nivo/core'\nimport { useTooltip } from '@nivo/tooltip'\nimport {\n CellComponent,\n ComputedCell,\n HeatMapDatum,\n HeatMapSvgProps,\n CellAnimatedProps,\n} from './types'\nimport { HeatMapCellRect } from './HeatMapCellRect'\nimport { HeatMapCellCircle } from './HeatMapCellCircle'\n\ninterface HeatMapCellsProps<Datum extends HeatMapDatum, ExtraProps extends object> {\n cells: ComputedCell<Datum>[]\n cellComponent: NonNullable<HeatMapSvgProps<Datum, ExtraProps>['cellComponent']>\n borderRadius: NonNullable<HeatMapSvgProps<Datum, ExtraProps>['borderRadius']>\n borderWidth: NonNullable<HeatMapSvgProps<Datum, ExtraProps>['borderWidth']>\n isInteractive: NonNullable<HeatMapSvgProps<Datum, ExtraProps>['isInteractive']>\n setActiveCell: (cell: ComputedCell<Datum> | null) => void\n onMouseEnter: HeatMapSvgProps<Datum, ExtraProps>['onMouseEnter']\n onMouseMove: HeatMapSvgProps<Datum, ExtraProps>['onMouseMove']\n onMouseLeave: HeatMapSvgProps<Datum, ExtraProps>['onMouseLeave']\n onClick: HeatMapSvgProps<Datum, ExtraProps>['onClick']\n tooltip: NonNullable<HeatMapSvgProps<Datum, ExtraProps>['tooltip']>\n enableLabels: NonNullable<HeatMapSvgProps<Datum, ExtraProps>['enableLabels']>\n}\n\nconst enterTransition = <Datum extends HeatMapDatum>(cell: ComputedCell<Datum>) => ({\n x: cell.x,\n y: cell.y,\n width: cell.width,\n height: cell.height,\n color: cell.color,\n opacity: 0,\n borderColor: cell.borderColor,\n labelTextColor: cell.labelTextColor,\n scale: 0,\n})\n\nconst regularTransition = <Datum extends HeatMapDatum>(cell: ComputedCell<Datum>) => ({\n x: cell.x,\n y: cell.y,\n width: cell.width,\n height: cell.height,\n color: cell.color,\n opacity: cell.opacity,\n borderColor: cell.borderColor,\n labelTextColor: cell.labelTextColor,\n scale: 1,\n})\n\nconst exitTransition = <Datum extends HeatMapDatum>(cell: ComputedCell<Datum>) => ({\n x: cell.x,\n y: cell.y,\n width: cell.width,\n height: cell.height,\n color: cell.color,\n opacity: 0,\n borderColor: cell.borderColor,\n labelTextColor: cell.labelTextColor,\n scale: 0,\n})\n\nexport const HeatMapCells = <Datum extends HeatMapDatum, ExtraProps extends object>({\n cells,\n cellComponent,\n borderRadius,\n borderWidth,\n isInteractive,\n setActiveCell,\n onMouseEnter,\n onMouseMove,\n onMouseLeave,\n onClick,\n tooltip,\n enableLabels,\n}: HeatMapCellsProps<Datum, ExtraProps>) => {\n const { animate, config: springConfig } = useMotionConfig()\n\n const transition = useTransition<ComputedCell<Datum>, CellAnimatedProps>(cells, {\n keys: (cell: ComputedCell<Datum>) => cell.id,\n initial: regularTransition,\n from: enterTransition,\n enter: regularTransition,\n update: regularTransition,\n leave: exitTransition,\n config: springConfig,\n immediate: !animate,\n })\n\n const { showTooltipFromEvent, hideTooltip } = useTooltip()\n\n const handleMouseEnter = useMemo(() => {\n if (!isInteractive) return undefined\n\n return (cell: ComputedCell<Datum>) => (event: MouseEvent) => {\n showTooltipFromEvent(createElement(tooltip, { cell }), event)\n setActiveCell(cell)\n onMouseEnter?.(cell, event)\n }\n }, [isInteractive, showTooltipFromEvent, tooltip, setActiveCell, onMouseEnter])\n\n const handleMouseMove = useMemo(() => {\n if (!isInteractive) return undefined\n\n return (cell: ComputedCell<Datum>) => (event: MouseEvent) => {\n showTooltipFromEvent(createElement(tooltip, { cell }), event)\n onMouseMove?.(cell, event)\n }\n }, [isInteractive, showTooltipFromEvent, tooltip, onMouseMove])\n\n const handleMouseLeave = useMemo(() => {\n if (!isInteractive) return undefined\n\n return (cell: ComputedCell<Datum>) => (event: MouseEvent) => {\n hideTooltip()\n setActiveCell(null)\n onMouseLeave?.(cell, event)\n }\n }, [isInteractive, hideTooltip, setActiveCell, onMouseLeave])\n\n const handleClick = useMemo(() => {\n if (!isInteractive) return undefined\n\n return (cell: ComputedCell<Datum>) => (event: MouseEvent) => {\n onClick?.(cell, event)\n }\n }, [isInteractive, onClick])\n\n let Cell: CellComponent<Datum>\n if (cellComponent === 'rect') {\n Cell = HeatMapCellRect\n } else if (cellComponent === 'circle') {\n Cell = HeatMapCellCircle\n } else {\n Cell = cellComponent\n }\n\n return (\n <>\n {transition((animatedProps, cell) =>\n createElement(Cell, {\n cell,\n borderRadius,\n borderWidth,\n animatedProps,\n enableLabels,\n onMouseEnter: handleMouseEnter,\n onMouseMove: handleMouseMove,\n onMouseLeave: handleMouseLeave,\n onClick: handleClick,\n })\n )}\n </>\n )\n}\n","import { Annotation } from '@nivo/annotations'\nimport { ComputedCell, HeatMapCommonProps, HeatMapDatum } from './types'\nimport { useCellAnnotations } from './hooks'\n\ninterface HeatMapCellAnnotationsProps<Datum extends HeatMapDatum> {\n cells: ComputedCell<Datum>[]\n annotations: NonNullable<HeatMapCommonProps<Datum>['annotations']>\n}\n\nexport const HeatMapCellAnnotations = <Datum extends HeatMapDatum>({\n cells,\n annotations,\n}: HeatMapCellAnnotationsProps<Datum>) => {\n const boundAnnotations = useCellAnnotations<Datum>(cells, annotations)\n\n return (\n <>\n {boundAnnotations.map((annotation, i) => (\n <Annotation key={i} {...annotation} />\n ))}\n </>\n )\n}\n","import { ReactNode, Fragment, createElement, useMemo, forwardRef, Ref, ReactElement } from 'react'\nimport { SvgWrapper, Container, useDimensions, WithChartRef } from '@nivo/core'\nimport { Axes, Grid } from '@nivo/axes'\nimport { AnchoredContinuousColorsLegendSvg } from '@nivo/legends'\nimport {\n DefaultHeatMapDatum,\n HeatMapDatum,\n HeatMapCommonProps,\n HeatMapSvgProps,\n LayerId,\n CustomLayerProps,\n} from './types'\nimport { useHeatMap } from './hooks'\nimport { svgDefaultProps } from './defaults'\nimport { HeatMapCells } from './HeatMapCells'\nimport { HeatMapCellAnnotations } from './HeatMapCellAnnotations'\n\ntype InnerHeatMapProps<Datum extends HeatMapDatum, ExtraProps extends object> = Omit<\n HeatMapSvgProps<Datum, ExtraProps>,\n 'animate' | 'motionConfig' | 'renderWrapper' | 'theme'\n> & {\n forwardedRef: Ref<SVGSVGElement>\n}\n\nconst InnerHeatMap = <Datum extends HeatMapDatum, ExtraProps extends object>({\n data,\n layers = svgDefaultProps.layers,\n valueFormat,\n width,\n height,\n margin: partialMargin,\n forceSquare = svgDefaultProps.forceSquare,\n xInnerPadding = svgDefaultProps.xInnerPadding,\n xOuterPadding = svgDefaultProps.xOuterPadding,\n yInnerPadding = svgDefaultProps.yInnerPadding,\n yOuterPadding = svgDefaultProps.yOuterPadding,\n sizeVariation = svgDefaultProps.sizeVariation,\n cellComponent = svgDefaultProps.cellComponent as NonNullable<\n HeatMapSvgProps<Datum, ExtraProps>['cellComponent']\n >,\n opacity = svgDefaultProps.opacity,\n activeOpacity = svgDefaultProps.activeOpacity,\n inactiveOpacity = svgDefaultProps.inactiveOpacity,\n borderRadius = svgDefaultProps.borderRadius,\n borderWidth = svgDefaultProps.borderWidth,\n borderColor = svgDefaultProps.borderColor as HeatMapCommonProps<Datum>['borderColor'],\n enableGridX = svgDefaultProps.enableGridX,\n enableGridY = svgDefaultProps.enableGridY,\n axisTop = svgDefaultProps.axisTop,\n axisRight = svgDefaultProps.axisRight,\n axisBottom = svgDefaultProps.axisBottom,\n axisLeft = svgDefaultProps.axisLeft,\n enableLabels = svgDefaultProps.enableLabels,\n label = svgDefaultProps.label as HeatMapCommonProps<Datum>['label'],\n labelTextColor = svgDefaultProps.labelTextColor as HeatMapCommonProps<Datum>['labelTextColor'],\n colors = svgDefaultProps.colors as HeatMapCommonProps<Datum>['colors'],\n emptyColor = svgDefaultProps.emptyColor,\n legends = svgDefaultProps.legends,\n annotations = svgDefaultProps.annotations as HeatMapCommonProps<Datum>['annotations'],\n isInteractive = svgDefaultProps.isInteractive,\n onMouseEnter,\n onMouseMove,\n onMouseLeave,\n onClick,\n hoverTarget = svgDefaultProps.hoverTarget,\n tooltip = svgDefaultProps.tooltip as HeatMapCommonProps<Datum>['tooltip'],\n role,\n ariaLabel,\n ariaLabelledBy,\n ariaDescribedBy,\n forwardedRef,\n}: InnerHeatMapProps<Datum, ExtraProps>) => {\n const {\n margin: _margin,\n innerWidth: _innerWidth,\n innerHeight: _innerHeight,\n outerWidth,\n outerHeight,\n } = useDimensions(width, height, partialMargin)\n\n const {\n width: innerWidth,\n height: innerHeight,\n offsetX,\n offsetY,\n xScale,\n yScale,\n cells,\n colorScale,\n activeCell,\n setActiveCell,\n } = useHeatMap<Datum, ExtraProps>({\n data,\n valueFormat,\n width: _innerWidth,\n height: _innerHeight,\n forceSquare,\n xInnerPadding,\n xOuterPadding,\n yInnerPadding,\n yOuterPadding,\n sizeVariation,\n colors,\n emptyColor,\n opacity,\n activeOpacity,\n inactiveOpacity,\n borderColor,\n label,\n labelTextColor,\n hoverTarget,\n })\n\n const margin = useMemo(\n () => ({\n ..._margin,\n top: _margin.top + offsetY,\n left: _margin.left + offsetX,\n }),\n [_margin, offsetX, offsetY]\n )\n\n const layerById: Record<LayerId, ReactNode> = {\n grid: null,\n axes: null,\n cells: null,\n legends: null,\n annotations: null,\n }\n\n if (layers.includes('grid')) {\n layerById.grid = (\n <Grid\n key=\"grid\"\n width={innerWidth} // - offsetX * 2\n height={innerHeight} // - offsetY * 2\n xScale={enableGridX ? xScale : null}\n yScale={enableGridY ? yScale : null}\n />\n )\n }\n\n if (layers.includes('axes')) {\n layerById.axes = (\n <Axes\n key=\"axes\"\n xScale={xScale}\n yScale={yScale}\n width={innerWidth} // - offsetX * 2\n height={innerHeight} // - offsetY * 2\n top={axisTop}\n right={axisRight}\n bottom={axisBottom}\n left={axisLeft}\n />\n )\n }\n\n if (layers.includes('cells')) {\n layerById.cells = (\n <Fragment key=\"cells\">\n <HeatMapCells<Datum, ExtraProps>\n cells={cells}\n cellComponent={cellComponent}\n borderRadius={borderRadius}\n borderWidth={borderWidth}\n isInteractive={isInteractive}\n setActiveCell={setActiveCell}\n onMouseEnter={onMouseEnter}\n onMouseMove={onMouseMove}\n onMouseLeave={onMouseLeave}\n onClick={onClick}\n tooltip={tooltip}\n enableLabels={enableLabels}\n />\n </Fragment>\n )\n }\n\n if (layers.includes('legends') && colorScale !== null) {\n layerById.legends = (\n <Fragment key=\"legends\">\n {legends.map((legend, index) => (\n <AnchoredContinuousColorsLegendSvg\n {...legend}\n key={index}\n containerWidth={innerWidth}\n containerHeight={innerHeight}\n scale={colorScale}\n />\n ))}\n </Fragment>\n )\n }\n\n if (layers.includes('annotations') && annotations.length > 0) {\n layerById.annotations = (\n <HeatMapCellAnnotations<Datum>\n key=\"annotations\"\n cells={cells}\n annotations={annotations}\n />\n )\n }\n\n const customLayerProps: CustomLayerProps<Datum> = {\n cells,\n activeCell,\n setActiveCell,\n }\n\n return (\n <SvgWrapper\n width={outerWidth}\n height={outerHeight}\n margin={Object.assign({}, margin, {\n top: margin.top, //+ offsetY,\n left: margin.left, // + offsetX,\n })}\n role={role}\n ariaLabel={ariaLabel}\n ariaLabelledBy={ariaLabelledBy}\n ariaDescribedBy={ariaDescribedBy}\n ref={forwardedRef}\n >\n {layers.map((layer, i) => {\n if (typeof layer === 'function') {\n return <Fragment key={i}>{createElement(layer, customLayerProps)}</Fragment>\n }\n\n return layerById?.[layer] ?? null\n })}\n </SvgWrapper>\n )\n}\n\nexport const HeatMap = forwardRef(\n <\n Datum extends HeatMapDatum = DefaultHeatMapDatum,\n ExtraProps extends object = Record<string, never>,\n >(\n {\n isInteractive = svgDefaultProps.isInteractive,\n animate = svgDefaultProps.animate,\n motionConfig = svgDefaultProps.motionConfig,\n theme,\n renderWrapper,\n ...otherProps\n }: HeatMapSvgProps<Datum, ExtraProps>,\n ref: Ref<SVGSVGElement>\n ) => (\n <Container\n {...{\n animate,\n isInteractive,\n motionConfig,\n renderWrapper,\n theme,\n }}\n >\n <InnerHeatMap<Datum, ExtraProps>\n isInteractive={isInteractive}\n {...otherProps}\n forwardedRef={ref}\n />\n </Container>\n )\n) as <\n Datum extends HeatMapDatum = DefaultHeatMapDatum,\n ExtraProps extends object = Record<string, never>,\n>(\n props: WithChartRef<HeatMapSvgProps<Datum, ExtraProps>, SVGSVGElement>\n) => ReactElement\n","import { forwardRef, Ref, ReactElement } from 'react'\nimport { ResponsiveWrapper, WithChartRef, ResponsiveProps } from '@nivo/core'\nimport { DefaultHeatMapDatum, HeatMapDatum, HeatMapSvgProps } from './types'\nimport { HeatMap } from './HeatMap'\n\nexport const ResponsiveHeatMap = forwardRef(\n <\n Datum extends HeatMapDatum = DefaultHeatMapDatum,\n ExtraProps extends object = Record<string, never>,\n >(\n {\n defaultWidth,\n defaultHeight,\n onResize,\n debounceResize,\n ...props\n }: ResponsiveProps<HeatMapSvgProps<Datum, ExtraProps>>,\n ref: Ref<SVGSVGElement>\n ) => (\n <ResponsiveWrapper\n defaultWidth={defaultWidth}\n defaultHeight={defaultHeight}\n onResize={onResize}\n debounceResize={debounceResize}\n >\n {({ width, height }) => (\n <HeatMap<Datum, ExtraProps> width={width} height={height} {...props} ref={ref} />\n )}\n </ResponsiveWrapper>\n )\n) as <\n Datum extends HeatMapDatum = DefaultHeatMapDatum,\n ExtraProps extends object = Record<string, never>,\n>(\n props: WithChartRef<ResponsiveProps<HeatMapSvgProps<Datum, ExtraProps>>, SVGSVGElement>\n) => ReactElement\n","import { setCanvasFont, drawCanvasText } from '@nivo/text'\nimport { CellCanvasRendererProps, HeatMapDatum } from './types'\n\nexport const renderRect = <Datum extends HeatMapDatum>(\n ctx: CanvasRenderingContext2D,\n {\n cell: { x, y, width, height, color, borderColor, opacity, labelTextColor, label },\n borderWidth,\n enableLabels,\n theme,\n }: CellCanvasRendererProps<Datum>\n) => {\n ctx.save()\n ctx.globalAlpha = opacity\n\n ctx.fillStyle = color\n if (borderWidth > 0) {\n ctx.strokeStyle = borderColor\n ctx.lineWidth = borderWidth\n }\n\n ctx.fillRect(x - width / 2, y - height / 2, width, height)\n if (borderWidth > 0) {\n ctx.strokeRect(x - width / 2, y - height / 2, width, height)\n }\n\n if (enableLabels) {\n setCanvasFont(ctx, theme.labels.text)\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n drawCanvasText(\n ctx,\n {\n ...theme.labels.text,\n fill: labelTextColor,\n },\n label,\n x,\n y\n )\n }\n\n ctx.restore()\n}\n\nexport const renderCircle = <Datum extends HeatMapDatum>(\n ctx: CanvasRenderingContext2D,\n {\n cell: { x, y, width, height, color, borderColor, opacity, labelTextColor, label },\n borderWidth,\n enableLabels,\n theme,\n }: CellCanvasRendererProps<Datum>\n) => {\n ctx.save()\n ctx.globalAlpha = opacity\n\n const radius = Math.min(width, height) / 2\n\n ctx.fillStyle = color\n if (borderWidth > 0) {\n ctx.strokeStyle = borderColor\n ctx.lineWidth = borderWidth\n }\n\n ctx.beginPath()\n ctx.arc(x, y, radius, 0, 2 * Math.PI)\n\n ctx.fill()\n if (borderWidth > 0) {\n ctx.stroke()\n }\n\n if (enableLabels) {\n setCanvasFont(ctx, theme.labels.text)\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n drawCanvasText(\n ctx,\n {\n ...theme.labels.text,\n fill: labelTextColor,\n },\n label,\n x,\n y\n )\n }\n\n ctx.restore()\n}\n","import {\n useEffect,\n useRef,\n useCallback,\n createElement,\n useMemo,\n MouseEvent,\n forwardRef,\n Ref,\n ReactElement,\n} from 'react'\nimport {\n getRelativeCursor,\n isCursorInRect,\n useDimensions,\n Container,\n WithChartRef,\n mergeRefs,\n} from '@nivo/core'\nimport { useTheme } from '@nivo/theming'\nimport { renderAxesToCanvas, renderGridLinesToCanvas } from '@nivo/axes'\nimport { useTooltip } from '@nivo/tooltip'\nimport { renderContinuousColorLegendToCanvas } from '@nivo/legends'\nimport { renderAnnotationsToCanvas, useComputedAnnotations } from '@nivo/annotations'\nimport { useHeatMap, useCellAnnotations } from './hooks'\nimport { renderRect, renderCircle } from './canvas'\nimport { canvasDefaultProps } from './defaults'\nimport {\n CellCanvasRenderer,\n DefaultHeatMapDatum,\n HeatMapCanvasProps,\n HeatMapCommonProps,\n HeatMapDatum,\n CellShape,\n CustomLayerProps,\n} from './types'\n\ntype InnerNetworkCanvasProps<Datum extends HeatMapDatum, ExtraProps extends object> = Omit<\n HeatMapCanvasProps<Datum, ExtraProps>,\n 'renderWrapper' | 'theme'\n> & {\n forwardedRef: Ref<HTMLCanvasElement>\n}\n\nconst InnerHeatMapCanvas = <Datum extends HeatMapDatum, ExtraProps extends object>({\n data,\n layers = canvasDefaultProps.layers,\n valueFormat,\n width,\n height,\n margin: partialMargin,\n xInnerPadding = canvasDefaultProps.xInnerPadding,\n xOuterPadding = canvasDefaultProps.xOuterPadding,\n yInnerPadding = canvasDefaultProps.yInnerPadding,\n yOuterPadding = canvasDefaultProps.yOuterPadding,\n forceSquare = canvasDefaultProps.forceSquare,\n sizeVariation = canvasDefaultProps.sizeVariation,\n renderCell: _renderCell = canvasDefaultProps.renderCell as CellShape,\n opacity = canvasDefaultProps.opacity,\n activeOpacity = canvasDefaultProps.activeOpacity,\n inactiveOpacity = canvasDefaultProps.inactiveOpacity,\n borderWidth = canvasDefaultProps.borderWidth,\n borderColor = canvasDefaultProps.borderColor as HeatMapCommonProps<Datum>['borderColor'],\n enableGridX = canvasDefaultProps.enableGridX,\n enableGridY = canvasDefaultProps.enableGridY,\n axisTop = canvasDefaultProps.axisTop,\n axisRight = canvasDefaultProps.axisRight,\n axisBottom = canvasDefaultProps.axisBottom,\n axisLeft = canvasDefaultProps.axisLeft,\n enableLabels = canvasDefaultProps.enableLabels,\n label = canvasDefaultProps.label as HeatMapCommonProps<Datum>['label'],\n labelTextColor = canvasDefaultProps.labelTextColor as HeatMapCommonProps<Datum>['labelTextColor'],\n colors = canvasDefaultProps.colors as HeatMapCommonProps<Datum>['colors'],\n emptyColor = canvasDefaultProps.emptyColor,\n legends = canvasDefaultProps.legends,\n annotations = canvasDefaultProps.annotations as HeatMapCommonProps<Datum>['annotations'],\n isInteractive = canvasDefaultProps.isInteractive,\n onClick,\n hoverTarget = canvasDefaultProps.hoverTarget,\n tooltip = canvasDefaultProps.tooltip as HeatMapCommonProps<Datum>['tooltip'],\n role,\n ariaLabel,\n ariaLabelledBy,\n ariaDescribedBy,\n pixelRatio = canvasDefaultProps.pixelRatio,\n forwardedRef,\n}: InnerNetworkCanvasProps<Datum, ExtraProps>) => {\n const canvasEl = useRef<HTMLCanvasElement | null>(null)\n\n const {\n margin: _margin,\n innerWidth: _innerWidth,\n innerHeight: _innerHeight,\n outerWidth,\n outerHeight,\n } = useDimensions(width, height, partialMargin)\n\n const {\n width: innerWidth,\n height: innerHeight,\n offsetX,\n offsetY,\n xScale,\n yScale,\n cells,\n colorScale,\n activeCell,\n setActiveCell,\n } = useHeatMap<Datum, ExtraProps>({\n data,\n valueFormat,\n width: _innerWidth,\n height: _innerHeight,\n xInnerPadding,\n xOuterPadding,\n yInnerPadding,\n yOuterPadding,\n forceSquare,\n sizeVariation,\n colors,\n emptyColor,\n opacity,\n activeOpacity,\n inactiveOpacity,\n borderColor,\n label,\n labelTextColor,\n hoverTarget,\n })\n\n const margin = useMemo(\n () => ({\n ..._margin,\n top: _margin.top + offsetY,\n left: _margin.left + offsetX,\n }),\n [_margin, offsetX, offsetY]\n )\n\n const boundAnnotations = useCellAnnotations(cells, annotations)\n const computedAnnotations = useComputedAnnotations({\n annotations: boundAnnotations,\n })\n\n let renderCell: CellCanvasRenderer<Datum>\n if (typeof _renderCell === 'function') {\n renderCell = _renderCell\n } else if (_renderCell === 'circle') {\n renderCell = renderCircle\n } else {\n renderCell = renderRect\n }\n\n const theme = useTheme()\n\n const customLayerProps: CustomLayerProps<Datum> = useMemo(\n () => ({\n cells,\n activeCell,\n setActiveCell,\n }),\n [cells, activeCell, setActiveCell]\n )\n\n useEffect(() => {\n if (canvasEl.current === null) return\n\n const ctx = canvasEl.current.getContext('2d')\n if (!ctx) return\n\n canvasEl.current.width = outerWidth * pixelRatio\n canvasEl.current.height = outerHeight * pixelRatio\n\n ctx.scale(pixelRatio, pixelRatio)\n\n ctx.fillStyle = theme.background\n ctx.fillRect(0, 0, outerWidth, outerHeight)\n ctx.translate(margin.left, margin.top) // + offsetX, margin.top + offsetY)\n\n layers.forEach(layer => {\n if (layer === 'grid') {\n ctx.lineWidth = theme.grid.line.strokeWidth as number\n ctx.strokeStyle = theme.grid.line.stroke as string\n\n if (enableGridX) {\n renderGridLinesToCanvas(ctx, {\n width: innerWidth,\n height: innerHeight,\n scale: xScale,\n axis: 'x',\n })\n }\n if (enableGridY) {\n renderGridLinesToCanvas(ctx, {\n width: innerWidth,\n height: innerHeight,\n scale: yScale,\n axis: 'y',\n })\n }\n } else if (layer === 'axes') {\n renderAxesToCanvas(ctx, {\n xScale,\n yScale,\n width: innerWidth, // - offsetX * 2,\n height: innerHeight, // - offsetY * 2,\n top: axisTop,\n right: axisRight,\n bottom: axisBottom,\n left: axisLeft,\n theme,\n })\n } else if (layer === 'cells') {\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n\n cells.forEach(cell => {\n renderCell(ctx, { cell, borderWidth, enableLabels, theme })\n })\n } else if (layer === 'legends' && colorScale !== null) {\n legends.forEach(legend => {\n renderContinuousColorLegendToCanvas(ctx, {\n ...legend,\n containerWidth: innerWidth,\n containerHeight: innerHeight,\n scale: colorScale,\n theme,\n })\n })\n } else if (layer === 'annotations') {\n renderAnnotationsToCanvas(ctx, {\n annotations: computedAnnotations,\n theme,\n })\n } else if (typeof layer === 'function') {\n layer(ctx, customLayerProps)\n }\n })\n }, [\n canvasEl,\n pixelRatio,\n outerWidth,\n outerHeight,\n innerWidth,\n innerHeight,\n margin,\n layers,\n customLayerProps,\n cells,\n renderCell,\n enableGridX,\n enableGridY,\n axisTop,\n axisRight,\n axisBottom,\n axisLeft,\n xScale,\n yScale,\n theme,\n borderWidth,\n enableLabels,\n colorScale,\n legends,\n computedAnnotations,\n ])\n\n const { showTooltipFromEvent, hideTooltip } = useTooltip()\n\n const handleMouseHover = useCallback(\n (event: MouseEvent<HTMLCanvasElement>) => {\n if (canvasEl.current === null) return\n\n const [x, y] = getRelativeCursor(canvasEl.current, event)\n\n const cell = cells.find(c =>\n isCursorInRect(\n c.x + margin.left - c.width / 2, // + offsetX - c.width / 2,\n c.y + margin.top - c.height / 2, //+ offsetY - c.height / 2,\n c.width,\n c.height,\n x,\n y\n )\n )\n if (cell !== undefined) {\n setActiveCell(cell)\n showTooltipFromEvent(createElement(tooltip, { cell }), event)\n } else {\n setActiveCell(null)\n hideTooltip()\n }\n },\n [\n canvasEl,\n cells,\n margin,\n // offsetX,\n // offsetY,\n setActiveCell,\n showTooltipFromEvent