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

Lis*_*isa 6 html javascript chart.js

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

\n

超文本标记语言

\n
<!DOCTYPE html>\n<html lang="de">\n   <head>\n      <meta charset="UTF-8">\n      <meta name="viewport" content="width=device-width, initial-scale=1.0">\n      <title>CSV Diagramm mit Chart.js</title>\n      <link rel="stylesheet" href="styles.css">\n   </head>\n   <body>\n      <div id="drop-area" class="drop-area" style="width: 100%;" ondrop="handleDrop(event)" ondragover="handleDragOver(event)">\n         <p>Datei hier ablegen</p>\n         <input type="file" id="csvFileInput" accept=".csv" style="display:none;" onchange="handleUpload()">\n      </div>\n      <div class="chart-container" style="width: 100%;">\n         <canvas id="myChart"></canvas>\n      </div>\n      <button onclick="showPreviousData()">Vorheriger Tag</button>\n      <button onclick="showNextData()">N\xc3\xa4chster Tag</button>\n      <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>\n      <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>\n      <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>\n      <script src="script.js"></script>\n   </body>\n</html>\n
Run Code Online (Sandbox Code Playgroud)\n

JS

\n
let startIndex = 0;\nconst displayCount = 1440;\nlet labels = [];\nlet datasets = [];\nlet originalDatasetVisibility = [];\n\nfunction handleUpload() {\n    const fileInput = document.getElementById('csvFileInput');\n    const file = fileInput.files[0];\n    handleFile(file);\n}\n\nfunction processData(csvData) {\n    const rows = csvData.split('\\n');\n    labels = [];\n    datasets = [];\n    originalDatasetVisibility = [];\n\n    const colors = ['rgba(255, 0, 0, 1)', 'rgba(0, 255, 0, 1)', 'rgba(255, 255, 0, 1)', 'rgba(0, 0, 255, 1)'];\n\n    const columns = rows[0].split(';');\n\n    for (let i = 1; i < columns.length; i++) {\n        const data = [];\n        const currentLabel = columns[i];\n        const color = colors[i - 1];\n\n        for (let j = 1; j < rows.length; j++) {\n            const cols = rows[j].split(';');\n            if (i === 1) {\n                labels.push(cols[0]);\n            }\n            data.push(parseFloat(cols[i]));\n        }\n\n        const dataset = {\n            label: currentLabel,\n            data: data,\n            backgroundColor: color,\n            borderColor: color,\n            fill: false,\n            borderWidth: 1,\n            pointRadius: 1,\n        };\n\n        datasets.push(dataset);\n        originalDatasetVisibility.push(true);\n    }\n\n    createChart(labels.slice(startIndex, startIndex + displayCount), datasets, function() {\n        console.log('Diagramm wurde erstellt');\n    });\n}\n\nfunction createChart(labels, datasets, callback) {\n    const chartContainer = document.querySelector('.chart-container');\n    const canvasElement = document.getElementById('myChart');\n\n    if (canvasElement) {\n        chartContainer.removeChild(canvasElement);\n    }\n\n    chartContainer.innerHTML = '<canvas id="myChart"></canvas>';\n\n    const ctx = document.getElementById('myChart').getContext('2d');\n    window.myChart = new Chart(ctx, {\n        type: 'line',\n        data: {\n            labels: labels,\n            datasets: datasets.map((dataset, index) => ({\n                ...dataset,\n                data: dataset.data.slice(startIndex, startIndex + displayCount),\n                hidden: !originalDatasetVisibility[index],\n            })),\n        },\n        options: {\n            scales: {\n                x: {\n                    stacked: true,\n                    min: labels[startIndex],\n                    max: labels[startIndex + displayCount - 1],\n                },\n                y: {},\n            },\n            plugins: {\n                zoom: {\n                    pan: {\n                        enabled: true,\n                        mode: 'x'\n                    },\n                    zoom: {\n                        wheel: {\n                            enabled: true,\n                        },\n                        pinch: {\n                            enabled: true\n                        },\n                        mode: 'x',\n                    }\n                }\n            }\n        }\n    });\n\n    if (callback && typeof callback === 'function') {\n        callback();\n    }\n\n    window.myChart.resetZoom();\n    window.myChart.ctx.canvas.addEventListener('wheel', handleZoom);\n}\n\nfunction handleZoom(event) {\n    const chart = window.myChart;\n    const chartArea = chart.chartArea;\n    const originalDatasets = chart.data.datasets;\n\n    const zoomEnabled = chart.options.plugins.zoom.zoom.wheel.enabled;\n    const deltaY = event.deltaY;\n\n    if (zoomEnabled && deltaY !== 0) {\n        const deltaMode = event.deltaMode;\n        const scaleDelta = deltaY > 0 ? 0.9 : 1.1;\n\n        let newMinIndex = chart.getDatasetMeta(0).data.findIndex(\n            (d) => d.x >= chartArea.left\n        );\n        let newMaxIndex = chart.getDatasetMeta(0).data.findIndex(\n            (d) => d.x >= chartArea.right\n        );\n\n        if (deltaMode === 0) {\n            newMinIndex = Math.max(0, newMinIndex - Math.abs(deltaY));\n            newMaxIndex = Math.min(\n                originalDatasets[0].data.length - 1,\n                newMaxIndex + Math.abs(deltaY)\n            );\n        } else if (deltaMode === 1) {\n            newMinIndex = Math.max(0, newMinIndex - Math.abs(deltaY) * 10);\n            newMaxIndex = Math.min(\n                originalDatasets[0].data.length - 1,\n                newMaxIndex + Math.abs(deltaY) * 10\n            );\n        }\n\n        const newMinLabel = originalDatasets[0].data[newMinIndex].label;\n        const newMaxLabel = originalDatasets[0].data[newMaxIndex].label;\n\n        chart.options.scales.x.min = newMinLabel;\n        chart.options.scales.x.max = newMaxLabel;\n\n        chart.update();\n    }\n}\n\nfunction handleFile(file) {\n    if (file) {\n        const reader = new FileReader();\n\n        reader.onload = function (e) {\n            const csvData = e.target.result;\n            processData(csvData);\n        };\n\n        reader.readAsText(file);\n    } else {\n        alert('Bitte eine CSV-Datei ausw\xc3\xa4hlen.');\n    }\n}\n\nfunction handleDrop(event) {\n    event.preventDefault();\n    const file = event.dataTransfer.files[0];\n    handleFile(file);\n}\n\nfunction handleDragOver(event) {\n    event.preventDefault();\n}\n\nfunction showPreviousData() {\n    if (startIndex - displayCount >= 0) {\n        startIndex -= displayCount;\n        updateChart();\n    }\n}\n\nfunction showNextData() {\n    if (startIndex + displayCount < labels.length) {\n        startIndex += displayCount;\n        updateChart();\n    }\n}\n\nfunction updateChart() {\n    const endIndex = Math.min(startIndex + displayCount, labels.length);\n    const updatedLabels = labels.slice(startIndex, endIndex);\n    const updatedDatasets = datasets.map((dataset, index) => ({\n        ...dataset,\n        data: dataset.data.slice(startIndex, endIndex),\n        hidden: !originalDatasetVisibility[index],\n    }));\n\n    window.myChart.data.labels = updatedLabels;\n    window.myChart.data.datasets = updatedDatasets;\n    window.myChart.options.scales.x.min = updatedLabels[0];\n    window.myChart.options.scales.x.max = updatedLabels[updatedLabels.length - 1];\n\n    window.myChart.update();\n}\n\nfunction removeZoomEventListener() {\n    window.myChart.ctx.canvas.removeEventListener('wheel', handleZoom);\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

win*_*ner 1

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

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

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

ChartJs相关

  1. 查看 Chartjs官方性能“提示和技巧”

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

    图 1:细微调整 ~ 70 秒(5255999 数据行) 无秤 ~ 70 秒

    图 2:细微调整 + 固定缩放 ~ 3 秒(5255999 数据行) 一些比例调整 ~ 4 秒

  2. 准备数据以便于使用

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

在数据/Web端

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

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