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

如何为React-ECharts柱状图实现多层提示框/标签?

如何为React-ECharts柱状图实现多层提示框/标签?

我明白你想要实现的效果——鼠标悬停柱状图时显示基础提示框,再把鼠标移到这个提示框上时,弹出另一个包含更多细节的提示框对吧?你的思路方向是对的,但可能在ECharts的事件处理和自定义提示框的交互逻辑上有点小问题,我来帮你调整一下:

问题根源分析

你之前的代码里,自定义提示框是通过监听柱状图的mouseover事件触发的,但ECharts自带的tooltip浮层默认会拦截鼠标事件,导致你无法直接在tooltip上触发mouseenter;另外,通过event.offsetX/Y获取的位置是相对于图表容器的,用来定位fixed元素会出现偏差,还有状态管理的逻辑也可以更简洁。

修正后的完整代码

import React, { useState } from "react";
import ReactDOM from "react-dom";
import ReactECharts from "echarts-for-react";

const BarChartUpdated = ({ data, label, categories }: any) => {
  const [extraTooltip, setExtraTooltip] = useState<{
    x: number;
    y: number;
    details: string;
    show: boolean;
  } | null>(null);

  const generateColors = (categories: string[]) => {
    const colorMap: Record<string, string> = {};
    const colorPalette = [
      "#5470C6",
      "#91CC75",
      "#EE6666",
      "#FAC858",
      "#73C0DE",
      "#9A60B4",
      "#EA7CCC",
    ];
    categories.forEach((category, index) => {
      if (!colorMap[category]) {
        colorMap[category] = colorPalette[index % colorPalette.length];
      }
    });
    return colorMap;
  };

  const categoryColors = generateColors(categories);
  const sanitizedData = data.map((value: number) => (isNaN(value) ? 0 : value));
  const total = sanitizedData.reduce(
    (sum: number, value: number) => sum + value,
    0,
  );

  const option = {
    title: {
      text: label,
    },
    tooltip: {
      trigger: "axis",
      enterable: true, // 关键:允许鼠标进入tooltip内部
      formatter: (params: any) => {
        return params
          .map(
            (item: any) =>
              `<b>${item.name}</b></br> Count: ${item.value > 0 ? item.value : 0}`,
          )
          .join("<br/>");
      },
    },
    toolbox: {
      feature: {
        saveAsImage: { show: true, title: "Save as Image" },
        dataZoom: { show: true, title: "Zoom" },
      },
    },
    xAxis: {
      type: "category",
      data: categories,
    },
    yAxis: {
      type: "value",
    },
    series: [
      {
        type: "bar",
        data: data.map((value: number, index: number) => ({
          value,
          itemStyle: {
            color: categoryColors[categories[index]],
          },
          label: {
            show: true,
            position: "top",
            formatter:
              total <= 0 ? "0%" : `${Math.ceil((value / total) * 100)}%`,
          },
        })),
      },
    ],
    grid: {
      left: "3%",
      right: "4%",
      bottom: "3%",
      containLabel: true,
    },
  };

  // 监听自带tooltip显示事件
  const handleTooltipShow = (event: any) => {
    const { params, position } = event;
    if (params && params.length > 0) {
      const { name, value } = params[0];
      // 用ECharts返回的position来定位,更准确
      setExtraTooltip({
        x: position[0],
        y: position[1],
        details: `More details for ${name}: Count ${value}`,
        show: false, // 默认隐藏,等鼠标移到tooltip上再显示
      });
    }
  };

  // 监听自带tooltip隐藏事件
  const handleTooltipHide = () => {
    // 延迟隐藏,避免鼠标移到自定义提示框时立刻消失
    setTimeout(() => {
      if (extraTooltip?.show === false) {
        setExtraTooltip(null);
      }
    }, 100);
  };

  // 鼠标进入自带tooltip时,显示自定义提示框
  const handleTooltipMouseEnter = () => {
    if (extraTooltip) {
      setExtraTooltip({ ...extraTooltip, show: true });
    }
  };

  // 鼠标离开自定义提示框时,隐藏它并清空状态
  const handleExtraTooltipLeave = () => {
    setExtraTooltip(null);
  };

  return (
    <>
      <ReactECharts
        option={option}
        style={{ height: 400, width: "100%" }}
        onEvents={{
          tooltipshow: handleTooltipShow,
          tooltiphide: handleTooltipHide,
          // 监听自带tooltip的鼠标进入事件
          tooltipmouseenter: handleTooltipMouseEnter,
        }}
      />
      {extraTooltip && extraTooltip.show &&
        ReactDOM.createPortal(
          <div
            onMouseLeave={handleExtraTooltipLeave}
            style={{
              position: "fixed",
              top: extraTooltip.y - 60, // 调整位置,避免和自带tooltip重叠
              left: extraTooltip.x + 80,
              background: "white",
              border: "1px solid #ccc",
              padding: "10px",
              borderRadius: "4px",
              boxShadow: "0 2px 4px rgba(0,0,0,0.2)",
              zIndex: 1001, // 层级要比自带tooltip高
            }}
          >
            {extraTooltip.details}
          </div>,
          document.body,
        )}
    </>
  );
};

export default BarChartUpdated;

关键改动说明

  • 开启Tooltip的可进入性:在ECharts tooltip配置里加了enterable: true,这样鼠标才能穿透tooltip触发内部的鼠标事件。
  • 改用ECharts专属事件:放弃监听柱状图的mouseover,转而使用tooltipshow、tooltiphide、tooltipmouseenter这些ECharts提供的tooltip相关事件,能更精准地控制提示框的显示时机,还能直接拿到tooltip的准确位置。
  • 优化定位逻辑:用ECharts返回的position数据来设置自定义提示框的位置,避免了容器偏移带来的定位错误,同时调整了自定义框的位置,避免和自带tooltip重叠。
  • 简化状态管理:去掉了复杂的isVisible切换逻辑,直接通过tooltipmouseenter来触发自定义框的显示,鼠标离开自定义框时直接清空状态,交互更顺畅。

备注:内容来源于stack exchange,提问作者Aakarsha Kodthiwada

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 14:02:58