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

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>

关键修复点说明

  1. 统一标签格式:将所有标签改为Date对象,避免字符串与时间戳混用导致的解析问题
  2. 完整数据集更新:遍历所有商家数据集,使用slice截取对应日期范围的数据,替代错误的splice操作
  3. 修复索引获取逻辑:用findIndex替代indexOf,避免日期重复时索引错误
  4. 局部变量声明:为filterDateStart和filterDateEnd添加const声明,消除全局变量风险
  5. 调整输入框限制:修改filterDateStart的min属性为预设的最早日期,确保90天/30天按钮能正常设置日期值
  6. 优化数据重置逻辑:使用扩展运算符[...arr]复制原始数据,避免直接引用导致的原始数据污染

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 10:54:52