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;
问题截图

解决方案
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
相关产品推荐
相关产品推荐

