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

Highcharts散点图Tooltip悬停关闭及内部图表重复渲染问题

问题排查:Highcharts散点图Tooltip嵌套图表的两个异常问题

问题现象

  • 悬停Tooltip中上部区域时,Tooltip意外关闭
  • 悬停Tooltip内的图表时,图表持续重复渲染

原因分析

1. Tooltip意外关闭

  • 同时开启split: true和shared: true会拆分Tooltip结构,鼠标移至非数据点关联区域时触发关闭逻辑
  • stickyTracking: false导致鼠标离开散点后,Tooltip无法保持激活状态
  • Tooltip内部的图表容器未被识别为Tooltip的一部分,鼠标移至图表区域时被判定为离开Tooltip范围

2. 图表重复渲染

  • 监听Tooltip.refresh事件,该事件会在鼠标移动(包括在Tooltip内部移动)时频繁触发,导致每次都调用renderChart重新请求数据并渲染
  • 未缓存请求数据,也未销毁旧图表,既造成重复请求,也引发内存泄漏

解决方案

针对Tooltip关闭问题

  • 移除split: true和shared: true配置,单个散点不需要共享Tooltip
  • 保留stickOnContact: true,确保鼠标接触Tooltip时保持显示
  • 给Tooltip容器添加样式,让内部图表区域可以正常响应鼠标事件,同时避免事件穿透关闭Tooltip

针对重复渲染问题

  • 缓存已请求的数据,避免重复fetch
  • 仅当hover的散点发生变化时,才重新渲染内部图表
  • 渲染新图表前销毁旧图表,清理资源

修改后的完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://code.highcharts.com/stock/highstock.js"></script>
    <script src="https://code.highcharts.com/stock/modules/accessibility.js"></script>
    <style>
        /* 确保Tooltip内部图表可响应鼠标,同时避免关闭Tooltip */
        .highcharts-tooltip .hc-tooltip-container {
            pointer-events: auto;
        }
        .highcharts-tooltip {
            pointer-events: none;
        }
    </style>
</head>
<body>
    <div id="scatter_container" style="height: 90vh;"></div>

    <script>
        // 缓存请求的数据
        let cachedStockData = null;
        // 记录当前hover的点ID,避免重复渲染
        let currentHoverPointId = null;
        // 保存内部图表实例,用于销毁
        let innerChartInstance = null;

        async function getStockData() {
            if (cachedStockData) return cachedStockData;
            const data = await fetch(
                'https://demo-live-data.highcharts.com/aapl-c.json'
            ).then(response => response.json());
            cachedStockData = data;
            return data;
        }

        async function renderChart(point) {
            // 点未变化时不重复渲染
            if (point.id === currentHoverPointId) return;
            currentHoverPointId = point.id;

            // 销毁旧图表
            if (innerChartInstance) {
                innerChartInstance.destroy();
            }

            const data = await getStockData();
            // 创建新图表
            innerChartInstance = Highcharts.stockChart('hc-tooltip', {
                rangeSelector: {
                    selected: 1
                },
                title: {
                    text: 'AAPL Stock Price'
                },
                series: [{
                    name: 'AAPL',
                    data: data,
                    tooltip: {
                        valueDecimals: 2
                    }
                }]
            });
        }

        Highcharts.addEvent(
            Highcharts.Tooltip,
            'refresh',
            function () {
                const hoverPoint = this.chart.hoverPoint;
                if (hoverPoint) {
                    renderChart(hoverPoint);
                }
            }
        );

        const series = [{
            name: 'Basketball',
            id: 'basketball',
            marker: {
                symbol: 'circle',
                width: 32,
                height: 32
            }
        }];

        async function getData() {
            const response = await fetch('https://api.jsonbin.io/v3/qs/65b8d298dc746540189dacb7');
            return response.json();
        }

        getData().then(data => {
            const getData = sportName => {
                const temp = [];
                data.record.forEach(elm => {
                    temp.push([elm.height, elm.weight]);
                });
                return temp;
            };
            series.forEach(s => {
                s.data = getData(s.id);
            });

            Highcharts.chart('scatter_container', {
                chart: {
                    type: 'scatter',
                    zoomType: 'xy'
                },
                title: {
                    text: 'Olympics athletes by height and weight',
                    align: 'left'
                },
                subtitle: {
                    text:'',
                    align: 'left'
                },
                xAxis: {
                    title: {
                        text: 'Height'
                    },
                    labels: {
                        format: '{value} m'
                    },
                    startOnTick: true,
                    endOnTick: true,
                    showLastLabel: true
                },
                yAxis: {
                    title: {
                        text: 'Weight'
                    },
                    labels: {
                        format: '{value} kg'
                    }
                },
                legend: {
                    enabled: true
                },
                plotOptions: {
                    scatter: {
                        marker: {
                            radius: 2.5,
                            symbol: 'circle',
                            states: {
                                hover: {
                                    enabled: true,
                                    lineColor: 'rgb(100,100,100)'
                                }
                            }
                        },
                        states: {
                            hover: {
                                marker: {
                                    enabled: false
                                }
                            }
                        },
                        jitter: {
                            x: 0.005
                        }
                    }
                },
                credits:{
                    enabled:false
                },
                tooltip: {
                    stickOnContact: true,
                    useHTML: true,
                    headerFormat: '',
                    pointFormat: '<div id="hc-tooltip" class="hc-tooltip-container" style="width:320px;height:45vh;"></div>'
                },
                series
            });
        });
    </script>
</body>
</html>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 10:15:02