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

Highcharts中Packed Bubble钻取至Scatter图表不渲染问题求助

问题:Packed Bubble钻取Scatter图表渲染失败+主气泡钻取合并数据需求

我用React开发,现有一个Packed Bubble图表,想实现点击气泡钻取到对应Scatter图表的功能,另外还希望主气泡(比如ABC、XYZ)能钻取到合并该分类下所有Scatter数据的图表(不确定是否可行)。但目前点击气泡钻取后,Scatter图表无法正常渲染,以下是我的代码和问题截图:

component.js代码

import React, { useEffect, useState } from "react";
import { Spinner } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.min.css";
import ScatterChart from "./scatter";
import "./App.css";

const ScatterComponent = ({ data, filteredData }) => {
  const [loading, setLoading] = useState(true);
  const chartData = filteredData.length !== 0 ? filteredData : data;

  const bubbleSeries = [{"name":"ABC","data":[{"name":"page_views","value":6652301,
  "drilldown":"ABC - page_views"},{"name":"article_views","value":4812992,"drilldown":"ABC - article_views"},
  {"name":"visits","value":5116176,"drilldown":"ABC - visits"},]},
  {"name":"XYZ","data":[{"name":"page_views","value":16448241,"drilldown":"XYZ - page_views"},
  {"name":"article_views","value":10791478,"drilldown":"XYZ - article_views"},
  {"name":"visits","value":11921915,"drilldown":"XYZ - visits"},]}];

  const scatterSeries = [{"type":"scatter","id":"ABC - page_views",
  "data":[["2024-02-27T00:00:00.000Z",1],["2024-02-26T00:00:00.000Z",1],["2024-02-27T00:00:00.000Z",1],["2024-02-28T00:00:00.000Z",1]],},
  {"type":"scatter","id":"ABC - article_views",
  "data":[["2024-02-27T00:00:00.000Z",1],["2024-02-26T00:00:00.000Z",1],["2024-02-27T00:00:00.000Z",1],["2024-02-28T00:00:00.000Z",1]],},
  {"type":"scatter","id":"ABC - visits",
  "data":[["2024-02-27T00:00:00.000Z",1],["2024-02-26T00:00:00.000Z",1],["2024-02-27T00:00:00.000Z",1],["2024-02-28T00:00:00.000Z",1]],},
  {"type":"scatter","id":"XYZ - page_views",
  "data":[["2024-02-27T00:00:00.000Z",1],["2024-02-26T00:00:00.000Z",1],["2024-02-27T00:00:00.000Z",1],["2024-02-28T00:00:00.000Z",1]],},
  {"type":"scatter","id":"XYZ - article_views",
  "data":[["2024-02-27T00:00:00.000Z",1],["2024-02-26T00:00:00.000Z",1],["2024-02-27T00:00:00.000Z",1],["2024-02-28T00:00:00.000Z",1]],},
  {"type":"scatter","id":"XYZ - visits",
  "data":[["2024-02-27T00:00:00.000Z",1],["2024-02-26T00:00:00.000Z",1],["2024-02-27T00:00:00.000Z",1],["2024-02-28T00:00:00.000Z",1]],},
]
  


  useEffect(() => {
    const delay = setTimeout(() => {
      setLoading(false);
    }, 2000);

    return () => clearTimeout(delay);
  }, []);

  return (
    <div className="bubbleScatterTrends clearfix w-100 column">
      <h3 className="mt-1 ms-1" style={{ color: "#81b0d2" }}>
        <u>ABC-XYZ Breakdowns</u>
      </h3>
      {loading ? (
        <div className="text-center">
          <Spinner animation="border" variant="primary" />
          <Spinner animation="border" variant="secondary" />
          <Spinner animation="border" variant="success" />
          <Spinner animation="border" variant="danger" />
          <Spinner animation="border" variant="warning" />
          <Spinner animation="border" variant="info" />
          <Spinner animation="border" variant="light" />
          <Spinner animation="border" variant="dark" />
        </div>
      ) : (
        <div className="clearfix w-100 column">
          <div className="clearfix w-100 column">
            <div className="w-100 bubbleScatterCharts">
              <ScatterChart
                bubbleData={bubbleSeries}
                scatterData={scatterSeries}
              />
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

export default ScatterComponent;

scatter.js代码

import React, { useMemo } from "react";
import Highcharts from "highcharts";
import HighchartsReact from "highcharts-react-official";
import highchartsAccessibility from "highcharts/modules/accessibility";
import highchartsExporting from "highcharts/modules/exporting";
import highchartsExportData from "highcharts/modules/export-data";
import highchartsPackedbubble from "highcharts/highcharts-more";
import dayjs from "dayjs";
import "dayjs/locale/es";

highchartsAccessibility(Highcharts);
highchartsExporting(Highcharts);
highchartsExportData(Highcharts);
highchartsPackedbubble(Highcharts);

const ScatterChart = ({ bubbleData, scatterData }) => {
  dayjs.locale("en");
  const addCommas = (x) =>
    x.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ",");

  const options = useMemo(
    () => ({
      chart: {
        type: "packedbubble",
        backgroundColor: "#283347",
      },
      exporting: {
        enabled: true,
      },
      navigation: {
        buttonOptions: {
          verticalAlign: "top",
          y: -10,
          x: -5,
        },
      },
      accessibility: {
        enabled: false,
      },
      credits: {
        enabled: false,
      },
      legend: {
        enabled: true,
        itemStyle: {
          color: "#fff",
        },
      },
      tooltip: {
        backgroundColor: "#283347",
        style: { color: "#fff" },
        formatter: function () {
          if (this.y !== undefined || this.key !== undefined) {
            let tooltip = `<span><b><u>${this.key}</u></b>: ${addCommas(
              this.y
            )}</span>`;
            return tooltip;
          }
        },
        useHTML: true,
      },
      plotOptions: {
        packedbubble: {
          minSize: "20%",
          maxSize: "100%",
          zMin: 0,
          zMax: 1000,
          layoutAlgorithm: {
            gravitationalConstant: 0.05,
            splitSeries: true,
            seriesInteraction: false,
            dragBetweenSeries: true,
            parentNodeLimit: true,
          },
          dataLabels: {
            enabled: true,
            format: "{point.name}",
            filter: {
              property: "y",
              operator: ">",
              value: 250,
            },
            style: {
              color: "black",
              textOutline: "none",
              fontWeight: "normal",
            },
          },
        },
      },
      series: bubbleData,
      drilldown: {
        series: scatterData,
      },
    }),
    [bubbleData, scatterData]
  );

  return <HighchartsReact highcharts={Highcharts} options={options} />;
};

export default ScatterChart;

问题截图

钻取后异常的Scatter图表


解决方案

1. 钻取后Scatter渲染失败的核心原因及修复

Highcharts钻取默认保留原图表类型(此处为packedbubble),但Scatter图表需要明确的坐标轴配置和类型切换。修改scatter.js,通过事件监听动态切换图表配置:

import React, { useMemo } from "react";
import Highcharts from "highcharts";
import HighchartsReact from "highcharts-react-official";
import highchartsAccessibility from "highcharts/modules/accessibility";
import highchartsExporting from "highcharts/modules/exporting";
import highchartsExportData from "highcharts/modules/export-data";
import highchartsPackedbubble from "highcharts/highcharts-more";
import dayjs from "dayjs";
import "dayjs/locale/es";

highchartsAccessibility(Highcharts);
highchartsExporting(Highcharts);
highchartsExportData(Highcharts);
highchartsPackedbubble(Highcharts);

const ScatterChart = ({ bubbleData, scatterData }) => {
  dayjs.locale("en");
  const addCommas = (x) =>
    x.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ",");

  // 预处理主气泡的合并数据
  const mergedScatterData = useMemo(() => {
    const mergedMap = {};
    scatterData.forEach(series => {
      const mainKey = series.id.split(" - ")[0];
      if (!mergedMap[mainKey]) {
        mergedMap[mainKey] = {
          type: "scatter",
          id: `${mainKey}-merged`,
          name: `${mainKey} 全量数据`,
          data: []
        };
      }
      mergedMap[mainKey].data = [...mergedMap[mainKey].data, ...series.data];
    });
    return [...scatterData, ...Object.values(mergedMap)];
  }, [scatterData]);

  const options = useMemo(
    () => ({
      chart: {
        type: "packedbubble",
        backgroundColor: "#283347",
        events: {
          // 钻取时切换为Scatter类型并配置坐标轴
          drilldown: function(e) {
            this.update({
              chart: { type: "scatter" },
              xAxis: {
                type: "datetime",
                labels: { style: { color: "#fff" } },
                lineColor: "#fff",
                tickColor: "#fff"
              },
              yAxis: {
                labels: { style: { color: "#fff" } },
                lineColor: "#fff",
                tickColor: "#fff",
                title: { text: "数值", style: { color: "#fff" } }
              }
            }, false);
          },
          // 返回时恢复为Packed Bubble类型
          drillup: function() {
            this.update({
              chart: { type: "packedbubble" },
              xAxis: {},
              yAxis: {}
            }, false);
          }
        }
      },
      exporting: { enabled: true },
      navigation: {
        buttonOptions: { verticalAlign: "top", y: -10, x: -5 }
      },
      accessibility: { enabled: false },
      credits: { enabled: false },
      legend: { enabled: true, itemStyle: { color: "#fff" } },
      tooltip: {
        backgroundColor: "#283347",
        style: { color: "#fff" },
        formatter: function () {
          if (this.point) {
            // Scatter图表显示日期+数值
            if (this.series.type === "scatter") {
              return `<span><b><u>${dayjs(this.x).format("YYYY-MM-DD")}</u></b>: ${this.y}</span>`;
            }
            // Packed Bubble显示名称+格式化数值
            return `<span><b><u>${this.key}</u></b>: ${addCommas(this.y)}</span>`;
          }
        },
        useHTML: true
      },
      plotOptions: {
        packedbubble: {
          minSize: "20%",
          maxSize: "100%",
          zMin: 0,
          zMax: 1000,
          layoutAlgorithm: {
            gravitationalConstant: 0.05,
            splitSeries: true,
            seriesInteraction: false,
            dragBetweenSeries: true,
            parentNodeLimit: true
          },
          dataLabels: {
            enabled: true,
            format: "{point.name}",
            filter: { property: "y", operator: ">", value: 250 },
            style: { color: "black", textOutline: "none", fontWeight: "normal" }
          }
        }
      },
      // 给主气泡添加合并数据的钻取关联
      series: bubbleData.map(series => ({
        ...series,
        drilldown: `${series.name}-merged`
      })),
      drilldown: {
        series: mergedScatterData,
        // 添加钻取返回按钮
        drillUpButton: {
          theme: { fill: "#283347", stroke: "#fff", style: { color: "#fff" } },
          relativeTo: "spacingBox",
          position: { x: 0, y: -10 }
        }
      }
    }),
    [bubbleData, mergedScatterData]
  );

  return <HighchartsReact highcharts={Highcharts} options={options} />;
};

export default ScatterChart;

2. 主气泡钻取合并数据的实现逻辑

  • 通过useMemo遍历scatterData,按主气泡名称(ABC/XYZ)合并对应子项的Scatter数据,生成新的合并系列
  • 给主气泡的series添加drilldown属性,指向合并系列的id
  • 钻取时自动加载合并后的全量Scatter数据

关键修复点总结

  • 钻取事件中动态切换图表类型为scatter,并配置datetime类型的x轴和y轴(Scatter必须依赖坐标轴渲染)
  • 钻取返回事件中恢复原packedbubble类型,清空坐标轴配置
  • 预处理合并主气泡对应的所有Scatter数据,实现主气泡的合并钻取需求
  • 适配两种图表类型的tooltip显示格式

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 04:58:09