如何仅下载d3.js图表中当前选择项对应的CSV数据?
解决D3.js切换数据后下载多个CSV文件的问题
问题原因
每次切换下拉选项调用updateGraph时,都会执行downloadData函数,而该函数每次都会给下载按钮新增一个click事件监听器。切换选项的次数越多,按钮上绑定的事件就越多,点击下载时会触发所有绑定的事件,导致下载多个文件。
解决方案
推荐采用只绑定一次下载事件的方式,避免重复绑定。在初始化时给下载按钮绑定一次事件,点击时动态获取当前选中项对应的数据,而不是每次更新图表都传递数据并重复绑定事件。
修正后的完整代码
JavaScript部分
var margin = { top: 50, right: 20, bottom: 30, left: 60 }, width = 1000 - margin.left - margin.right, height = 550 - margin.top - margin.bottom; var x = d3.scaleTime().range([0, width]); var y = d3.scaleLinear().range([height, 0]); var xAxis = d3.axisBottom(x) .ticks(5) const parseTime = d3.utcParse("%Y"); var yAxis = d3.axisLeft(y).ticks(5); var line = d3.line() .x(function (d) { return x(d.year); }) .y(function (d) { return y(d.value); }) .curve(d3.curveBasis); var color = d3.scaleOrdinal() .range(['royalblue', 'green', 'pink']); var svg = d3.select("#chart") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); d3.csv("https://raw.githubusercontent.com/sprucegoose1/sample-data/main/fruit.csv").then(function (csv) { csv.forEach(function (d) { d.value = +d.value; d.year = parseTime(d.year); }); // 初始化下载事件,只绑定一次 document.getElementById('download').addEventListener('click', function() { const selectedProduce = document.getElementById('inds').value; const currentData = csv.filter(d => d.produce === selectedProduce); let blob = new Blob([d3.csvFormat(currentData)], { type: "text/csv;charset=utf-8" }); saveAs(blob, `${selectedProduce}_data.csv`); }); initData = csv.filter(function (d) { return d.produce == 'apples' }) updateGraph(initData) d3.select('#inds') .on("change", function () { var sect = document.getElementById("inds"); var section = sect.options[sect.selectedIndex].value; data = csv.filter(function (d) { return d.produce == section }) data.forEach(function (d) { d.value = +d.value; d.active = true; }); updateGraph(data); }); data = csv.filter(function (d) { return d.produce == 'apples' }) updateGraph(data); }); function updateGraph(data) { x.domain(d3.extent(data, function (d) { return d.year; })); y.domain([d3.min(data, function (d) { return d.value; }), d3.max(data, function (d) { return d.value; })]); let dataNest = d3.group(data, d => d.state) let dataNestArray = Array.from(dataNest.keys()); var state = svg.selectAll(".line") .data(dataNest, function (d) { return d[0] }); state.enter().append("path") .attr("class", "line"); state.transition() .style("stroke", "#333") .attr("d", d => { d.line = this; return line(d[1]); }) .style("stroke", d => color(d[0])) state.exit().remove(); var legend = d3.select("#legend") .selectAll("text") .data(dataNest, function (d) { return d.key }); svg.selectAll(".axis").remove(); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); svg.append("g") .attr("class", "y axis") .call(yAxis); // 移除原有的downloadData调用,不再重复绑定事件 };
CSS部分
body { font: 12px Arial; } path { stroke: #ccc; stroke-width: 2; fill: none; } .axis path, .axis line { fill: none; stroke: grey; stroke-width: 1; shape-rendering: crispEdges; } #legendContainer { position: absolute; top: 60px; left: 10px; overflow: auto; height: 490px; width: 110px; } #legend { width: 90px; height: 200px; } .legend { font-size: 12px; font-weight: normal; text-anchor: left; } .legendcheckbox { cursor: pointer; } input { border-radius: 5px; padding: 5px 10px; background: #999; border: 0; color: #fff; } #inds { position: absolute; top: 10px; left: 10px; } #download { margin-top: 30px; margin-left: 30px; text-decoration: underline; color: darkred; cursor: pointer; }
HTML部分
<script src="https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script> <select id="inds"> <option value="apples" selected="selected">apples</option> <option value="pears">pears</option> <option value="tomatoes">tomatoes</option> </select> <div id="chart"></div> <div id="download">Download Data</div>
关键修改点
- 将下载事件的绑定移到CSV数据加载完成后的初始化阶段,只执行一次,避免重复绑定。
- 在下载事件处理函数中,动态获取当前下拉菜单选中的值,然后过滤出对应的数据进行下载。
- 删除
updateGraph函数中调用downloadData的代码,不再每次更新图表都绑定事件。 - 优化下载文件名,用当前选中的品类命名,更直观。
内容的提问来源于stack exchange,提问作者sprucegoose
相关产品推荐
相关产品推荐

