[Chartjs]-Overlay 2 x axis on a line graph

1👍

If you enable the x and x1 axes ticks, you’ll see what the problem is: both x axes are are categorical, not time axes, which means that the date strings are treated as simple strings and not interpreted as dates. This might work for you since the dates are all of the same interval (1 day), but it is still dangerous as they not sorted and there’s no guarantee that they will be in order.

So, to solve the issue and make the chart display safer, I’d parse the dates and transform them into a date-sorted array of the form

[{x:timestamp1, y: value1}, {x: timestamp2, y: value2},...]

Here’s a function that does that for your date format:

function parseDateIndexed(data){
    return Object.entries(data).
        map(([s, f])=>({x: Date.parse(s), y: f, s})).
        sort(({x:t1},{x:t2})=>t1-t2);
}

I also saved the original date in string format under s, because it can prove useful when formatting, for instance the tooltip.

If you don’t want to display the dates, you can make axes "linear", as in the example below, where the only tricky part is formatting the tooltip title. The same technique can be applied to format the axis labels, but you can also opt for declaring the type of the x axes as "time" and using an adapter, like chartjs-adapter-date-fns.

function parseDateIndexed(data){
    return Object.entries(data).
    map(([s, f])=>({x: Date.parse(s), y: f, s})).
    sort(({x:t1},{x:t2})=>t1-t2);
}

let startGraph = function() {
    let canvas = document.getElementById('chart');
    let chart = new Chart(canvas.getContext('2d'), {
        type: 'line',
        data: {
            datasets: [{
                label: 'Revenue',
                data: parseDateIndexed({
                    "2023-03-01": 250,
                    "2023-03-02": 488,
                    "2023-03-03": 358,
                    "2023-03-04": 255,
                    "2023-03-05": 895
                }),
                borderColor: '#ff6384',
                xAxisID: 'x',
            },
                {
                    label: 'Profit',
                    data: parseDateIndexed({
                        "2023-03-01": 125,
                        "2023-03-02": 156,
                        "2023-03-03": 195,
                        "2023-03-04": 98,
                        "2023-03-05": 466
                    }),
                    borderColor: '#36a2eb',
                    xAxisID: 'x',
                },
                {
                    label: 'Past Revenue',
                    data: parseDateIndexed({
                        "2023-02-24": 280,
                        "2023-02-25": 456,
                        "2023-02-26": 665,
                        "2023-02-27": 288,
                        "2023-02-28": 499
                    }),
                    borderColor: '#fa829b',
                    xAxisID: 'x1',
                },
                {
                    label: 'Past Profit',
                    data: parseDateIndexed({
                        "2023-02-24": 125,
                        "2023-02-25": 198,
                        "2023-02-26": 265,
                        "2023-02-27": 101,
                        "2023-02-28": 221
                    }),
                    borderColor: '#76c1f5',
                    xAxisID: 'x1',
                }
            ]
        },
        options: {
            elements: {
                point: {
                    radius: 0,
                    hoverRadius: 0,
                    hitRadius: 3,
                },
                line: {
                    borderWidth: 2,
                },
            },
            responsive: true,
            maintainAspectRatio: false,
            interaction: {
                intersect: false,
                mode: 'index',
            },
            stacked: false,
            scales: {
                y: {
                    ticks: {
                        display: true,
                        beginAtZero: true,
                    }
                },
                x: {
                    type:"linear",
                    position: 'bottom',
                    bounds: 'data',
                    ticks: {
                        display: false,
                    },
                },
                x1: {
                    type:"linear",
                    position: 'top',
                    bounds: 'data',
                    ticks: {
                       display: false,
                    },
                    grid: {
                        drawOnChartArea: false,
                    },
                },
            },
            plugins: {
                tooltip:{
                    callbacks:{
                        title(items){
                            const dates = items.map(o=>o.raw.s);
                            const d1 = dates[0], d2 = dates[dates.length-1];
                            const ad = (d1 === d2) ? d1 : [d1, d2];
                            return ad.join(' / ')
                        }
                    }
                },
                legend: {
                    display: true,
                    position: 'bottom',
                    labels: {
                        boxWidth: 8,
                        boxHeight: 8,
                        useBorderRadius: true,
                        borderRadius: 3,
                        usePointStyle: false,
                        pointStyleWidth: 0,
                        padding: 7,
                        font: {
                            size: 11
                        }
                    }
                },
            },
        }
    });
}

document.addEventListener("DOMContentLoaded", function() {
    startGraph();
});
<canvas id="chart"></canvas>

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.1.2/chart.umd.js"
        integrity="sha512-t41WshQCxr9T3SWH3DBZoDnAT9gfVLtQS+NKO60fdAwScoB37rXtdxT/oKe986G0BFnP4mtGzXxuYpHrMoMJLA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Leave a comment