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

如何将散点图中重叠的相同数据点水平分散显示?

解决散点图重叠数据点水平分散显示问题

我给散点图输入了三个数据系列,总计6个数据点,但图表只显示3个点,剩下3个因为和已有数据点数值完全相同,导致完全重叠。

当前效果:
当前散点图效果

期望效果:
期望散点图效果

数据代码

const A = [
  {
    x: new Date("01-01-2023"),
    y: 130,
  },
  {
    x: new Date("03-01-2023"),
    y: 2500,
  },
  {
    x: new Date("07-01-2023"),
    y: 5000,
  },
];

const B = [
  {
    x: new Date("03-01-2023"),
    y: 2500,
  },
  {
    x: new Date("07-01-2023"),
    y: 5000,
  },
];

const C = [
  {
    x: new Date("07-01-2023"),
    y: 5000,
  },
];

现有散点图代码

<ScatterChart
  width={400}
  height={220}
>
  <CartesianGrid strokeDasharray="3 3" />
  <XAxis
    dataKey="x"
    type="number"
    name="date"
    domain={["dataMin", "dataMax"]}
    tickFormatter={(tick) =>
      new Date(tick).toLocaleDateString("en-US", {
        year: "numeric",
        month: "numeric",
        day: "numeric",
      })
    }
   
  />
  <YAxis
    dataKey="y"
    type="number"
    name="price"
    tickFormatter={(tick) => `$${tick}`}
   
  />

  <Legend />
  <Scatter
    name="A"
    data={A.map((item) => ({
      x: item.x.getTime(),
      y: item.y / 100,
    }))}
    fill="#8884d8"
  />
  <Scatter
    name="B"
    data={B.map((item) => ({
      x: item.x.getTime(), //convert date to epoch because we need a number for x-axis
      y: item.y / 100,
    }))}
    fill="#82ca9d"
  />
  <Scatter
    name="C"
    data={C.map((item) => ({
      x: item.x.getTime(),
      y: item.y / 100,
    }))}
    fill="#a8325c"
  />
</ScatterChart>

解决方案

核心思路是给重叠的点添加微小的x轴偏移量,让它们水平分散开,同时保证x轴仍显示原日期。

步骤1:统计重叠点数量

先收集所有数据点,统计每个(x,y)组合的出现次数:

// 转换所有数据点为统一格式并收集
const allPoints = [...A, ...B, ...C].map(item => ({
  x: item.x.getTime(),
  y: item.y / 100
}));

// 统计每个(x,y)组合的出现次数
const pointOccurrences = {};
allPoints.forEach(point => {
  const key = `${point.x}-${point.y}`;
  pointOccurrences[key] = (pointOccurrences[key] || 0) + 1;
});

步骤2:编写偏移处理函数

对每个数据系列的点,根据重叠次数计算偏移量,让点在原x值附近居中分散:

const offsetPoints = (dataSeries) => {
  const usedOffsets = {}; // 记录每个(x,y)已分配的偏移索引
  return dataSeries.map(item => {
    const xTimestamp = item.x.getTime();
    const yValue = item.y / 100;
    const key = `${xTimestamp}-${yValue}`;
    const totalOccurrences = pointOccurrences[key];

    // 无重叠则直接返回原数据
    if (totalOccurrences <= 1) {
      return { x: xTimestamp, y: yValue };
    }

    // 计算偏移量:用12小时的时间戳作为基础偏移单位,让点居中分布
    const currentOffsetIndex = usedOffsets[key] || 0;
    usedOffsets[key] = currentOffsetIndex + 1;
    const baseOffset = 1000 * 60 * 60 * 12; // 12小时的毫秒数
    const offset = (currentOffsetIndex - (totalOccurrences - 1) / 2) * baseOffset;

    return { x: xTimestamp + offset, y: yValue };
  });
};

步骤3:修改散点图组件

将原map替换为offsetPoints函数:

<ScatterChart
  width={400}
  height={220}
>
  <CartesianGrid strokeDasharray="3 3" />
  <XAxis
    dataKey="x"
    type="number"
    name="date"
    domain={["dataMin", "dataMax"]}
    tickFormatter={(tick) =>
      new Date(tick).toLocaleDateString("en-US", {
        year: "numeric",
        month: "numeric",
        day: "numeric",
      })
    }
   
  />
  <YAxis
    dataKey="y"
    type="number"
    name="price"
    tickFormatter={(tick) => `$${tick}`}
   
  />

  <Legend />
  <Scatter
    name="A"
    data={offsetPoints(A)}
    fill="#8884d8"
  />
  <Scatter
    name="B"
    data={offsetPoints(B)}
    fill="#82ca9d"
  />
  <Scatter
    name="C"
    data={offsetPoints(C)}
    fill="#a8325c"
  />
</ScatterChart>

说明

  • 偏移量用12小时的毫秒数,既不会让点偏离原日期范围,又能清晰区分不同系列的重叠点
  • x轴的tickFormatter会自动将偏移后的时间戳显示为原日期,因为偏移量小于1天,toLocaleDateString仍会输出同一日期

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 23:18:33