如何使用HTML、JS和chart.js处理大型CSV文件

问题描述 投票:0回答:1

我必须上传一个 90 MB 的 CSV 文件,然后使用 Chart.js 将其作为图表进行分析。 CSV 文件包含每分钟记录的测量值。这 90 MB 几乎相当于一年的数据量。我已经将网站响应时间设置为较高的值。但我的代码正在付诸东流。这就是为什么我只显示一定数量的数据值,然后每隔一段时间单击图表。即使这样仍然很慢而且不好。对于评估来说,至少每月进行一次概述会更好。但我不知道我还能做出哪些调整。你有什么想法吗?

HTML

<!DOCTYPE html>
<html lang="de">
   <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>CSV Diagramm mit Chart.js</title>
      <link rel="stylesheet" href="styles.css">
   </head>
   <body>
      <div id="drop-area" class="drop-area" style="width: 100%;" ondrop="handleDrop(event)" ondragover="handleDragOver(event)">
         <p>Datei hier ablegen</p>
         <input type="file" id="csvFileInput" accept=".csv" style="display:none;" onchange="handleUpload()">
      </div>
      <div class="chart-container" style="width: 100%;">
         <canvas id="myChart"></canvas>
      </div>
      <button onclick="showPreviousData()">Vorheriger Tag</button>
      <button onclick="showNextData()">Nächster Tag</button>
      <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js" integrity="sha512-UXumZrZNiOwnTcZSHLOfcTs0aos2MzBWHXOHOuB0J/R44QB0dwY5JgfbvljXcklVf65Gc4El6RjZ+lnwd2az2g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-zoom/2.0.1/chartjs-plugin-zoom.min.js" integrity="sha512-wUYbRPLV5zs6IqvWd88HIqZU/b8TBx+I8LEioQ/UC0t5EMCLApqhIAnUg7EsAzdbhhdgW07TqYDdH3QEXRcPOQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
      <script src="script.js"></script>
   </body>
</html>

JS

let startIndex = 0;
const displayCount = 1440;
let labels = [];
let datasets = [];
let originalDatasetVisibility = [];

function handleUpload() {
    const fileInput = document.getElementById('csvFileInput');
    const file = fileInput.files[0];
    handleFile(file);
}

function processData(csvData) {
    const rows = csvData.split('\n');
    labels = [];
    datasets = [];
    originalDatasetVisibility = [];

    const colors = ['rgba(255, 0, 0, 1)', 'rgba(0, 255, 0, 1)', 'rgba(255, 255, 0, 1)', 'rgba(0, 0, 255, 1)'];

    const columns = rows[0].split(';');

    for (let i = 1; i < columns.length; i++) {
        const data = [];
        const currentLabel = columns[i];
        const color = colors[i - 1];

        for (let j = 1; j < rows.length; j++) {
            const cols = rows[j].split(';');
            if (i === 1) {
                labels.push(cols[0]);
            }
            data.push(parseFloat(cols[i]));
        }

        const dataset = {
            label: currentLabel,
            data: data,
            backgroundColor: color,
            borderColor: color,
            fill: false,
            borderWidth: 1,
            pointRadius: 1,
        };

        datasets.push(dataset);
        originalDatasetVisibility.push(true);
    }

    createChart(labels.slice(startIndex, startIndex + displayCount), datasets, function() {
        console.log('Diagramm wurde erstellt');
    });
}

function createChart(labels, datasets, callback) {
    const chartContainer = document.querySelector('.chart-container');
    const canvasElement = document.getElementById('myChart');

    if (canvasElement) {
        chartContainer.removeChild(canvasElement);
    }

    chartContainer.innerHTML = '<canvas id="myChart"></canvas>';

    const ctx = document.getElementById('myChart').getContext('2d');
    window.myChart = new Chart(ctx, {
        type: 'line',
        data: {
            labels: labels,
            datasets: datasets.map((dataset, index) => ({
                ...dataset,
                data: dataset.data.slice(startIndex, startIndex + displayCount),
                hidden: !originalDatasetVisibility[index],
            })),
        },
        options: {
            scales: {
                x: {
                    stacked: true,
                    min: labels[startIndex],
                    max: labels[startIndex + displayCount - 1],
                },
                y: {},
            },
            plugins: {
                zoom: {
                    pan: {
                        enabled: true,
                        mode: 'x'
                    },
                    zoom: {
                        wheel: {
                            enabled: true,
                        },
                        pinch: {
                            enabled: true
                        },
                        mode: 'x',
                    }
                }
            }
        }
    });

    if (callback && typeof callback === 'function') {
        callback();
    }

    window.myChart.resetZoom();
    window.myChart.ctx.canvas.addEventListener('wheel', handleZoom);
}

