如何将散点图中重叠的相同数据点水平分散显示?
解决散点图重叠数据点水平分散显示问题
我给散点图输入了三个数据系列,总计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
相关产品推荐
相关产品推荐

