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

Highcharts多饼图图例点击联动及悬停效果实现求助

解决Highcharts多饼图图例联动与悬停高亮问题

我帮你梳理下问题并给出修复方案,你的核心需求是在最多6个饼图的图表中,实现图例点击同步所有饼图对应分类的显示/隐藏,以及图例悬停时对应所有饼图扇区高亮,现在遇到的问题是超过2个饼图时联动失效,点击事件无输出,这是因为你之前手动绑定DOM事件的方式不够可靠,而且没有利用Highcharts原生的事件钩子。

问题根源

  1. 你通过jQuery绑定第一个系列的图例项DOM元素的点击事件,这种方式依赖图表加载后的DOM结构,当多个饼图重新布局后,事件绑定可能失效,而且Highcharts本身提供了更稳定的legendItemClick事件。
  2. 没有设置悬停同步的逻辑,需要监听点的鼠标进出事件来同步所有饼图的高亮状态。
  3. resize事件中未正确获取seriesList变量,导致resize时出错。

修复后的完整代码

var options = {
    chart: {
        type: 'pie',
        renderTo: 'container',
        width: null,
        height: 800,
        events: {
            load: function() {
                var chart = this;
                var currWidth = chart.chartWidth;
                var currHeight = chart.chartHeight;
                var seriesList = chart.series;
                var seriesCount = seriesList.length;
                var piesize = CalcSize(currWidth, currHeight, seriesCount);
                var numCols;
                var numRows;
                var locArray = [];
                if (seriesCount == 6) {
                    numCols = 2;
                    numRows = 3;
                    for (nR = 0; nR < numRows; nR++) {
                        for (nC = 0; nC < numCols; nC++) {
                            if (locArray.length <= seriesCount - 1) {
                                locArray.push([((piesize / 2) + (piesize * nC)) + 60, (piesize / 2) + (piesize * nR)])
                            }
                        }
                    }
                } else {
                    numCols = currWidth / piesize;
                    numRows = Math.floor(currHeight / piesize);
                    for (nR = 0; nR < numRows; nR++) {
                        for (nC = 0; nC < numCols; nC++) {
                            if (locArray.length <= seriesCount - 1) {
                                locArray.push([(piesize / 2) + (piesize * nC), ((piesize / 2) + (piesize * nR)) - 75])
                            }
                        }
                    }
                }
                locArray.sort();
                $(seriesList).each(function(j) {
                    seriesList[j].update({
                        size: piesize,
                        center: locArray[j]
                    })
                });
            },
            resize: function() {
                var chart = this;
                var seriesList = chart.series; // 修复:获取当前图表的系列列表
                var piesize = CalcSize(chart.chartWidth, chart.chartHeight, seriesList.length);
                var locArray = [];
                // 重新计算布局,保持和load事件一致的逻辑
                if (seriesList.length == 6) {
                    numCols = 2;
                    numRows = 3;
                    for (nR = 0; nR < numRows; nR++) {
                        for (nC = 0; nC < numCols; nC++) {
                            if (locArray.length <= seriesList.length - 1) {
                                locArray.push([((piesize / 2) + (piesize * nC)) + 60, (piesize / 2) + (piesize * nR)])
                            }
                        }
                    }
                } else {
                    numCols = chart.chartWidth / piesize;
                    numRows = Math.floor(chart.chartHeight / piesize);
                    for (nR = 0; nR < numRows; nR++) {
                        for (nC = 0; nC < numCols; nC++) {
                            if (locArray.length <= seriesList.length - 1) {
                                locArray.push([(piesize / 2) + (piesize * nC), ((piesize / 2) + (piesize * nR)) - 75])
                            }
                        }
                    }
                }
                locArray.sort();
                $(seriesList).each(function(j) {
                    seriesList[j].update({
                        center: locArray[j]
                    })
                });
            }
        }
    },
    title: {
        text: null
    },
    xAxis: {
        categories: ['Not Specified', 'Entry Level', 'Less than 1 year', '1 Year to 2 Years', '2 Years to 5 Years', '5 Years to 10 Years', 'More than 10 Years'],
        labels: {
            style: {}
        }
    },
    series: [{
        name: 'Chattanooga, TN-GA MSA',
        visible: true,
        showInLegend: true,
        dataLabels: {
            enabled: false
        },
        tooltip: {
            valueDecimals: null,
            valuePrefix: null,
            valueSuffix: null
        },
        data: [
            ['Not Specified', 12],
            ['Entry Level', 1],
            ['Less than 1 year', 0],
            ['1 Year to 2 Years', 0],
            ['2 Years to 5 Years', 4],
            ['5 Years to 10 Years', 1],
            ['More than 10 Years', 0]
        ]
    }, {
        name: 'Jackson, TN MSA',
        visible: true,
        showInLegend: false,
        dataLabels: {
            enabled: false
        },
        tooltip: {
            valueDecimals: null,
            valuePrefix: null,
            valueSuffix: null
        },
        data: [
            ['Not Specified', 1],
            ['Entry Level', 0],
            ['Less than 1 year', 0],
            ['1 Year to 2 Years', 0],
            ['2 Years to 5 Years', 2],
            ['5 Years to 10 Years', 0],
            ['More than 10 Years', 0]
        ]
    }, {
        name: 'Kingsport-Bristol-Bristol, TN-VA MSA',
        visible: true,
        showInLegend: false,
        dataLabels: {
            enabled: false
        },
        tooltip: {
            valueDecimals: null,
            valuePrefix: null,
            valueSuffix: null
        },
        data: [
            ['Not Specified', 0],
            ['Entry Level', 0],
            ['Less than 1 year', 0],
            ['1 Year to 2 Years', 1],
            ['2 Years to 5 Years', 2],
            ['5 Years to 10 Years', 0],
            ['More than 10 Years', 0]
        ]
    }, {
        name: 'Knoxville, TN MSA',
        visible: true,
        showInLegend: false,
        dataLabels: {
            enabled: false
        },
        tooltip: {
            valueDecimals: null,
            valuePrefix: null,
            valueSuffix: null
        },
        data: [
            ['Not Specified', 27],
            ['Entry Level', 2],
            ['Less than 1 year', 0],
            ['1 Year to 2 Years', 1],
            ['2 Years to 5 Years', 4],
            ['5 Years to 10 Years', 0],
            ['More than 10 Years', 0]
        ]
    }, {
        name: 'Memphis, TN-MS-AR MSA',
        visible: true,
        showInLegend: false,
        dataLabels: {
            enabled: false
        },
        tooltip: {
            valueDecimals: null,
            valuePrefix: null,
            valueSuffix: null
        },
        data: [
            ['Not Specified', 45],
            ['Entry Level', 18],
            ['Less than 1 year', 0],
            ['1 Year to 2 Years', 14],
            ['2 Years to 5 Years', 31],
            ['5 Years to 10 Years', 2],
            ['More than 10 Years', 0]
        ]
    }, {
        name: 'Nashville-Davidson--Murfreesboro, TN MSA',
        visible: true,
        showInLegend: false,
        dataLabels: {
            enabled: false
        },
        tooltip: {
            valueDecimals: null,
            valuePrefix: null,
            valueSuffix: null
        },
        data: [
            ['Not Specified', 176],
            ['Entry Level', 10],
            ['Less than 1 year', 0],
            ['1 Year to 2 Years', 16],
            ['2 Years to 5 Years', 31],
            ['5 Years to 10 Years', 3],
            ['More than 10 Years', 0]
        ]
    }],
    tooltip: {
        useHTML: false,
        hideDelay: 75,
        valuePrefix: null,
        valueSuffix: null
    },
    subtitle: {
        text: null
    },
    plotOptions: {
        pie: {
            allowPointSelect: true,
            borderWidth: 0,
            dataLabels: {
                enabled: false
            },
            // 图例点击事件:同步所有饼图对应分类的显示/隐藏
            legendItemClick: function() {
                var categoryName = this.name;
                var chart = this.series.chart;
                // 遍历所有系列
                chart.series.forEach(function(series) {
                    // 找到当前系列中同名的点
                    var point = series.data.find(p => p.name === categoryName);
                    if (point) {
                        // 切换显示状态,和当前图例项保持一致
                        point.setVisible(!point.visible);
                    }
                });
                // 返回false阻止默认行为,避免重复切换
                return false;
            },
            point: {
                events: {
                    // 鼠标悬停时高亮所有饼图中同名的点
                    mouseOver: function() {
                        var categoryName = this.name;
                        var chart = this.series.chart;
                        chart.series.forEach(function(series) {
                            var point = series.data.find(p => p.name === categoryName);
                            if (point) {
                                point.setState('hover');
                            }
                        });
                    },
                    // 鼠标离开时取消所有饼图中同名点的高亮
                    mouseOut: function() {
                        var categoryName = this.name;
                        var chart = this.series.chart;
                        chart.series.forEach(function(series) {
                            var point = series.data.find(p => p.name === categoryName);
                            if (point) {
                                point.setState('');
                            }
                        });
                    }
                }
            }
        }
    }
};