function handleZoom(event) {
    const chart = window.myChart;
    const chartArea = chart.chartArea;
    const originalDatasets = chart.data.datasets;

    const zoomEnabled = chart.options.plugins.zoom.zoom.wheel.enabled;
    const deltaY = event.deltaY;

    if (zoomEnabled && deltaY !== 0) {
        const deltaMode = event.deltaMode;
        const scaleDelta = deltaY > 0 ? 0.9 : 1.1;

        let newMinIndex = chart.getDatasetMeta(0).data.findIndex(
            (d) => d.x >= chartArea.left
        );
        let newMaxIndex = chart.getDatasetMeta(0).data.findIndex(
            (d) => d.x >= chartArea.right
        );

        if (deltaMode === 0) {
            newMinIndex = Math.max(0, newMinIndex - Math.abs(deltaY));
            newMaxIndex = Math.min(
                originalDatasets[0].data.length - 1,
                newMaxIndex + Math.abs(deltaY)
            );
        } else if (deltaMode === 1) {
            newMinIndex = Math.max(0, newMinIndex - Math.abs(deltaY) * 10);
            newMaxIndex = Math.min(
                originalDatasets[0].data.length - 1,
                newMaxIndex + Math.abs(deltaY) * 10
            );
        }

        const newMinLabel = originalDatasets[0].data[newMinIndex].label;
        const newMaxLabel = originalDatasets[0].data[newMaxIndex].label;

        chart.options.scales.x.min = newMinLabel;
        chart.options.scales.x.max = newMaxLabel;

        chart.update();
    }
}

function handleFile(file) {
    if (file) {
        const reader = new FileReader();

        reader.onload = function (e) {
            const csvData = e.target.result;
            processData(csvData);
        };

        reader.readAsText(file);
    } else {
        alert('Bitte eine CSV-Datei auswählen.');
    }
}

function handleDrop(event) {
    event.preventDefault();
    const file = event.dataTransfer.files[0];
    handleFile(file);
}

function handleDragOver(event) {
    event.preventDefault();
}

function showPreviousData() {
    if (startIndex - displayCount >= 0) {
        startIndex -= displayCount;
        updateChart();
    }
}

function showNextData() {
    if (startIndex + displayCount < labels.length) {
        startIndex += displayCount;
        updateChart();
    }
}

function updateChart() {
    const endIndex = Math.min(startIndex + displayCount, labels.length);
    const updatedLabels = labels.slice(startIndex, endIndex);
    const updatedDatasets = datasets.map((dataset, index) => ({
        ...dataset,
        data: dataset.data.slice(startIndex, endIndex),
        hidden: !originalDatasetVisibility[index],
    }));

    window.myChart.data.labels = updatedLabels;
    window.myChart.data.datasets = updatedDatasets;
    window.myChart.options.scales.x.min = updatedLabels[0];
    window.myChart.options.scales.x.max = updatedLabels[updatedLabels.length - 1];

    window.myChart.update();
}

function removeZoomEventListener() {
    window.myChart.ctx.canvas.removeEventListener('wheel', handleZoom);
}

javascript html chart.js
1个回答
0
投票

这取决于您的用例、数据必须如何可视化、表示必须有多精确以及需要多快。

基本上有两个攻击点:“缩小”数据保持客户端工作负载较低

也就是说,这里有一些提高性能的技巧:

ChartJs相关

  1. 查看Chartjs 官方表演“提示和技巧”

    • 在我的本地高数据示例(~100MB):“禁用动画”“禁用点绘制”“启用跨度间隙”“指定比例的最小值和最大值”极大地提高了性能, 仅举几个。 (但您必须调整选项,以获得最佳速度和视觉吸引力)

    图像 1:细微调整 ~ 70 秒(5255999 数据行) No Scales ~ 70 Seconds

    图 2:细微调整 + 固定缩放 ~ 3 秒(5255999 数据行) Some scale adjustments ~ 4 Sec

  2. 准备好数据以便于使用

    • 发送已解析和排序的 json 数据而不是 csv,您可以在客户端手动解析该数据。 (文件可能会更大,但chartjs不需要在客户端重新解析和标准化数据)
    • 在将数据发送给用户之前聚合/预计算/清理数据。
  3. 您可以加载一小块数据并异步加载数据,并更新图表数据,查看此更新图表示例

在数据/Web端

  1. 首先分析您的数据
    • 您是否必须发送一张图表的所有列?如果不删除不需要的列和行。
    • 如上图所示,chartjs“隐藏”一些值以适合画布上的图表,如果您知道这一点,只需发送 “可见” 值即可。
  2. 如果不需要 100% 准确度,请删除一些行并让 Chartjs 填补空白。
  3. 只需发送所需最大分辨率的数据,将数据拆分为多个文件。特别是如果您只需要特定数据分辨率的子集和/或数据用于不同的图表中。
  4. 异步加载数据,加载后显示。

额外提示:如果您不必使用chartjs,请查看这个SO问题/答案,建议使用Highcharts来处理大数据而不是chartjs。

© www.soinside.com 2019 - 2024. All rights reserved.