Chart.js Panning Not Working in ASP.NET MVC Application Using chartjs-plugin-zoom

I’m developing an ASP.NET 8 MVC application where I’m using Chart.js to display two real-time line charts: one for “Penetration Depth” and another for “Hydrophone Audio Levels.” I’ve integrated the chartjs-plugin-zoom to enable zooming and panning functionalities. While zooming (both wheel and pinch) works as expected, panning (click and drag) does not respond on either chart.

I’ve ensured that the plugin is correctly registered and have adjusted the threshold settings, but panning remains non-functional.

Problem:

I have integrated the chartjs-plugin-zoom to enable both zooming and panning on my Chart.js line charts. While zooming functionalities (using the mouse wheel and pinch gestures) work correctly, panning (clicking and dragging to move around the chart) does not respond. This issue persists across both charts in my application.

Code for the Charts being Displayed:





@model Sampling.Models.ViewModels.SamplingDataViewModel
@using System.Text.Json

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>Real-Time Chart</title>
    <!-- jQuery Library -->
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

    <!-- Chart.js and Luxon Adapter -->
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/build/global/luxon.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chartjs-plugin-zoom.min.js"></script>

    <style>
        .chart-container {
            position: relative;
            width: 100%;
            height: 60vh;
            margin-bottom: 50px;
        }

        #penetrationChartContainer, #hydrophoneChartContainer {
            background-color: #1a2b4c;
            padding: 20px;
            border-radius: 8px;
        }

        h2.chart-title {
            color: black;
            text-align: center;
            margin-top: 40px;
            margin-bottom: 20px;
            font-size: 1.5em;
        }
    </style>
</head>

