Highcharts 高阶交互 Demo|极坐标气泡图+ 环形饼图

1990-2020年发现行星展示图表

太阳系外离地球最近的240颗行星的恒星图。在这张恒星图中,气泡的大小与行星的质量成正比。气泡到图中心的距离大致与到地球的距离有关。气泡颜色代表每个行星的恒星大小,即从地球上看它在天空中的大小。在中心,你会发现四组行星,按它们被发现的年代分组。

图表功能说明:

外层极坐标气泡:系系系外行星,气泡大小 = 行星质量,径向距离 = 离地球光年,颜色 = 发现年代;

内层环形饼图:按年代统计行星数量,鼠标悬浮饼扇区,外层气泡自动变暗过滤,只保留对应年代行星高亮;中间动态文字,展示选中年代占比。

js 复制代码
const colors = ['#6CDDCA', '#C771F3', '#4D90DB', '#FAB776'];

Highcharts.chart('container', {
    chart: {
        type: 'bubble',
        polar: true,
        height: 600,
        events: {
            render() {
                const chart = this,
                    pieSeries = chart.series[1];
                pieSeries.customLabel = fillCenter(
                    100,
                    '1990-2020',
                    chart,
                    pieSeries.customLabel
                );
            }
        }
    },
    data: {
        csv: document.getElementById('csv').innerHTML,
        seriesMapping: [{
            name: 0,
            x: 1,
            y: 2,
            z: 3,
            'custom.lightYears': 4,
            'custom.planetMass': 5,
            'custom.stellarMagnitude': 6,
            'custom.discoveryDate': 7
        }]
    },
    title: {
        text: 'The 240 Closest Planets to the Earth other than our solar system'
    },
    subtitle: {
        text: 'Using bubble series in polar coordinate system along with pie ' +
            'series'
    },
    colorAxis: {
        dataClasses: [{
            from: 1990,
            to: 2000,
            color: colors[0]
        }, {
            from: 2000,
            to: 2010,
            color: colors[1]
        }, {
            from: 2010,
            to: 2020,
            color: colors[2]
        }, {
            from: 2020,
            color: colors[3]
        }]
    },
    legend: {
        enabled: false
    },
    pane: {
        innerSize: '42%',
        size: '95%',
        background: [{
            backgroundColor: 'var(--highcharts-neutral-color-3, #f7f7f7)',
            borderWidth: 0
        }, {
            backgroundColor: 'var(--highcharts-background-color, #fff)',
            borderWidth: 0,
            outerRadius: '42%'
        }]
    },
    xAxis: {
        tickInterval: 1,
        min: 0,
        max: 30,
        gridLineWidth: 0,
        labels: {
            enabled: false
        },
        lineWidth: 0
    },
    yAxis: {
        tickInterval: 1,
        labels: {
            enabled: false
        },
        gridLineWidth: 0.5,
        gridLineColor: '#BBBAC5',
        gridLineDashStyle: 'longdash',
        endOnTick: false
    },
    tooltip: {
        borderWidth: 1,
        shadow: false,
        outside: true,
        hideDelay: 20,
        useHTML: true,
        formatter: function (tooltip) {
            // Hide tooltip for pie series
            if (this.series.options.type === 'pie') {
                return false;
            }
            // If not null, use the default formatter
            return tooltip.defaultFormatter.call(this, tooltip);
        }
    },
    plotOptions: {
        series: {
            states: {
                inactive: {
                    enabled: false
                }
            }
        },
        pie: {
            states: {
                hover: {
                    halo: 0
                }
            }
        }
    },
    series: [{
        colorKey: 'custom.discoveryDate',
        maxSize: 14,
        minSize: 3,
        tooltip: {
            pointFormat: `<table>
                <tr>
                    <td style='padding:0'>
                        <span class="smallerLabel">Name:</span>
                        {point.name}
                    </td>
                </tr>
                <tr>
                    <td style='padding:0'>
                        <span class="smallerLabel">Mass:</span>
                        {point.custom.planetMass}
                    </td>
                </tr>
                <tr>
                    <td style='padding:0'>
                        <span class="smallerLabel">Distance:</span>
                        {point.custom.lightYears} Light Years
                    </td>
                </tr>
                <tr>
                    <td style='padding:0'>
                        <span class="smallerLabel">Stellar Magnitude:</span>
                        {point.custom.stellarMagnitude}
                    </td>
                </tr>
            </table>`
        }
    }, {
        type: 'pie',
        dataLabels: {
            enabled: false
        },
        size: '40%',
        innerSize: '85%',
        zIndex: -1,
        point: {
            events: {
                mouseOver() {
                    const {
                            minDate,
                            maxDate
                        } = this.options.custom,
                        point = this,
                        series = this.series,
                        chart = series.chart,
                        bubbleSeries = chart.series[0];

                    bubbleSeries.points.forEach(point => {
                        if (
                            point.options.custom.discoveryDate < minDate ||
                            point.options.custom.discoveryDate >= maxDate
                        ) {
                            point.graphic.attr({
                                opacity: 0.2
                            });
                        }
                    });

                    series.customLabel = fillCenter(
                        point.percentage,
                        point.options.custom.minDate,
                        chart,
                        series.customLabel
                    );
                },
                mouseOut() {
                    const chart = this.series.chart,
                        series = this.series,
                        bubbleSeries = chart.series[0];

                    bubbleSeries.points.forEach(point => {
                        point.graphic.attr({
                            opacity: 1
                        });
                    });
                    series.customLabel = fillCenter(
                        100,
                        '1990-2020',
                        chart,
                        series.customLabel
                    );
                }
            }
        },

        data: [{
            y: 12,
            color: '#6CDDCA',
            custom: {
                minDate: 1990,
                maxDate: 2000
            }
        }, {
            y: 47,
            color: '#C771F3',
            custom: {
                minDate: 2000,
                maxDate: 2010
            }
        }, {
            y: 117,
            color: '#4D90DB',
            custom: {
                minDate: 2010,
                maxDate: 2020
            }
        }, {
            y: 64,
            color: '#FAB776',
            custom: {
                minDate: 2020,
                maxDate: 2030
            }
        }]
    }],
    responsive: {
        rules: [{
            condition: {
                maxWidth: 500
            },
            chartOptions: {
                pane: {
                    innerSize: '47%'
                },
                series: [{
                    maxSize: 8
                }, {
                    size: '45%'
                }]
            }
        }]
    }
});

