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

D3.js绘制多系列折线图数值量级差异过大如何优化?

问题背景

使用d3.js绘制新冠病例数据多系列折线图时,以国家作为时间序列分组维度,因不同国家病例数数值量级差距过大:部分国家整体数值水平极低,部分国家在特定时间点存在极高的数值峰值,直接绘制后图表可读性极差,无法清晰分辨各序列的变化趋势。
编写完d3代码后生成的折线图效果

现有实现核心代码

样例CSV数据

iso_code,continent,location,date,total_cases,case_per_date,total_deaths,death_per_date,total_cases_million,case_per_million,death_per_million,total_death_million
AFG,Asia,Afghanistan,24-02-2020,5,5,0,0,0.126,0.126,0,0
AFG,Asia,Afghanistan,25-02-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,26-02-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,27-02-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,28-02-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,29-02-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,01-03-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,02-03-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,03-03-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,04-03-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,05-03-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,06-03-2020,5,0,0,0,0.126,0,0,0
AFG,Asia,Afghanistan,07-03-2020,8,3,0,0,0.201,0.075,0,0

比例尺定义

const x = d3.scaleTime().range([0, vis.WIDTH])
const y = d3.scaleLinear().range([vis.HEIGHT, 0])
const z = d3.scaleOrdinal(d3.schemeCategory10);

数据分组逻辑

const covidData = d3.nest()
            .key(d => d.location)
            .entries(data)

折线生成器

const line = d3.line()
     .x(function(d) { return x(d.date); })
     .y(function(d) { return y(d.case_per_date); });

折线绘制核心逻辑

const country = g.selectAll(".countries").data(data);
country.exit().remove();
country.enter().insert("g", ".focus")
       .append("path")
       .attr("class","chart-line")
       .style("stroke", d => z(d.id))
       .merge(country)
       .transition(vis.t)
       .attr("d",d => line(d.values))

可行优化方案
  • 方案1:替换线性Y轴为对数比例尺
    新冠病例属于典型的跨量级指数增长类数据,线性比例尺会将低数值国家的曲线完全挤压到坐标轴底部,无法辨认趋势。直接将Y轴的线性比例尺替换为对数比例尺即可解决量级差问题。

    注意:对数比例尺无法处理0值,需要给Y轴域设置最小下限(如1),同时预处理数据将0值替换为1,避免绘制报错。
    代码修改示例:

    // 预处理阶段补全日期解析、替换0值
    const parseDate = d3.timeParse("%d-%m-%Y")
    data.forEach(d => {
      d.date = parseDate(d.date)
      d.case_per_date = d.case_per_date === 0 ? 1 : d.case_per_date
    })
    // 替换原Y轴比例尺
    const maxCase = d3.max(data, d => d.case_per_date)
    const y = d3.scaleLog().range([vis.HEIGHT, 0]).domain([1, maxCase]).nice()
    
  • 方案2:使用相对人口的归一化指标
    现有数据集已经提供了case_per_million(每百万人口新增病例)字段,直接用该字段替代绝对新增病例数case_per_date作为Y轴映射值,即可消除不同国家人口规模带来的数值量级差,所有国家的曲线处于同一可比维度,趋势对比更客观。
    仅需修改折线生成器的取值逻辑即可:

    const line = d3.line()
       .x(function(d) { return x(d.date); })
       .y(function(d) { return y(d.case_per_million); });
    
  • 方案3:交互筛选+小多图视图兜底
    如果需要保留绝对数值展示、且不想使用对数轴,可以通过两个维度优化可读性:

    • 增加国家多选筛选控件,默认仅展示用户选中的、量级接近的国家曲线,其余曲线暂时隐藏
    • 拆分单图为小多图(Small Multiples)布局,每个国家对应独立的小绘图面板,每个面板单独适配Y轴数值范围,既可以查看单个国家的病例变化趋势,也可以跨国家对比走势形态

额外代码bug提示:现有绘制逻辑中数据绑定传入的是原始未分组的data,和前面d3.nest分组得到的covidData不匹配,需要将绑定部分改为.data(covidData),否则分组逻辑不会生效。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 18:31:12