d3.js - D3 从下拉菜单中选择新数据后如何更新图表

标签 d3.js

我正在 D3 中构建瀑布图。当页面加载时,它将呈现默认页面,但用户可以选择不同的 下拉菜单中的“公司”和“年份”。我已经能够创建我想要的图表。但是,当我选择任何不同的公司或年份时,D3 会在现有图表之上添加另一个图表,而不是替换它,这是因为我的目标是 HTML 中的特定 div/svg。 如何使用 D3 用新数据更新图表而不是添加另一个顶部数据?如果我可以通过过渡实现图表条形的移动,那就太棒了。

HTML 是一个简单的 svg:

<svg class="chart"></svg>

这是创建图表的函数,当 Ajax 调用成功时我会调用它:

function waterfallChart (dataset) {

            var data = [];

            for (var key in dataset[0]) {
              data.push({
                name: key,
                value: dataset[0][key]
              })
            }

        var margin = {top: 20, right: 30, bottom: 30, left: 40},        
            width = 960 - margin.left - margin.right,
            height = 500 - margin.top - margin.bottom,
            padding = 0.3;

    var x = d3.scaleBand()
            .domain(data.map(function(d) {
                return d.name
                }))
            .range([0, width])
            .padding(padding);

    var y = d3.scaleLinear()
        .range([height, 0]);

    var xAxis = d3.axisBottom(x)


    var yAxis = d3.axisLeft(y)
        .tickFormat(function(d) {
            return dollarFormatter(d);
        });

    var chart = d3.select(".chart")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        var cumulative = 0;
        for (var i = 0; i < data.length; i++) {
            data[i].start = cumulative;
            cumulative += data[i].value;
            data[i].end = cumulative;

            data[i].class = (data[i].value >= 0) ? 'positive' : 'negative'
        }
        data.push({
            name: 'Total',
            end: cumulative,
            start: 0,
            class: 'total'
        });

        x.domain(data.map(function(d) {
            return d.name;
        }));
        y.domain([0, d3.max(data, function(d) {
            return d.end;
        })]);

        chart.append("g")
            .attr("class", "x axis")
            .attr("transform", "translate(0," + height + ")")
            .call(xAxis);

        chart.append("g")
            .attr("class", "y axis")
            .call(yAxis);

        var bar = chart.selectAll(".bar")
            .data(data)
            .enter().append("g")
            .attr("class", function(d) {
                return "bar " + d.class
            })
            .attr("transform", function(d) {
                return "translate(" + x(d.name) + ",0)";
            });

        bar.append("rect")
            .attr("y", function(d) {
                return y(Math.max(d.start, d.end));
            })
            .attr("height", function(d) {
                return Math.abs(y(d.start) - y(d.end));
            })
            .attr("width", x.bandwidth());

        bar.append("text")
            .attr("x", x.bandwidth() / 2)
            .attr("y", function(d) {
                return y(d.end) + 5;
            })
            .attr("dy", function(d) {
                return ((d.class == 'negative') ? '-' : '') + ".75em"
            })
            .text(function(d) {
                return dollarFormatter(d.end - d.start);
            });

        bar.filter(function(d) {
                return d.class != "total"
            }).append("line")
            .attr("class", "connector")
            .attr("x1", x.bandwidth() + 5)
            .attr("y1", function(d) {
                return y(d.end)
            })
            .attr("x2", x.bandwidth() / (1 - padding) - 5)
            .attr("y2", function(d) {
                return y(d.end)
            })

            function dollarFormatter(n) {
                  n = Math.round(n);
                  var result = n;
                  if (Math.abs(n) > 1000) {
                    result = Math.round(n/1000) + 'B';
                  }
                  return '$ ' + result;
                }
        }

这是我有事件监听器的代码,选择后它将运行上述函数:

$("#airline-selected, #year-selected").change(function chartsData(event) {
            event.preventDefault();
            var airlineSelected = $('#airline-selected').find(":selected").val();
            var yearSelected = $('#year-selected').find(":selected").val();

            $.ajax({
                url: "{% url 'airline_specific_filtered' %}",
                method: 'GET',
                data : {
                    airline_category: airlineSelected,
                    year_category: yearSelected
                        },
                success: function(dataset){
                        waterfallChart(dataset)
         },
                error: function(error_data){
                console.log("error")
                console.log(error_data)
         }
            })
        });

最佳答案