function CalcSize(width, height, numberCharts) {
    var number = numberCharts;
    var width = width;
    var height = height;
    var area = height * width;
    var elementArea = parseInt(area / number);
    var sideLength = parseInt(Math.sqrt(elementArea));
    var numX = Math.ceil(width / sideLength);
    sideLength = width / numX;
    while (numX <= number) {
        if (Math.floor(height / sideLength) * numX >= number) {
            return sideLength;
        }
        numX++;
        sideLength = width / numX;
    }
    sideLength = height;
    return sideLength;
}

var newChart = new Highcharts.Chart(options);

关键修复点说明

  1. 图例点击同步:
    用plotOptions.pie.legendItemClick替代手动DOM事件绑定,这个事件是Highcharts原生提供的,点击任何图例项都会触发,我们在事件中遍历所有系列,找到同名的点并切换显示状态,保证所有饼图同步。

  2. 悬停高亮同步:
    在plotOptions.pie.point.events中添加mouseOver和mouseOut事件,当鼠标悬停在某个扇区时,遍历所有系列找到同名点,调用setState('hover')设置高亮;离开时调用setState('')取消高亮,实现所有饼图的同步高亮。

  3. resize事件修复:
    原来的resize事件中没有定义seriesList,现在改为在函数内部获取chart.series,并且重新计算饼图的位置,保证窗口大小变化时饼图布局正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:02:11