<body>
    <!-- Penetration Reading Chart Section -->
    <h2 class="chart-title">Penetration Readings Chart</h2>
    <div class="chart-container" id="penetrationChartContainer">
        <canvas id="penetrationChart"></canvas>
    </div>

    <script>
        let lastPenetrationTimestamp = null;

        function initializePenetrationChart() {
            const ctx = document.getElementById('penetrationChart').getContext('2d');
            window.penetrationChart = new Chart(ctx, {
                type: 'line',
                data: {
                    datasets: [{
                        label: 'Penetration Depth',
                        data: [],
                        borderColor: '#ffcc00',
                        backgroundColor: '#ffcc00',
                        fill: false,
                        borderWidth: 2,
                        pointRadius: 2,
                        pointHoverRadius: 7
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    scales: {
                        x: {
                            type: 'time',
                            time: { unit: 'second', tooltipFormat: 'yyyy-MM-dd HH:mm:ss.SSSSSSS' },
                            ticks: { color: '#ffffff' },
                            title: { display: true, text: 'Time', color: '#ffffff' }
                        },
                        y: {
                            beginAtZero: true,
                            ticks: { color: '#ffffff' },
                            title: { display: true, text: 'Penetration Depth', color: '#ffffff' }
                        }
                    },
                    plugins: {
                        legend: { display: true, labels: { color: '#ffffff' } },
                        zoom: {
                            pan: { enabled: true, mode: 'xy', threshold: 10 },
                            zoom: { wheel: { enabled: true, speed: 0.05 }, pinch: { enabled: false }, mode: 'xy' }
                        },
                        tooltip: {
                            backgroundColor: 'rgba(0, 0, 0, 0.7)',
                            titleColor: '#ffffff',
                            bodyColor: '#ffffff',
                            borderColor: '#ffffff',
                            borderWidth: 1,
                            callbacks: {
                                title: tooltipItems => tooltipItems[0].raw.x.replace('T', ' ').replace('Z', ''),
                                label: tooltipItem => `Sample ID: ${tooltipItem.raw.sId}, Depth: ${tooltipItem.parsed.y} m`
                            }
                        }
                    }
                }
            });
        }

        function updatePenetrationChart(newData) {
            const chart = window.penetrationChart;
            if (chart && chart.data.datasets[0]) {
                const formattedData = newData.map(item => ({ x: item.timeStamp, y: item.penetrationDepth, sId: item.sampId }));
                chart.data.datasets[0].data.push(...formattedData);
                chart.update();
                lastPenetrationTimestamp = formattedData[formattedData.length - 1].x;
            }
        }

        function fetchPenetrationDataBatch() {
            const data = lastPenetrationTimestamp ? { lastTimestamp: lastPenetrationTimestamp } : {};

            $.ajax({
                url: '/PenetrationReadings/GetPenetrationReadingsBatch',
                method: 'GET',
                data: data,
                success: function (newData) {
                    if (newData && newData.length > 0) {
                        updatePenetrationChart(newData);
                    }
                },
                error: function (xhr, status, error) {
                    console.error('Error fetching penetration data batch:', error);
                    alert('Error fetching data: ' + xhr.responseText);
                }
            });
        }

        $(document).ready(function () {
            initializePenetrationChart();
            fetchPenetrationDataBatch();
            setInterval(fetchPenetrationDataBatch, 5000);
        });
    </script>

    <!-- Space between charts -->
    <br>
    <br>

    <!-- Hydrophone Audio Level Chart Section -->
    <h2 class="chart-title">Hydrophone Audio Level Chart</h2>
    <div class="chart-container" id="hydrophoneChartContainer">
        <canvas id="hydrophoneChart"></canvas>
    </div>

    <script>
        let lastHydrophoneTimestamp = null;

        function initializeHydrophoneChart() {
            const ctx2 = document.getElementById('hydrophoneChart').getContext('2d');
            const gradient = ctx2.createLinearGradient(0, 0, 0, 400);
            gradient.addColorStop(0, 'rgba(75, 192, 192, 0.05)');
            gradient.addColorStop(1, 'rgba(75, 192, 192, 0.15)');

            window.hydrophoneChart = new Chart(ctx2, {
                type: 'line',
                data: {
                    datasets: [{
                        label: 'Volume (dB)',
                        data: [],
                        borderColor: 'rgba(75, 192, 192, 1)',
                        backgroundColor: gradient,
                        fill: true,
                        tension: 0.4,
                        pointRadius: 2,
                        borderWidth: 2
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    scales: {
                        x: {
                            type: 'time',
                            time: {
                                unit: 'second',
                                tooltipFormat: 'yyyy-MM-dd HH:mm:ss',
                                displayFormats: {
                                    second: 'HH:mm:ss',
                                    minute: 'HH:mm',
                                    hour: 'HH:mm',
                                    day: 'yyyy-MM-dd',
                                },
                            },
                            title: { display: true, text: 'Time', color: '#ffffff' },
                            ticks: { color: '#ffffff' },
                            grid: { color: 'rgba(255, 255, 255, 0.1)' }
                        },
                        y: {
                            beginAtZero: true,
                            title: { display: true, text: 'Volume (dB)', color: '#ffffff' },
                            ticks: { color: '#ffffff' },
                            grid: { color: 'rgba(255, 255, 255, 0.1)' }
                        }
                    },
                    plugins: {
                        legend: {
                            display: true,
                            labels: { color: '#ffffff' }
                        },
                        tooltip: {
                            backgroundColor: 'rgba(0, 0, 0, 0.7)',
                            titleColor: '#ffffff',
                            bodyColor: '#ffffff',
                            borderColor: '#ffffff',
                            borderWidth: 1,
                            callbacks: {
                                title: tooltipItems => tooltipItems[0].raw.x.replace('T', ' ').replace('Z', ''),
                                label: tooltipItem => `Volume: ${tooltipItem.parsed.y} dB, Sample ID: ${tooltipItem.raw.sId}`
                            }
                        },
                        zoom: {
                            pan: { enabled: true, mode: 'xy' },
                            zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' }
                        }
                    }
                }
            });
        }

        function updateHydrophoneChart(newData) {
            const chart = window.hydrophoneChart;
            if (chart && chart.data.datasets[0]) {
                const formattedData = newData.map(item => ({ x: item.timeStamp, y: item.volumeDb, sId: item.sampId }));
                chart.data.datasets[0].data.push(...formattedData);
                chart.update();
                lastHydrophoneTimestamp = formattedData[formattedData.length - 1].x;
            }
        }

        function fetchHydrophoneDataBatch() {
            const data = lastHydrophoneTimestamp ? { lastTimestamp: lastHydrophoneTimestamp } : {};

            $.ajax({
                url: '/PenetrationReadings/GetHydrophoneAudioLevelsBatch',
                method: 'GET',
                data: data,
                success: function (newData) {
                    if (newData && newData.length > 0) {
                        updateHydrophoneChart(newData);
                    }
                },
                error: function (xhr, status, error) {
                    console.error('Error fetching hydrophone data batch:', error);
                    alert('Error fetching data: ' + xhr.responseText);
                }
            });
        }

        $(document).ready(function () {
            initializeHydrophoneChart();
            fetchHydrophoneDataBatch();
            setInterval(fetchHydrophoneDataBatch, 5000);
        });
    </script>
</body>
</html>

What I’ve Tried:

Plugin Registration:
Ensured that the chartjs-plugin-zoom is correctly registered with Chart.js using:





if (typeof Chart !== 'undefined' && typeof ChartZoom !== 'undefined') {
    Chart.register(ChartZoom);
} else {
    console.error('Chart.js or ChartZoom plugin is not loaded.');
}

Adjusting Threshold:
Reduced the threshold value from 10 to 5 in the pan configuration to make panning more responsive:





pan: { enabled: true, mode: 'xy', threshold: 5 },