You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何仅下载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>

关键修改点

  1. 将下载事件的绑定移到CSV数据加载完成后的初始化阶段,只执行一次,避免重复绑定。
  2. 在下载事件处理函数中,动态获取当前下拉菜单选中的值,然后过滤出对应的数据进行下载。
  3. 删除updateGraph函数中调用downloadData的代码,不再每次更新图表都绑定事件。
  4. 优化下载文件名,用当前选中的品类命名,更直观。

内容的提问来源于stack exchange,提问作者sprucegoose

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 02:15:57