你在这里遗漏了一些非常重要的东西。如果您要更新数据,您需要做几件事。

  1. 提供 data() 函数的 key 。您需要为 D3 提供一种在更新数据时识别数据的方法,以便它知道是否应该添加、删除或保留现有数据。 key 就是这样做的。例如,您可能会这样做:

    .data(data, function(d) { return d.name })
    

    现在,假设 d.name 是唯一标识符,d3 将能够告诉您数据项。

  2. 对于更新期间删除的数据,您需要一个 exit()。您需要保存数据连接选择,以便可以对其调用 enterexit:

    var bar = chart.selectAll(".bar")
              .data(data, function(d) { return d.name})
    

    现在您可以调用:bar.exit().remove() 来删除已删除的项目,并调用 bar.enter() 来添加项目。

  3. 您需要进行未调用 enter() 来更新属性的选择。

  4. 可能更多的是风格问题,但您应该在更新函数之外设置 SVG 和边距,因为它们声明相同。您仍然可以通过调用更新中的相应函数来更新轴和比例。

您发布的代码对于其他人来说运行起来有点困难 - 如果您发布的代码已简化为主要问题并且其他人无需访问异地数据或 API 即可运行,那么您总是会获得更好更快的答案.

以下示例根据您的代码更新两个数据集之间的 setInterval。但您还应该查看常规更新模式 - 它们非常简单,但几乎包含您需要了解的所有内容。 (https://bl.ocks.org/mbostock/3808234)

dataset = [
        {name: "Albert", start: 0, end:220},
        {name: "Mark", start: 0, end:200},
        {name: "Søren", start: 0, end:100},
        {name: "Immanuel", start: 0, end:60},
        {name: "Michel", start: 0, end:90},
        {name: "Jean Paul", start: 0, end: 80}
    ]
     dataset2 = [
        {name: "Albert", start: 0, end:20},
         {name: "Immanuel", start:0, end:220},
        {name: "Jaques", start: 0, end:100},
        {name: "Gerhard", start:0 , end:50},
        {name: "Søren", start: 0, end:150},
        {name: "William", start: 0, end: 180}
    ]

var margin = {
    top: 10,
    right: 30,
    bottom: 30,
    left: 40
  },
  width = 400 - margin.left - margin.right,
  height = 200 - margin.top - margin.bottom,
  padding = 0.3;

var chart = d3.select(".chart")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var x = d3.scaleBand()
  .range([0, width])
  .padding(padding);

var y = d3.scaleLinear()
  .range([height, 0])

chart.append("g")
  .attr("class", "x axis")
  .attr("transform", "translate(0," + height + ")")
chart.append("g")
  .attr("class", "y axis")

var currentData = dataset

waterfallChart(currentData)

setInterval(function() {
  currentData = currentData === dataset ? dataset2 : dataset
  waterfallChart(currentData)
}, 3000)

function waterfallChart(data) {
  var t = d3.transition()
    .duration(750)

  x.domain(data.map(function(d) {
    return d.name
  }))
  y.domain([0, d3.max(data, function(d) {
    return d.end
  })])
  var xAxis = d3.axisBottom(x)

  var yAxis = d3.axisLeft(y)

  d3.select('g.x').transition(t).call(xAxis)
  d3.select('g.y').call(yAxis)

  var bar = chart.selectAll(".bar")
    .data(data, function(d) {
      return d.name
    })

  // ENTER -- ADD ITEMS THAT ARE NEW IN DATA
  bar.enter().append("g")
    .attr("transform", function(d) {
      return "translate(" + x(d.name) + ",0)"
    })
    .attr("class", 'bar')
    .append("rect")
    .attr("y", function(d) {
      return y(Math.max(d.start, d.end));
    })
    .attr("height", function(d) {
      return Math.abs(y(d.start) - y(d.end));
    })
    .attr("width", x.bandwidth())

  // UPDATE EXISTING ITEMS
  chart.selectAll(".bar")
    .transition(t)
    .attr("transform", function(d) {
      return "translate(" + x(d.name) + ",0)"
    })
    .select('rect')
    .attr("y", function(d) {
      return y(Math.max(d.start, d.end))
    })
    .attr("height", function(d) {
      return Math.abs(y(d.start) - y(d.end))
    })
    .attr("width", x.bandwidth())

  // REMOVE ITEMS DELETED FROM DATA
  bar.exit().remove()
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg class="chart"></svg>

关于d3.js - D3 从下拉菜单中选择新数据后如何更新图表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45579350/

相关文章:

javascript - 集成 angularjs 和 d3,简单的数据绑定(bind)示例

javascript - SVG 路径在 D3 力网络图中无法正确渲染

javascript - d3 v4 tree.nodeSize 问题。即使应用平移后,根也设置为 0,0

php - 最后一步,将MySQL与PHP连接到JSON与D3(javascript)很酷的东西

javascript - 如何复制 SVG 的内容并将其附加到另一个 SVG 框架?

javascript - d3.js 和 NVD3 axisLabel 带有 £(英镑)符号

javascript - d3.js - 如何水平扩展力定向图?

javascript - 在 d3.selectAll 中使用 attr 方法时出现“未定义”

google-chrome - 在 Chrome 上转换后 SVG textPath 不可见

javascript - 使用 jquery 在 nvd3 中触发图例单击事件?