【问题标题】:How do I update and remove groups of bars in a bar chart?如何更新和删除条形图中的条形组?
【发布时间】:2017-12-13 16:48:10
【问题描述】:

我使用 d3 v4 已经有几个月的时间了,并且做了很多研究。我终于掌握了数据连接的概念,但在试图弄清楚如何更新分组条形图时遇到了困难。

我认为我的问题是我在执行连接时没有选择正确的 SVG 元素,因此 update()exit() 方法总是返回空集。这是我基于 d3 块的代码:Codepen 请参见 #135 行,其中所有的魔法应该正在发生。

/* Based on https://bl.ocks.org/mbostock/3887051 */

var svg = d3.select("svg"),
  margin = { top: 20, right: 20, bottom: 100, left: 40 },
  width = +svg.attr("width") - margin.left - margin.right,
  height = +svg.attr("height") - margin.top - margin.bottom,
  g = svg
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var formatTime = d3.timeFormat("%X");
var legendKeys = new Map();
var x0 = d3
  .scaleBand()
  .rangeRound([0, width])
  .paddingInner(0.1);

var x1 = d3.scaleBand().padding(0.05);

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

var z = d3
  .scaleOrdinal()
  .range([
    "#98abc5",
    "#8a89a6",
    "#7b6888",
    "#6b486b",
    "#a05d56",
    "#d0743c",
    "#ff8c00"
  ]);

var data = [];
for (var i = 0; i < 5; i++) {
  generateData();
}

var keys = Object.keys(data[0].y);
keys.forEach(k => legendKeys.set(".data-" + k.replace(/ /g, "")));
g.append("g").attr("class", "bars");

var xAxis = g
  .append("g")
  .attr("class", "x--axis")
  .attr("transform", "translate(0," + height + ")")
  .call(d3.axisBottom(x0).tickFormat(formatTime));

var yAxis = g
  .append("g")
  .attr("class", "y--axis")
  .call(d3.axisLeft(y).ticks(null, "s"));

var legend = g
  .append("g")
  .attr("font-family", "sans-serif")
  .attr("font-size", 8)
  .attr("text-anchor", "end")
  .attr("class", "legend")
  .selectAll("g")
  .data(keys.slice())
  .enter()
  .append("g")
  .attr("transform", function(d, i) {
    return "translate(0," + i * 20 + ")";
  });

legend
  .append("rect")
  .attr("x", width - 19)
  .attr("width", 19)
  .attr("height", 19)
  .attr("style", "cursor:pointer")
  .attr("fill", z)
  .on("click", function(d) {
    var key = ".data-" + d.replace(/ /g, "");
    // determine if current line is visible
    var active = legendKeys.get(key) ? false : true,
      newOpacity = active ? 0 : 1;
    // hide or show the elements
    d3.selectAll(key).style("opacity", newOpacity);
    // update whether or not the elements are active
    legendKeys.set(key, active);
    this.style.opacity = active ? 0.5 : 1;
  });

legend
  .append("text")
  .attr("x", width - 24)
  .attr("y", 9.5)
  .attr("dy", "0.32em")
  .text(function(d) {
    return d;
  });

window.setInterval(() => {
  generateData();
  if (data.length > 14) {
    data.shift();
  }
  updateChart();
}, 1000);

function updateChart() {
  x0.domain(
    data.map(function(d) {
      return d.x;
    })
  );
  x1.domain(keys).rangeRound([0, x0.bandwidth()]);
  y
    .domain([
      0,
      d3.max(data, function(d) {
        return d3.max(keys, function(key) {
          return d.y[key];
        });
      })
    ])
    .nice();

  d3.select(".x--axis")
    .transition()
    .call(d3.axisBottom(x0).tickFormat(formatTime));

  d3.select(".y--axis")
    .transition()
    .call(d3.axisLeft(y));

  var barGroups = g
    .select(".bars")
    .selectAll(".barGroup")
    .data(data);

  barGroups
    .enter()
    .append("g")
    .attr("transform", function(d) {
      return "translate(" + x0(d.x) + ",0)";
    })
    .attr("class", "barGroup")
    .selectAll("rect")
    .data(function(d) {
      return keys.map(function(key) {
        return { key: key, value: d.y[key] };
      });
    })
    .enter()
    .append("rect")
    .attr("x", function(d) {
      return x1(d.key);
    })
    .attr("y", function(d) {
      return y(d.value);
    })
    .attr("width", x1.bandwidth())
    .attr("height", function(d) {
      return height - y(d.value);
    })
    .attr("class", function(d) {
      return "data-" + d.key.replace(/ /g, "");
    })
    .attr("fill", function(d) {
      return z(d.key);
    })
    .merge(barGroups)
    .attr("width", x1.bandwidth())
    .attr("height", function(d) {
      return height - y(d.value);
    })
    .attr("x", function(d) {
      return x1(d.key);
    })
    .attr("y", function(d) {
      return y(d.value);
    });

  // Exit
  var old = barGroups
    .exit()
    .remove();

  // Release memory for elements that were removed
  old = null;
}

