如何用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
相关产品推荐
相关产品推荐

