You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中ECharts触发showTip无tooltip显示,如何实现键盘导航?

问题分析与解决方案

1. 代码中的核心错误

  • 缺少useState导入:代码中使用了useState但未从React导入,会导致运行时错误。
  • showTip参数不完整:仅指定seriesIndex无法让ECharts定位到具体数据点,必须同时提供dataIndex才能触发对应位置的tooltip。
  • Chart实例获取不可靠:反复通过getInstanceByDom查询DOM获取实例,不如直接将实例存储在ref中更稳定。

2. 修正后的完整代码

import type { LineSeriesOption } from "echarts/charts";
import { LineChart } from "echarts/charts";
import type { GridComponentOption, TitleComponentOption } from "echarts/components";
import {
    DataZoomComponent,
    GridComponent,
    LegendComponent,
    TitleComponent,
    ToolboxComponent,
    TooltipComponent,
} from "echarts/components";
import type { ComposeOption, ECharts, SetOptionOpts } from "echarts/core";
import { init, use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import { CSSProperties, useEffect, useRef, useState } from "react";

use([
    LegendComponent,
    LineChart,
    GridComponent,
    TooltipComponent,
    TitleComponent,
    ToolboxComponent,
    DataZoomComponent,
    CanvasRenderer,
]);

export type EChartsOption = ComposeOption<LineSeriesOption | TitleComponentOption | GridComponentOption>;

export interface ReactEChartsProps {
    option: EChartsOption;
    style?: CSSProperties;
    settings?: SetOptionOpts;
}

export function ReactECharts({ option, style, settings }: ReactEChartsProps): JSX.Element {
    const chartRef = useRef<HTMLDivElement>(null);
    const chartInstanceRef = useRef<ECharts | null>(null);
    const [chartInitialized, setChartInitialized] = useState(false);
    // 维护当前选中的数据点索引
    const [currentDataIndex, setCurrentDataIndex] = useState(0);

    useEffect(() => {
        if (chartRef.current !== null) {
            const chart = init(chartRef.current);
            chartInstanceRef.current = chart;
            setChartInitialized(true);
        }

        const resizeChart = () => chartInstanceRef.current?.resize();
        window.addEventListener("resize", resizeChart);

        return () => {
            chartInstanceRef.current?.dispose();
            window.removeEventListener("resize", resizeChart);
        };
    }, []);

    useEffect(() => {
        const canvas = chartRef.current?.querySelector("canvas");
        const chart = chartInstanceRef.current;
        if (!canvas || !chart) return;

        canvas.setAttribute("tabindex", "0");
        
        const handleKeydown = (e: KeyboardEvent) => {
            const series = option.series as LineSeriesOption[];
            const dataLength = series[0]?.data?.length || 0;
            if (dataLength === 0) return;

            switch(e.key) {
                case "ArrowLeft":
                    setCurrentDataIndex(prev => prev > 0 ? prev - 1 : dataLength - 1);
                    break;
                case "ArrowRight":
                    setCurrentDataIndex(prev => prev < dataLength - 1 ? prev + 1 : 0);
                    break;
                default:
                    return;
            }

            // 触发tooltip,指定系列和数据点索引
            chart.dispatchAction({
                type: "showTip",
                seriesIndex: 0, // 根据你的图表系列调整索引
                dataIndex: currentDataIndex
            });
        };

        const handleFocusIn = () => {
            window.addEventListener("keydown", handleKeydown);
            // 焦点获取时默认显示第一个数据点的tooltip
            chart.dispatchAction({
                type: "showTip",
                seriesIndex: 0,
                dataIndex: 0
            });
        };

        const handleFocusOut = () => {
            window.removeEventListener("keydown", handleKeydown);
            // 失去焦点时隐藏tooltip
            chart.dispatchAction({ type: "hideTip" });
        };

        canvas.addEventListener("focusin", handleFocusIn);
        canvas.addEventListener("focusout", handleFocusOut);

        return () => {
            canvas.removeEventListener("focusin", handleFocusIn);
            canvas.removeEventListener("focusout", handleFocusOut);
            window.removeEventListener("keydown", handleKeydown);
        };
    }, [chartInitialized, currentDataIndex, option]);

    useEffect(() => {
        chartInstanceRef.current?.setOption(option, settings);
    }, [option, settings]);

    return <div ref={chartRef} style={{ width: "100%", height: "300px", ...style }} />;
}

3. 关键优化说明

  • 存储Chart实例到Ref:直接将初始化后的实例存入chartInstanceRef,避免重复DOM查询,提升可靠性。
  • 数据索引状态管理:通过currentDataIndex跟踪选中的数据点,配合左右箭头按键切换索引,确保tooltip定位准确。
  • 完整的showTip参数:必须同时传入seriesIndex和dataIndex,ECharts才能找到对应的数据点并显示tooltip。
  • 焦点与边界处理:获取焦点时自动显示初始tooltip,失去焦点时隐藏;切换索引时处理边界,避免越界。

4. 额外注意事项

确保你的ECharts配置中tooltip组件处于启用状态:

const option = {
  tooltip: {
    trigger: 'item' // 根据图表类型选择item或axis
  },
  // ... 其他图表配置
}

若存在多个系列,可扩展逻辑支持切换seriesIndex实现跨系列导航。

内容的提问来源于stack exchange,提问作者Leff

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 20:05:02