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

散点图图例切换时如何同步移除关联注释与气泡文本?

散点图图例切换时同步隐藏关联注释与气泡文本的实现方案

我制作了带图例、文本和注释的散点图,但点击图例切换系列显示/隐藏时,气泡关联的文本和注释不会跟着消失。以下是当前生成注释的代码,求实现点击图例按钮时同步移除对应注释和气泡文本的方法:

const getAnnotations = () => {
    if (totalRecords <= 10) {
        const annotations: any[] = [];
        const placedPositions: { x: number; y: number }[] = [];
        const groupedData = new Map<string, { storeLabels: string[], x: number, y: number, z: number }>();

        // 按total_sales和spearman_correlation分组数据
        data.forEach((item) => {
            const storeLabel = item[firstColumnFieldName] || "Unknown Store";
            const key = `${item.total_sales}-${item.spearman_correlation}`;
            if (!groupedData.has(key)) {
                groupedData.set(key, { storeLabels: [], x: item.total_sales, y: item.spearman_correlation, z: item.max_sales });
            }
            groupedData.get(key)!.storeLabels.push(storeLabel);
        });

        // 交替使用正负Y轴偏移量
        let isPositiveYOffset = true;

        groupedData.forEach(({ storeLabels, x, y, z }) => {
            // 仅保留相关系数超出±0.5的注释
            if (y <= 0.5 && y >= -0.5) return;

            const combinedLabel = storeLabels.join(", <br>");
            const isShortLabel = combinedLabel.length <= 3;

            // 计算气泡大小
            const bubbleSize = calculateBubbleSize(y, totalRecords, x, z, allLessThanOrEqualZero);

            // 判断标签是否能放入气泡内
            const labelFitsInsideBubble = !isLabelTooBig(combinedLabel, bubbleSize);

            const yOffset = isPositiveYOffset ? 30 : -30;
            isPositiveYOffset = !isPositiveYOffset;

            // 检查注释是否与已有气泡/注释重叠
            const isOverlapping = (x: number, y: number, bubbleSize: number) => {
                const annotationOverlap = placedPositions.some(pos => {
                    const distance = calculateDistance(pos.x, pos.y, x, y);
                    return distance < bubbleSize * 0.8;
                });

                const bubbleOverlap = placedPositions.some(pos => {
                    const distance = calculateDistance(pos.x, pos.y, x, y);
                    return distance < bubbleSize;
                });

                return annotationOverlap || bubbleOverlap;
            };

            let currentX = x;
            let currentY = y + yOffset;

            if (isOverlapping(currentX, currentY, bubbleSize)) {
                return;
            }

            placedPositions.push({ x: currentX, y: currentY });

            annotations.push({
                x: x,
                y: y,
                text: `<b>${combinedLabel}</b>`,
                showarrow: !labelFitsInsideBubble,
                arrowhead: 1,
                arrowwidth: 2,
                ax: labelFitsInsideBubble ? 10 : 30,
                ay: labelFitsInsideBubble ? yOffset : -yOffset,
                font: { size: labelFitsInsideBubble ? 12 : 10, color: labelFitsInsideBubble ? "white" : "black", weight: "800" },
                bgcolor: labelFitsInsideBubble ? "transparent" : "white",
                bordercolor: labelFitsInsideBubble ? "transparent" : "black",
                borderradius: 10,
                xanchor: isShortLabel ? "center" : undefined,
                yanchor: isShortLabel ? "middle" : undefined,
                arrowcolor: "black",
            });
        });

        return annotations;
    }
    return [];
};

实现方案

要让图例切换时同步隐藏对应注释和气泡文本,核心是关联图例选中状态与注释/文本的显示逻辑,以下是两种直接可行的方式:

方式1:基于可见系列过滤注释(推荐)

  1. 维护可见系列状态:在组件中声明状态变量,记录当前显示的系列名称集合:

    const [visibleSeries, setVisibleSeries] = useState(new Set<string>(/* 初始可见系列名称 */));
    
  2. 监听图例点击事件:绑定图表的图例点击事件,更新可见系列集合(以Plotly.js为例,其他图表库逻辑类似):

    chart.current.on('legendclick', (eventData) => {
      const seriesName = eventData.data[eventData.curveNumber].name;
      const newVisibleSeries = new Set(visibleSeries);
      if (newVisibleSeries.has(seriesName)) {
        newVisibleSeries.delete(seriesName);
      } else {
        newVisibleSeries.add(seriesName);
      }
      setVisibleSeries(newVisibleSeries);
    });
    
  3. 修改注释生成逻辑:在getAnnotations中加入系列可见性过滤,只生成当前可见系列的注释:

    • 分组时保留每个数据项对应的系列名称(假设你的data数组每个item有seriesName字段);
    • 遍历分组数据时,先判断当前系列是否在可见集合中,不在则跳过:

    修改后的关键代码片段:

    const getAnnotations = () => {
        if (totalRecords <= 10) {
            const annotations: any[] = [];
            const placedPositions: { x: number; y: number }[] = [];
            const groupedData = new Map<string, { 
                storeLabels: string[], 
                x: number, 
                y: number, 
                z: number, 
                series: string // 新增:存储系列名称
            }>();
    
            // 分组时保留系列信息
            data.forEach((item) => {
                const storeLabel = item[firstColumnFieldName] || "Unknown Store";
                const key = `${item.total_sales}-${item.spearman_correlation}`;
                if (!groupedData.has(key)) {
                    groupedData.set(key, { 
                        storeLabels: [], 
                        x: item.total_sales, 
                        y: item.spearman_correlation, 
                        z: item.max_sales,
                        series: item.seriesName // 绑定对应系列
                    });
                }
                groupedData.get(key)!.storeLabels.push(storeLabel);
            });
    
            let isPositiveYOffset = true;
    
            groupedData.forEach(({ storeLabels, x, y, z, series }) => {
                // 新增:如果系列不可见,直接跳过
                if (!visibleSeries.has(series)) return;
    
                // 原有的注释生成逻辑保持不变...
            });
    
            return annotations;
        }
        return [];
    };
    
  4. 同步气泡文本:气泡文本的处理逻辑和注释一致,生成气泡数据时只保留visibleSeries中的系列项即可。

方式2:动态切换注释可见性

如果不想重新生成注释,可以给每个注释添加visible属性,通过图例点击事件直接切换:

  1. 生成注释时绑定系列标识:
    annotations.push({
        // 原有的注释属性...
        customdata: { series: item.seriesName }, // 绑定所属系列
        visible: true // 初始可见
    });
    
  2. 监听图例点击更新可见性:
    chart.current.on('legendclick', (eventData) => {
      const seriesName = eventData.data[eventData.curveNumber].name;
      const updatedAnnotations = annotations.map(anno => {
        if (anno.customdata?.series === seriesName) {
          return {...anno, visible: !anno.visible};
        }
        return anno;
      });
      setAnnotations(updatedAnnotations);
      // 气泡文本同理,更新对应项的可见状态
    });
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 05:05:01