D3.js堆叠柱状图无法堆叠,数据分组报错求助
D3.js堆叠柱状图数据分组与堆叠问题解决
问题描述
我是D3.js新手,尝试创建堆叠柱状图但无法正确堆叠数值。调用stack()的.value(([, group], key) => group.get(key).duration)时出现TypeError: Cannot read properties of undefined (reading 'duration')错误,直接绘制时柱子还会重叠而非堆叠。
给定数据
const transformedData = [ { person: 'Frodo Baggins', project: 'Project a', duration: 21 }, { person: 'Gandalf', project: 'Project a', duration: 32 }, { person: 'Samwise Gamgee', project: 'Project f', duration: 88 }, { person: 'Samwise Gamgee', project: 'Project g', duration: 27 }, { person: 'Samwise Gamgee', project: 'Project d', duration: 148 }, { person: 'Samwise Gamgee', project: 'Project h', duration: 34 }, ]
原错误代码
const margin = {top: 10, right: 30, bottom: 20, left: 50}, width = 460 - margin.left - margin.right, height = 400 - margin.top - margin.bottom; const svg = d3.select(document.body) .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); const stackedData = d3.stack() .keys(transformedData.map(d => d.person)) .value(([, group], key) => group.get(key).duration) // 此处报错 (d3.index(transformedData, d => d.project, d => d.person)); const x = d3.scaleBand() .domain(projects) .range([0, width]) .padding([0.2]) svg.append("g") .attr("transform", "translate(0," + height + ")") .call(d3.axisBottom(x).tickSizeOuter(0)); const y = d3.scaleLinear() .domain([0, totalDuration]) .range([ height, 0 ]); svg.append("g") .call(d3.axisLeft(y)); const color = d3.scaleOrdinal() .domain(people) .range(['#e41a1c','#377eb8','#4daf4a']) svg.append("g") .selectAll("g") .data(stackedData) .enter() .append("g") .attr("fill", d => color(d.key)) .selectAll("rect") .data((d) => projects.map((project) => ({ project, duration: transformedData.reduce((acc, i) => i.project === project && i.person === d.key ? acc + i.duration : acc, 0) }))) .enter() .append("rect") .attr('x', (d, i) => (i * width) / projects.length) .attr('y', (d) => height - (d.duration / totalDuration) * height) .attr('height', (d) => (d.duration / totalDuration) * height) .attr('width', width / projects.length - 2);
错误表现
直接绘制时柱子重叠,效果如下:
解决方案
1. 修正数据分组与stack配置
报错原因是d3.index返回的结构和.value中预期的不匹配,且.keys直接用transformedData.map(d => d.person)会产生重复的人员值。先预处理数据,得到适配stack的结构化格式:
// 获取唯一人员、项目列表 const people = [...new Set(transformedData.map(d => d.person))]; const projects = [...new Set(transformedData.map(d => d.project))]; // 转换为「项目-各人员时长」的结构化数据,无数据时补0 const preparedData = projects.map(project => { const obj = { project }; people.forEach(person => { const matchItem = transformedData.find(d => d.project === project && d.person === person); obj[person] = matchItem ? matchItem.duration : 0; }); return obj; }); // 生成堆叠数据 const stackedData = d3.stack() .keys(people) // 使用唯一人员列表作为堆叠键 (preparedData);
2. 补全比例尺与坐标轴缺失逻辑
原代码中projects和totalDuration未定义,补充计算逻辑:
// 计算所有项目的最大总时长 const totalDuration = d3.max(preparedData, d => { return people.reduce((sum, person) => sum + d[person], 0); }); // X轴比例尺与坐标轴 const x = d3.scaleBand() .domain(projects) .range([0, width]) .padding(0.2); svg.append("g") .attr("transform", `translate(0, ${height})`) .call(d3.axisBottom(x).tickSizeOuter(0)); // Y轴比例尺与坐标轴 const y = d3.scaleLinear() .domain([0, totalDuration]) .range([height, 0]); svg.append("g") .call(d3.axisLeft(y));
3. 修正柱状图绘制逻辑
利用stack返回的上下边界数据直接绘制,无需手动计算:
svg.append("g") .selectAll("g") .data(stackedData) .join("g") .attr("fill", d => color(d.key)) .selectAll("rect") .data(d => d) .join("rect") .attr("x", d => x(d.data.project)) .attr("y", d => y(d[1])) // d[1]是堆叠段的上边界 .attr("height", d => y(d[0]) - y(d[1])) // d[0]是堆叠段的下边界 .attr("width", x.bandwidth());
完整修正代码
const transformedData = [ { person: 'Frodo Baggins', project: 'Project a', duration: 21 }, { person: 'Gandalf', project: 'Project a', duration: 32 }, { person: 'Samwise Gamgee', project: 'Project f', duration: 88 }, { person: 'Samwise Gamgee', project: 'Project g', duration: 27 }, { person: 'Samwise Gamgee', project: 'Project d', duration: 148 }, { person: 'Samwise Gamgee', project: 'Project h', duration: 34 }, ]; const margin = {top: 10, right: 30, bottom: 20, left: 50}, width = 460 - margin.left - margin.right, height = 400 - margin.top - margin.bottom; const svg = d3.select(document.body) .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", `translate(${margin.left}, ${margin.top})`); // 预处理数据 const people = [...new Set(transformedData.map(d => d.person))]; const projects = [...new Set(transformedData.map(d => d.project))]; const preparedData = projects.map(project => { const obj = { project }; people.forEach(person => { const matchItem = transformedData.find(d => d.project === project && d.person === person); obj[person] = matchItem ? matchItem.duration : 0; }); return obj; }); // 生成堆叠数据 const stackedData = d3.stack() .keys(people) (preparedData); // 计算总时长最大值 const totalDuration = d3.max(preparedData, d => { return people.reduce((sum, person) => sum + d[person], 0); }); // X轴配置 const x = d3.scaleBand() .domain(projects) .range([0, width]) .padding(0.2); svg.append("g") .attr("transform", `translate(0, ${height})`) .call(d3.axisBottom(x).tickSizeOuter(0)); // Y轴配置 const y = d3.scaleLinear() .domain([0, totalDuration]) .range([height, 0]); svg.append("g") .call(d3.axisLeft(y)); // 颜色比例尺 const color = d3.scaleOrdinal() .domain(people) .range(['#e41a1c','#377eb8','#4daf4a']); // 绘制堆叠柱状图 svg.append("g") .selectAll("g") .data(stackedData) .join("g") .attr("fill", d => color(d.key)) .selectAll("rect") .data(d => d) .join("rect") .attr("x", d => x(d.data.project)) .attr("y", d => y(d[1])) .attr("height", d => y(d[0]) - y(d[1])) .attr("width", x.bandwidth());
内容的提问来源于stack exchange,提问作者Way2Pin
相关产品推荐
相关产品推荐

