D3.js X轴时间比例尺失效问题求助
问题排查与解决方案
我来帮你搞定这个问题,核心问题出在时间对象被强制转成字符串,导致D3的时间比例尺完全失效,进而影响了整个图表的渲染(包括Y轴不显示的问题)。下面一步步拆解修复:
问题根源分析
你用解析后的Date对象作为DomesticCountMap的键,但JavaScript普通对象的键会自动转成字符串。当你把map转成DomesticCount数组时,d.name已经是字符串形式的日期(比如"Tue Jan 01 2006 00:00:00 GMT+0800..."),但d3.scaleTime()要求输入必须是原生Date对象——这直接导致x比例尺无法工作,连带打乱了整个图表的布局逻辑,Y轴自然也没法正常显示。
分步解决方案
1. 修正数据聚合逻辑,保留Date对象
用Map代替普通对象存储聚合数据,因为Map支持非字符串类型的键,能完整保留Date对象:
// 替换原有的DomesticCountMap/InternationalCountMap逻辑 var domesticMap = new Map(); var internationalMap = new Map(); data.forEach(function(datum) { datum.ReportPeriod = parseTime(datum.ReportPeriod); // 确保乘客数是数值,避免非数值导致错误 const passengerCount = parseInt(datum.Passenger_Count) || 0; if (datum.Domestic_International === "Domestic") { // 累加计数,不存在则初始化为0 domesticMap.set(datum.ReportPeriod, (domesticMap.get(datum.ReportPeriod) || 0) + passengerCount); } else if (datum.Domestic_International === "International") { internationalMap.set(datum.ReportPeriod, (internationalMap.get(datum.ReportPeriod) || 0) + passengerCount); } }); // 将Map转成D3需要的数组格式,保留Date对象 var DomesticCount = Array.from(domesticMap.entries()).map(function([date, count]) { return { name: date, count: count }; }); // 关键:时间序列数据必须按时间排序,否则折线会乱序 DomesticCount.sort((a, b) => a.name - b.name);
2. 配置X轴只显示年份
现在x比例尺能正确识别Date对象了,接下来设置刻度规则,让X轴只展示年份:
// 修改X轴的绘制代码 g.append('g') .attr('class', 'axis axis--x') .attr('transform', 'translate(0,' + height + ')') .call(d3.axisBottom(x) .ticks(d3.timeYear) // 按年份自动生成刻度 .tickFormat(d3.timeFormat("%Y"))); // 格式化为四位年份
3. 验证Y轴逻辑
你的Y轴配置本身没问题,但可以加个小保险确保数值有效:
y.domain([0, d3.max(DomesticCount, function(d) { // 确保count是有效数值 return typeof d.count === 'number' ? d.count : 0; })]);
整合后的核心代码片段
把上述修改代入你的代码后,核心部分大概是这样:
var margin = { top: 20, right: 20, bottom: 30, left: 80 }; var svg = d3.select('body').append('svg') .attr('width', window.innerWidth) .attr('height', window.innerHeight); var width = window.innerWidth - margin.left - margin.right; var height = window.innerHeight - margin.top - margin.bottom; var parseTime = d3.timeParse("%d/%m/%Y %H:%M:%S %p"); var x = d3.scaleTime().rangeRound([0, width]); var y = d3.scaleLinear().rangeRound([height, 0]); var g = svg.append('g') .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); var line = d3.line() .x(function(d) { return x(d.name); }) .y(function(d) { return y(d.count); }); d3.csv('Los_Angeles_International_Airport_-_Passenger_Traffic_By_Terminal.csv', function(data) { var domesticMap = new Map(); var internationalMap = new Map(); data.forEach(function(datum) { datum.ReportPeriod = parseTime(datum.ReportPeriod); const passengerCount = parseInt(datum.Passenger_Count) || 0; if (datum.Domestic_International === "Domestic") { domesticMap.set(datum.ReportPeriod, (domesticMap.get(datum.ReportPeriod) || 0) + passengerCount); } else if (datum.Domestic_International === "International") { internationalMap.set(datum.ReportPeriod, (internationalMap.get(datum.ReportPeriod) || 0) + passengerCount); } }); var DomesticCount = Array.from(domesticMap.entries()).map(([date, count]) => ({name: date, count})); DomesticCount.sort((a, b) => a.name - b.name); x.domain(d3.extent(DomesticCount, d => d.name)); y.domain([0, d3.max(DomesticCount, d => d.count)]); // X轴(只显示年份) g.append('g') .attr('class', 'axis axis--x') .attr('transform', 'translate(0,' + height + ')') .call(d3.axisBottom(x) .ticks(d3.timeYear) .tickFormat(d3.timeFormat("%Y"))); // Y轴 g.append('g') .attr('class', 'axis axis--y') .call(d3.axisLeft(y)) .append('text') .attr("fill", "#000") .attr('transform', 'rotate(-90)') .attr('y', 6) .attr('dy', '0.71em') .attr('text-anchor', 'end') .text('Passenger_Count'); // 绘制折线 g.append('path') .datum(DomesticCount) .attr('fill', 'none') .attr('stroke', 'blue') .attr('stroke-linejoin', 'round') .attr('stroke-linecap', 'round') .attr('stroke-width', 1.5) .attr('d', line); });
这样修改后,X轴会正确显示年份,Y轴也会正常渲染,折线图的逻辑也能正常工作了。
内容的提问来源于stack exchange,提问作者alsjinus
相关产品推荐
相关产品推荐

