Chart.js动态多柱状图适配缺失数据:避免类型错位问题
柱状图数据错位问题解决方法
我尝试基于随过滤器动态变化的JSON数据创建柱状图,要求图表仅展示对应Period的Type数据(存在则显示,缺失则不展示)。参考实现后,只有当所有数据选项齐全时图表正常工作;如果某Period缺失某个Type,会导致该Type的数据错位到上一个Period,不符合需求。
示例1:两个月、两类数据(显示正常)
[ { "Period": "2023-12", "Total": 2238.86, "Type": "Drinks" }, { "Period": "2023-12", "Total": 4672.35, "Type": "Food" }, { "Period": "2024-01", "Total": 1166.5, "Type": "Drinks" } ]
示例2:三个月、三类数据(数据错位)
[ { "Period": "2023-11", "Total": 2782.13, "Type": "Other" }, { "Period": "2023-12", "Total": 2238.86, "Type": "Drinks" }, { "Period": "2023-12", "Total": 4672.35, "Type": "Food" }, { "Period": "2024-01", "Total": 1166.5, "Type": "Drinks" } ]
当前使用代码
jQuery.ajax({ url: "../../ajax/barGraph.php?period=<?= $period; ?>", type: "GET", dataType: 'json', success: function(soldeInfo) { let labels = [...new Set(soldeInfo.map((x) => x.Period))]; let datasets = soldeInfo.reduce(function (acc, cur) { let Type = acc.find((x) => x.label == cur.Type); if (Type == null) { let color = ''; switch (cur.Type) { case 'Food': color = 'red'; break; case 'Drinks': color = 'green'; break; default: color ='gray'; } Type = { label: cur.Type, data: [cur.Total], backgroundColor: color, borderColor: color, borderWidth: 1, }; acc.push(Type); } else { Type.data.push(cur.Total); } return acc; }, []); var soldeInfo = { type: 'bar', data: { labels: labels, datasets: datasets, }, }; const soldeChart = document.getElementById('chartSolde'); new Chart(soldeChart, soldeInfo); } });
问题原因
当前代码的核心问题是:reduce方法只是按原始数据顺序把每个Type的Total追加到data数组,没有对应到具体的Period位置。当某个Period缺失某类Type时,后续的Total会直接补到上一个空位,导致数据和Period标签错位。
解决方法
要让每个Type的data数组和Period标签完全对应,缺失的位置填充null(Chart.js会自动跳过该位置的柱子绘制)。具体步骤:
- 提取所有唯一的Period作为标签,同时提取所有唯一的Type。
- 为每个Type初始化和标签长度一致的数组,默认填充
null。 - 遍历原始数据,找到当前Period在标签中的索引,把Total填充到对应Type数组的指定位置。
修改后的代码:
jQuery.ajax({ url: "../../ajax/barGraph.php?period=<?= $period; ?>", type: "GET", dataType: 'json', success: function(soldeInfo) { // 提取所有唯一Period并按时间排序,保证顺序正确 let labels = [...new Set(soldeInfo.map(x => x.Period))].sort((a, b) => new Date(a) - new Date(b)); // 提取所有唯一的Type类型 let allTypes = [...new Set(soldeInfo.map(x => x.Type))]; // 初始化每个Type的数据集 let datasets = allTypes.map(type => { // 创建和标签长度一致的数组,默认填充null let data = new Array(labels.length).fill(null); // 筛选当前Type的所有数据 let typeData = soldeInfo.filter(item => item.Type === type); // 把对应Period的Total填充到指定索引位置 typeData.forEach(item => { let index = labels.indexOf(item.Period); if (index !== -1) { data[index] = item.Total; } }); // 设置对应颜色 let color = ''; switch (type) { case 'Food': color = 'red'; break; case 'Drinks': color = 'green'; break; default: color = 'gray'; } return { label: type, data: data, backgroundColor: color, borderColor: color, borderWidth: 1, }; }); var chartConfig = { type: 'bar', data: { labels: labels, datasets: datasets, }, }; const soldeChart = document.getElementById('chartSolde'); new Chart(soldeChart, chartConfig); } });
额外说明
- 对Period标签做了时间排序,避免原始数据顺序混乱导致的标签错位。
- 每个Type的data数组和标签完全对齐,缺失数据的位置用
null填充,Chart.js会自动不绘制该位置的柱子,彻底解决错位问题。 - 逻辑更清晰,先构建完整结构再填充数据,避免了原代码中顺序追加的漏洞。
内容的提问来源于stack exchange,提问作者Gustavo
相关产品推荐
相关产品推荐

