Chart.js自定义日期过滤器无法正常更新图表问题求助
Chart.js折线图日期过滤功能修复方案
问题根源分析
原实现存在以下核心问题:
- 仅更新第一个数据集,忽略其余6个商家的数据
- 数组截取逻辑错误,误用
splice处理二维数组 - 标签(labels)格式前后不一致(初始化用字符串日期,过滤后用时间戳)
- 未声明全局变量导致潜在冲突
- 预设日期超出输入框
min限制,无法生效
修复后的完整代码
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.js" integrity="sha512-d6nObkPJgV791iTGuBoVC9Aa2iecqzJRE0Jiqvk85BhLHAPhWqkuBiQb1xz2jvuHNqHLYoN3ymPfpiB1o+Zgpw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script> <div> <canvas id="earningsChart"></canvas> </div> <div class="container"> <div class="row"> <div class="col"> <label>Last</label> <button onclick="filterDateAll()">All</button> <button onclick="filterDate90()">90</button> <button onclick="filterDate30()">30</button> <button onclick="filterDate7()">7</button> <label>Days</label> </div> <div class="col"> <label for="filterDateStart">Start</label> <input class="lph input" type="date" id="filterDateStart" min="2023-09-29" max="2023-12-27" value="2023-12-14"> <label for="filterDateEnd" class="lph">End</label> <input class="lph input" type="date" id="filterDateEnd" min="2023-09-30" max="2023-12-28" value="2023-12-28"> <button onclick="filterDate()">Filter</button> <button onclick="filterDateReset()">Reset</button> </div> </div> </div> <script> const ctx = document.getElementById('earningsChart'); const earningDates = ['2023-12-14', '2023-12-15', '2023-12-16', '2023-12-17', '2023-12-18', '2023-12-19', '2023-12-20', '2023-12-21', '2023-12-22', '2023-12-23', '2023-12-24', '2023-12-25', '2023-12-26', '2023-12-27', '2023-12-28']; const earningDatesConverted = earningDates.map(dateStr => new Date(dateStr)); const earningsData = [ [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], [, , , , , , , , , , , , 1.0, 1.0, 1.0], [, , , , , , , , , , , , 1.0, 1.0, 1.0], [, , , , , , , , , , , , 1.0, 1.0, 1.0], [, , , , , , , , , , , , 1.5, 1.5, 1.5], [, , , , , , , , , , , , 2.0, 2.0, 2.0] ]; const earningsChart = new Chart(ctx, { type: 'line', data: { labels: earningDatesConverted, datasets: [ { label: 'Merchant 1', data: earningsData[0], fill: false, borderColor: '#6FA1C1', tension: 0.5, spanGaps: true }, { label: 'Merchant 2', data: earningsData[1], fill: false, borderColor: '#FFEB8C', tension: 0.5, spanGaps: true }, { label: 'Merchant 3', data: earningsData[2], fill: false, borderColor: '#E3F687', tension: 0.5, spanGaps: true }, { label: 'Merchant 4', data: earningsData[3], fill: false, borderColor: '#81D374', tension: 0.5, spanGaps: true }, { label: 'Merchant 5', data: earningsData[4], fill: false, borderColor: '#7A6AB0', tension: 0.5, spanGaps: true }, { label: 'Merchant 6', data: earningsData[5], fill: false, borderColor: '#D073A4', tension: 0.5, spanGaps: true }, { label: 'Merchant 7', data: earningsData[6], fill: false, borderColor: '#FFCE8C', tension: 0.5, spanGaps: true } ] }, options: { spanGaps: true, scales: { x: { type: 'time', time: { unit: 'day' } }, y: { beginAtZero: true } }, plugins: { legend: { display: true, position: 'bottom' }, tooltip: { callbacks: { title: function(tooltipItem) { if( tooltipItem[0].parsed.x ) { let dt = new Date(tooltipItem[0].parsed.x); let dtMonth = dt.getMonth() + 1; return `${dtMonth}/${dt.getDate()}/${dt.getFullYear()}`; } return ''; }, label: function(context) { let earn_rate = context.parsed.y; let merchant_name = context.dataset.label; return `${merchant_name}: ${earn_rate.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`; } }, titleFont: { size: 18 }, bodyFont: { size: 16 } } } } }); function filterDate() { const filterDateStartObj = new Date(document.getElementById('filterDateStart').value); const filterDateStart = filterDateStartObj.setHours(0, 0, 0, 0); const filterDateEndObj = new Date(document.getElementById('filterDateEnd').value); const filterDateEnd = filterDateEndObj.setHours(0, 0, 0, 0); const filteredLabels = earningDatesConverted.filter(date => date.getTime() >= filterDateStart && date.getTime() <= filterDateEnd); earningsChart.data.labels = filteredLabels; const startIdx = earningDatesConverted.findIndex(date => date.getTime() === filteredLabels[0].getTime()); const endIdx = earningDatesConverted.findIndex(date => date.getTime() === filteredLabels[filteredLabels.length - 1].getTime()); earningsChart.data.datasets.forEach((dataset, idx) => { dataset.data = earningsData[idx].slice(startIdx, endIdx + 1); }); earningsChart.update(); } function filterDateReset() { earningsChart.data.labels = [...earningDatesConverted]; earningsChart.data.datasets.forEach((dataset, idx) => { dataset.data = [...earningsData[idx]]; }); document.getElementById('filterDateStart').value = '2023-12-14'; document.getElementById('filterDateEnd').value = '2023-12-28'; earningsChart.update(); } function filterDateAll() { filterDateReset(); } function filterDate90() { document.getElementById('filterDateStart').value = '2023-09-29'; document.getElementById('filterDateEnd').value = '2023-12-28'; filterDate(); } function filterDate30() { document.getElementById('filterDateStart').value = '2023-11-28'; document.getElementById('filterDateEnd').value = '2023-12-28'; filterDate(); } function filterDate7() { document.getElementById('filterDateStart').value = '2023-12-21'; document.getElementById('filterDateEnd').value = '2023-12-28'; filterDate(); } </script>
关键修复点说明
- 统一标签格式:将所有标签改为
Date对象,避免字符串与时间戳混用导致的解析问题 - 完整数据集更新:遍历所有商家数据集,使用
slice截取对应日期范围的数据,替代错误的splice操作 - 修复索引获取逻辑:用
findIndex替代indexOf,避免日期重复时索引错误 - 局部变量声明:为
filterDateStart和filterDateEnd添加const声明,消除全局变量风险 - 调整输入框限制:修改
filterDateStart的min属性为预设的最早日期,确保90天/30天按钮能正常设置日期值 - 优化数据重置逻辑:使用扩展运算符
[...arr]复制原始数据,避免直接引用导致的原始数据污染
内容的提问来源于stack exchange,提问作者Kermit
相关产品推荐
相关产品推荐