function generateData() {
  var lastDate = new Date();
  if (data.length > 0) {
    lastDate = new Date(data[data.length - 1].x);
  }
  lastDate.setHours(lastDate.getHours() + 1);
  
  data.push({
    x: lastDate,
    y: {
      A: d3.randomUniform(0, 100)(),
      B: d3.randomUniform(0, 100)(),
      C: d3.randomUniform(0, 100)()
    }
  });
}
/* Based on https://bl.ocks.org/mbostock/3887051 */
rect.bar {
  fill: steelblue;
}

text {
  fill: #000;
  font: 11px sans-serif;
  font-weight:500;
}

/* body {
  background-color: #f00;
} */

.legend rect:hover {
  stroke-width:0.5;
  stroke: black;
}

svg {
  background-color:#eee;
  shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.11.0/d3.min.js"></script>
<!-- Based on https://bl.ocks.org/mbostock/3887051 -->
<svg width="900" height="400"></svg>

【问题讨论】:

  • 群组似乎更新得很好,是条形图没有更新。他们将需要自己的更新模式。
  • @RyanMorton 我该怎么做?
  • 在回答这个问题 (stackoverflow.com/questions/38921456/…) 时,有两个变量(状态和条形图)都遵循更新模式。我认为它展示了通常所做的事情。
  • @RyanMorton 我看了你提到的那个问题,但我仍然没有看到如何更新/删除这些栏组。同样在引用的问题中,他们正在使用 d3 v3,并且在 d3 v4 中更改了 enter.append 行为。

标签: javascript d3.js graph


【解决方案1】:

我提供了用于更新分组条形图的这种模式。无需深入研究您的代码,就可以对其进行调整。

    //update pattern for bar chart
    var groups = this.plot.selectAll('.bar-group').data(data, function(d) { return d; });

    //EXIT bars no longer in the data   
    groups.exit()
     .transition()
      .duration(300)
     .remove();

    //ENTER bars with new data
    var bars = groups.enter().append("g")
            .attr('class', 'bar-group')
            .attr("transform", function(d) { return "translate(" + that.x0Scale(d[that.x_var]) + ",0)"; })
          .merge(groups)
            .selectAll('rect')
            .data(function(d) { return that.grouping.map(function(key) { return {key: key, value: +d[key]}; }); });

    //EXIT bars in group just in case that changed
    bars.exit().transition().duration(300).attr('height', 0).attr("y", that.yScale(0)).remove()

    bars.enter().append("rect")
            .attr('class', 'bar')
            .attr("x", function(d) { return that.x1Scale(d.key); })
            .attr("y", that.yScale(0));
        .merge(bars)
        .transition().duration(500)
            .attr("x", function(d) { return that.x1Scale(d.key); })
            .attr("y", function(d) { return that.yScale(Math.max(0, d.value)); })
            .attr("width", d3.min([that.x1Scale.bandwidth(), 200]))
            .attr("height", function(d) { return Math.abs(that.yScale(0)- that.yScale(d.value)); })
            .attr("fill", function(d) { return that.zScale(d.key); })
            .attr("opacity", 0.85 );

【讨论】:

  • 在查看了您的示例后,我仍然无法使其正常工作。我更新了我的Codepen 以反映这一点。
  • 您没有提供第一个数据连接的密钥,也没有合并组。让我看看我能不能让 codepen 工作。
  • 这是你的想法吗:codepen.io/mortonanalytics/pen/baRZQd
  • 感谢您修改我的示例,我现在看到了我缺少的内容。我现在意识到在调用数据方法时指定键函数的重要性。
猜你喜欢
  • 1970-01-01
  • 2013-05-10
  • 2018-05-06
  • 2013-04-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-26
  • 1970-01-01
  • 2023-03-24
相关资源
最近更新 更多