function fillCenter(percentage, decade, chart, customLabel) {
    const labelText = `<div class="center-label">
        <p class="symbol">☉</p>
        <p class="uppercase">${decade}<small>s</p>
        <p class="label">Planets discovered</p>
        <p class="percentage">
            ${percentage.toFixed(2)}
            <sup style='font-size: 0.5em;'>%</sup>
        </p>
    </div>`;

    if (!customLabel) {
        customLabel = chart.renderer.label(
            labelText, 0, 0, void 0, void 0,
            void 0, true
        ).css({
            color: 'var(--highcharts-neutral-color-100, #000)',
            pointerEvents: 'none'
        }).add();
    } else {
        customLabel.attr({
            text: labelText
        });
    }
    customLabel.attr({
        x: (chart.pane[0].center[0] + chart.plotLeft) -
            customLabel.attr('width') / 2,
        y: (chart.pane[0].center[1] + chart.plotTop) -
            customLabel.attr('height') / 2 - 10
    });
    return customLabel;
}
<js>

一、图表简介

本案例是复合联动可视化极坐标气泡图(外层) + 环形饼图(内层),属于Highcharts高阶交互Demo。

  • 业务场景:多维数据展示,支持分类筛选联动,适合科研数据、市场多维分析、产品多维度分布可视化。
  • 原始业务:系外行星多维数据,包含:行星名称、距离地球光年、行星质量、发现年份、恒星星等。

视觉映射规则

  1. 极坐标径向长度(y轴):行星距离地球的光年,越靠外圈距离越远;
  2. 气泡大小z:行星质量,气泡越大,行星质量越大;
  3. 气泡颜色:按发现年份区间 着色(颜色分段colorAxis.dataClasses);
  4. 内层环形饼:统计各年代发现行星数量占比;
  5. 交互核心:鼠标悬浮内层饼扇区 → 外层气泡自动过滤,仅保留该年代行星高亮,其余气泡降低透明度淡化,中间文字动态更新占比;鼠标移出恢复全部。

二、核心配置解析

  1. polar:true 开启极坐标,图表从直角坐标系转为圆形画布
  2. bubble气泡系列:z字段控制气泡半径,colorKey绑定自定义字段custom.discoveryDate实现颜色分段
  3. seriesMapping:CSV字段映射,把csv列映射到point属性,支持自定义custom.*扩展字段
  4. 饼图zIndex=-1:放在底层,作为内环背景筛选控件
  5. chart.render事件:首次渲染中间文字标签
  6. fillCenter自定义渲染函数:用chart.renderer.label绘制SVG文本,放置在极坐标圆心,动态更新HTML文本

三、交互逻辑拆解

  1. 鼠标hover内环饼扇区 → 获取当前年代min/max
  2. 遍历外层气泡所有点,判断发现年份不在区间,设置opacity=0.2淡化
  3. 更新圆心文字,展示当前年代行星占比
  4. mouseOut事件:重置所有气泡透明度,圆心文字恢复默认

可选:替换业务数据改造方案

如果你想把这个图改成国内业务(如企业、项目、客户多维数据),只需要修改CSV部分:

  • name:项目名称
  • x/y:极坐标角度/径向距离
  • z:项目规模
  • custom字段:行业、营收、成立年份等多维属性
相关推荐
Highcharts.js2 天前
Highcharts 多Y轴折线图入门讲解:让不同单位的数据在一张图中正确表达
信息可视化·数据挖掘·数据分析·highcharts·图表工具·自定义图表·y轴多图
Highcharts.js4 天前
常见报错排雷指南2:导出失败的官方解法
javascript·react.js·ecmascript·highcharts·可视化图表·导出模块失败·导出服务
Highcharts.js4 天前
常见报错排雷指南4:坐标轴重叠、导出失败、兼容性问题的FAQ
开发工具·常见问题·highcharts·可视化图表·faq
Highcharts.js4 天前
常见报错排雷指南3:兼容性问题的官方解法
前端·javascript·react.js·可视化·highcharts·报错解法
Highcharts.js6 天前
金融行情/股票图、甘特图、地图用哪个库?
金融·可视化·甘特图·highcharts
Highcharts.js11 天前
Highcharts 数据可视化快速入门指南
信息可视化·highcharts·可视化图表·基础入门
Highcharts.js12 天前
甘特图纵向任务、横向时间显示图表可视化开发|Highcharts甘特图示例
开发语言·前端·javascript·可视化·甘特图·数据可视化·highcharts
Highcharts.js12 天前
Highcharts Orbit 管理器:让企业级可视化与数据分析实现平台化
信息可视化·数据挖掘·数据分析·可视化工具·highcharts·图表工具·管理器
Highcharts.js17 天前
Highcharts 渲染方式解析:SVG 默认渲染与 Boost 模块的 WebGL 加速,以及它与其他可视化库的不同
svg·webgl·可视化·canvas·highcharts·渲染方式