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

如何实现点击悬停中的hover template使其持久显示并可隐藏?

Plotly 实现点击标记持久显示Hover模板的方案

思路分析

你提到的通过添加trace实现持久hover的方向是正确的,但直接为每个点击点新建trace会导致性能问题,需要优化。另外还有更轻量的annotations方案可以选择,两种方案各有优劣:

方案一:复用单个Trace实现持久Hover

核心思路是维护一个专门用于显示持久hover的Trace,所有需要固定显示hover的点都添加到这个Trace中,避免创建大量Trace导致卡顿。

实现代码示例

// 假设原数据为data,布局为layout
// 初始化时添加一个空的持久hover Trace
const persistedTrace = {
  x: [],
  y: [],
  customdata: [],
  hovertemplate: data[0].hovertemplate, // 和原Trace的hover模板保持一致
  mode: 'markers',
  marker: { size: 0 }, // 隐藏标记本身,只保留hover提示
  name: 'Persisted Hover'
};
data.push(persistedTrace);

const fig = Plotly.newPlot('myDiv', data, layout);

// 存储已持久化的点标识(避免重复添加)
const persistedPoints = new Set();

// 监听点击事件
fig.on('plotly_click', function(clickData) {
  const point = clickData.points[0];
  const pointKey = `${point.curveNumber}-${point.pointIndex}`;

  if (persistedPoints.has(pointKey)) {
    // 移除已持久化的点
    const targetIndex = persistedTrace.x.indexOf(point.x);
    if (targetIndex !== -1) {
      persistedTrace.x.splice(targetIndex, 1);
      persistedTrace.y.splice(targetIndex, 1);
      persistedTrace.customdata.splice(targetIndex, 1);
    }
    persistedPoints.delete(pointKey);
  } else {
    // 添加需要持久化的点
    persistedTrace.x.push(point.x);
    persistedTrace.y.push(point.y);
    persistedTrace.customdata.push(point.customdata);
    persistedPoints.add(pointKey);
  }

  // 更新图表的持久hover Trace
  Plotly.update('myDiv', [persistedTrace], {}, [data.length - 1]);
});

原理说明

这个方案通过复用单个Trace来承载所有需要持久显示hover的点,每次点击只更新该Trace的数据,不会产生大量Trace占用资源。由于该Trace的hover模板和原数据一致,鼠标悬停在这些点上时会自动显示对应的hover内容,同时不影响其他点的正常hover交互。

方案二:用Annotations模拟持久Hover框

如果觉得Trace方案仍有性能顾虑,可以用Annotations模拟原生hover框的样式,点击时创建/删除对应Annotation,无需额外Trace。

实现代码示例

const fig = Plotly.newPlot('myDiv', data, layout);

// 存储已创建的持久化Annotation的标识
const persistedAnnotations = new Map();

// 监听点击事件
fig.on('plotly_click', function(clickData) {
  const point = clickData.points[0];
  const pointKey = `${point.curveNumber}-${point.pointIndex}`;

  if (persistedAnnotations.has(pointKey)) {
    // 删除已存在的Annotation
    const annoId = persistedAnnotations.get(pointKey);
    fig.layout.annotations = fig.layout.annotations.filter(anno => anno.id !== annoId);
    persistedAnnotations.delete(pointKey);
    Plotly.relayout('myDiv', 'annotations', fig.layout.annotations);
    return;
  }

  // 生成hover内容(替换模板中的占位符)
  let hoverContent = point.fullData.hovertemplate;
  hoverContent = hoverContent.replace('%{x}', point.x)
                             .replace('%{y}', point.y)
                             .replace('%{customdata}', point.customdata || '');
  // 可根据实际需求替换更多占位符,比如%{text}等

  // 创建模拟原生hover样式的Annotation
  const newAnnotation = {
    id: pointKey,
    x: point.x,
    y: point.y,
    xref: 'x',
    yref: 'y',
    text: hoverContent,
    showarrow: true,
    arrowhead: 1,
    ax: 20,
    ay: -30,
    bgcolor: '#ffffff',
    bordercolor: '#444444',
    borderwidth: 1,
    font: {
      family: 'Arial',
      size: 12,
      color: '#000000'
    },
    opacity: 0.9
  };

  // 添加Annotation并更新图表
  fig.layout.annotations = [...(fig.layout.annotations || []), newAnnotation];
  persistedAnnotations.set(pointKey, pointKey);
  Plotly.relayout('myDiv', 'annotations', fig.layout.annotations);
});

原理说明

该方案直接创建和原生hover框样式一致的Annotation,点击标记时添加Annotation,再次点击则删除。这种方式不需要额外Trace,性能开销极小,适合数据量较大的场景,唯一需要注意的是要根据自己的hover模板调整占位符的替换逻辑,保证显示内容和原生hover一致。

关于你提到的官方示例的说明

你参考的官方示例核心逻辑是:监听hover事件记录当前悬停的点,点击时将该点的数据复制到一个新的高亮Trace中,通过这个Trace来保留hover提示。但它的问题是每次点击都会新建一个Trace,当点击次数多了之后,大量Trace会导致浏览器卡顿。优化的关键就是复用单个Trace,而不是每次新建,这样就能解决性能问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 11:02:52