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

如何用dygraphs/highcharter实现双图联动高亮的交互式散点回归图

基于highcharter实现联动高亮的分面散点+回归曲线可视化方案

以下是用highcharter复现需求并实现交互式联动的具体方案,核心通过Highcharts的JavaScript事件监听实现跨图表的点高亮与连线、曲线同步高亮:

1. 数据预处理(计算二次回归预测值)

首先需要为每个type分组计算二次回归的预测数据,用于绘制回归曲线:

library(highcharter)
library(dplyr)
library(tidyr)

# 假设你的数据集名为df,包含mts2、price、type三个字段
# 计算每个type的二次回归预测曲线数据
df_reg <- df %>%
  group_by(type) %>%
  do({
    # 拟合二次线性回归模型
    model <- lm(price ~ poly(mts2, 2, raw = TRUE), data = .)
    # 生成连续x值对应的预测y值
    tibble(
      mts2 = seq(min(.$mts2), max(.$mts2), length.out = 100),
      pred_price = predict(model, newdata = data.frame(mts2 = seq(min(.$mts2), max(.$mts2), length.out = 100)))
    )
  }) %>%
  ungroup()

2. 创建联动的两个图表

以type分为两个独立图表为例,添加鼠标悬停联动逻辑:

图表1(type = "A")

hc1 <- df %>%
  filter(type == "A") %>%
  hchart(
    type = "scatter",
    hcaes(x = mts2, y = price),
    name = "散点图A"
  ) %>%
  # 添加二次回归曲线
  hc_add_series(
    data = df_reg %>% filter(type == "A"),
    type = "line",
    hcaes(x = mts2, y = pred_price),
    name = "回归曲线A",
    lineWidth = 2,
    color = "#FF5733"
  ) %>%
  # 设置高亮样式
  hc_plotOptions(
    scatter = list(
      states = list(
        hover = list(marker = list(radius = 8, fillColor = "#FFC300"))
      )
    ),
    line = list(
      states = list(
        hover = list(lineWidth = 4, color = "#FFC300")
      )
    )
  ) %>%
  # 添加联动事件监听
  hc_chart(
    events = list(
      load = JS("function() {
        var chart = this;
        // 散点悬停联动
        chart.series[0].points.forEach(function(point) {
          point.on('mouseOver', function() {
            var mts2Val = this.x;
            var chart2 = Highcharts.charts[1];
            // 匹配另一图表对应mts2的点(浮点精度问题可加误差范围判断)
            var targetPoint = chart2.series[0].points.find(p => Math.abs(p.x - mts2Val) < 0.01);
            if (targetPoint) {
              targetPoint.setState('hover');
              // 创建跨图表连线
              if (!chart2.customLine) {
                chart2.customLine = chart2.renderer.path(['M', chart.plotLeft + this.plotX, chart.plotTop + this.plotY, 'L', chart2.plotLeft + targetPoint.plotX, chart2.plotTop + targetPoint.plotY])
                  .attr({'stroke': '#333', 'stroke-width': 1, 'dashstyle': 'dash'}).add();
              } else {
                chart2.customLine.attr({d: ['M', chart.plotLeft + this.plotX, chart.plotTop + this.plotY, 'L', chart2.plotLeft + targetPoint.plotX, chart2.plotTop + targetPoint.plotY]});
              }
            }
          });
          // 鼠标移出取消高亮与连线
          point.on('mouseOut', function() {
            var chart2 = Highcharts.charts[1];
            var mts2Val = this.x;
            var targetPoint = chart2.series[0].points.find(p => Math.abs(p.x - mts2Val) < 0.01);
            if (targetPoint) targetPoint.setState('');
            if (chart2.customLine) {
              chart2.customLine.destroy();
              chart2.customLine = null;
            }
          });
        });
        // 回归曲线悬停联动
        chart.series[1].on('mouseOver', function() {
          Highcharts.charts[1].series[1].setState('hover');
        });
        chart.series[1].on('mouseOut', function() {
          Highcharts.charts[1].series[1].setState('');
        });
      }")
    )
  )

图表2(type = "B")

hc2 <- df %>%
  filter(type == "B") %>%
  hchart(
    type = "scatter",
    hcaes(x = mts2, y = price),
    name = "散点图B"
  ) %>%
  hc_add_series(
    data = df_reg %>% filter(type == "B"),
    type = "line",
    hcaes(x = mts2, y = pred_price),
    name = "回归曲线B",
    lineWidth = 2,
    color = "#33FF57"
  ) %>%
  hc_plotOptions(
    scatter = list(
      states = list(
        hover = list(marker = list(radius = 8, fillColor = "#FFC300"))
      )
    ),
    line = list(
      states = list(
        hover = list(lineWidth = 4, color = "#FFC300")
      )
    )
  ) %>%
  hc_chart(
    events = list(
      load = JS("function() {
        var chart = this;
        // 散点悬停联动
        chart.series[0].points.forEach(function(point) {
          point.on('mouseOver', function() {
            var mts2Val = this.x;
            var chart1 = Highcharts.charts[0];
            var targetPoint = chart1.series[0].points.find(p => Math.abs(p.x - mts2Val) < 0.01);
            if (targetPoint) {
              targetPoint.setState('hover');
              if (!chart1.customLine) {
                chart1.customLine = chart1.renderer.path(['M', chart.plotLeft + this.plotX, chart.plotTop + this.plotY, 'L', chart1.plotLeft + targetPoint.plotX, chart1.plotTop + targetPoint.plotY])
                  .attr({'stroke': '#333', 'stroke-width': 1, 'dashstyle': 'dash'}).add();
              } else {
                chart1.customLine.attr({d: ['M', chart.plotLeft + this.plotX, chart.plotTop + this.plotY, 'L', chart1.plotLeft + targetPoint.plotX, chart1.plotTop + targetPoint.plotY]});
              }
            }
          });
          point.on('mouseOut', function() {
            var chart1 = Highcharts.charts[0];
            var mts2Val = this.x;
            var targetPoint = chart1.series[0].points.find(p => Math.abs(p.x - mts2Val) < 0.01);
            if (targetPoint) targetPoint.setState('');
            if (chart1.customLine) {
              chart1.customLine.destroy();
              chart1.customLine = null;
            }
          });
        });
        // 回归曲线悬停联动
        chart.series[1].on('mouseOver', function() {
          Highcharts.charts[0].series[1].setState('hover');
        });
        chart.series[1].on('mouseOut', function() {
          Highcharts.charts[0].series[1].setState('');
        });
      }")
    )
  )

3. 显示图表

直接运行即可生成两个联动的交互式图表:

hc1
hc2

关键说明

  • 点匹配逻辑:通过Math.abs(p.x - mts2Val) < 0.01处理浮点精度问题,避免因数据精度导致匹配失败。
  • 图表索引:Highcharts.charts的索引按图表创建顺序排列,第一个创建的图表为Highcharts.charts[0],第二个为Highcharts.charts[1]。
  • 自定义样式:可通过hc_plotOptions调整悬停时点的大小、颜色,以及曲线的宽度、颜色,提升视觉效果。
  • 多type扩展:若type多于2个,可通过循环生成图表,并修改JS事件中的索引逻辑实现多图表联动。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 21:35:16