前言
当我们结合 React 使用 highCharts 库时,存在一些特殊自定义的情况,比如针对 Tooltip 定制化样式。当然 highCharts 也提供了配置自定义 tooltip 的 formatter 方法,可以支持 html 元素。但是并不够灵活,对于配置复杂样式或组件复用情况下,并不友好。因此寻求新的思路,以下便是社区常用的一个方法,在此记录。
实现
Tooltip 组件封装
import {Chart,TooltipFormatterCallbackFunction,TooltipFormatterContextObject,
} from 'highcharts';
import { useEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom';const generateTooltipId = (chartId: number) =>`highcharts-custom-tooltip-${chartId}`;interface Props {chart: Chart | null;children(formatterContext: TooltipFormatterContextObject): JSX.Element;
}export const Tooltip = ({ chart, children }: Props) => {const isInit = useRef(false);const [context, setContext] = useState<TooltipFormatterContextObject | null>(null);useEffect(() => {if (chart) {const formatter: TooltipFormatterCallbackFunction = function () {// Ensures that tooltip DOM container is rendered before React portal is created.if (!isInit.current) {isInit.current = true;setTimeout(() => {chart.tooltip.refresh.apply(chart.tooltip, [this.points ? this.points.map(({ point }) => point) : this.point,]);chart.tooltip.hide(0);});}setContext(this);return `<div id="${generateTooltipId(chart.index)}"></div>`;};chart.update({tooltip: {formatter,useHTML: true,},});}}, [chart]);const node = chart && document.getElementById(generateTooltipId(chart.index));return node && context? ReactDOM.createPortal(children(context), node): null;
};
调用方式
import React, { useState, useCallback } from "react";
import Highcharts, { Chart as HighchartsChart } from "highcharts";
import HighchartsReact from "highcharts-react-official";
import { Tooltip } from "./Tooltip";const options = {title: {text: "Custom tooltip as React component"},series: [{type: "line",data: [1, 22424242424, 3, 4, 5, 123123132]}],tooltip: {useHTML: true}
};export const Chart = () => {const [chart, setChart] = useState<HighchartsChart | null>(null);const callback = useCallback((chart: HighchartsChart) => {setChart(chart);}, []);return (<><HighchartsReacthighcharts={Highcharts}options={options}callback={callback}/><Tooltip chart={chart}>{(formatterContext) => {const { x, y } = formatterContext;return (<><div>x: {x}</div><div>y: {y}</div><br /><button onClick={() => alert(`x: ${x}, y: ${y}`)}>Action</button></>);}}</Tooltip></>);
};
formatterContext
里包含以下常用的字段:
{pointspointseriesxy
}
这些字段在自定义 tooltip 组件时可以传参到组件中获取每个 point 的值。
除了 x,y 值外,可以自定义字段,在 series.keys 配置项中,配置如下:
keys: ['x','y','custom.A','custom.B','custom.C',
],
data:[[1, 2, 'A1', 'B1', 'C1'], [1, 2, 'A2', 'B2', 'C2']]
其值便可以在 formatterContext.point.options.custom.A
及 formatterContext.point.options.custom.B
等方式获取。
总结
该实现方式最基本的原理是采用 React 的 createPortal
方法,在指定的元素下渲染子元素。可以理解为类似纯 js 的 getElementById('id').html(children(context))
。