如何在更新d3.js图表​​之前干净地删除数据?

时间:2016-06-07 19:50:20

标签: javascript d3.js

我对D3很陌生,一直在努力解决所有问题。我正在尝试配置此示例here以使用新数据进行更新并进行适当的转换。

这是我配置的代码笔(单击提交更新) http://codepen.io/anon/pen/pbjLRW?editors=1010

从我可以收集的内容来看,使用.exit()的一些变体来进行干净的数据转换是必需的,但在阅读了一些教程后,我仍然发现很难知道它是如何工作的。我已经看过在调用绘图函数之前简单地删除容器的示例,但是在我有限的经验中,它会在更改数据时导致闪烁,所以我不确定它是否是最佳实践?

现在,我不确定为什么我的codepen中的数据没有正确更新,但我主要担心的是尝试正确转换。理想情况下,我想知道在更改数据时如何移动针,因此它将从90>例如,代替90> 0> 40.

但是,一旦点击链接的代码集中的提交,我肯定会找出为什么它不会在同一位置重绘自己。

这是我的更新功能;

function updateGuage() {
  d3.selectAll("text").remove()
  d3.selectAll('.needle').remove()
  chart.remove()
  name = "qwerty";
  value = "25";
  drawGuage();
}
初步抽签;

function drawGuage() {
  percToDeg = function(perc) {
    return perc * 360;
  };

  percToRad = function(perc) {
    return degToRad(percToDeg(perc));
  };

  degToRad = function(deg) {
    return deg * Math.PI / 180;
  };

  // Create SVG element
  svg = el.append('svg').attr('width', width + margin.left + margin.right).attr('height', height + margin.top + margin.bottom);

  // Add layer for the panel
  chart = svg.append('g').attr('transform', "translate(" + ((width + margin.left) / 2) + ", " + ((height + margin.top) / 2) + ")");

  chart.append('path').attr('class', "arc chart-first");
  chart.append('path').attr('class', "arc chart-second");
  chart.append('path').attr('class', "arc chart-third");

  arc3 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
  arc2 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
  arc1 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)

  repaintGauge = function() {
      perc = 0.5;
      var next_start = totalPercent;
      arcStartRad = percToRad(next_start);
      arcEndRad = arcStartRad + percToRad(perc / 3);
      next_start += perc / 3;

      arc1.startAngle(arcStartRad).endAngle(arcEndRad);

      arcStartRad = percToRad(next_start);
      arcEndRad = arcStartRad + percToRad(perc / 3);
      next_start += perc / 3;

      arc2.startAngle(arcStartRad + padRad).endAngle(arcEndRad);

      arcStartRad = percToRad(next_start);
      arcEndRad = arcStartRad + percToRad(perc / 3);

      arc3.startAngle(arcStartRad + padRad).endAngle(arcEndRad);

      chart.select(".chart-first").attr('d', arc1);
      chart.select(".chart-second").attr('d', arc2);
      chart.select(".chart-third").attr('d', arc3);

    }
    /////////

  var texts = svg.selectAll("text")
    .data(dataset)
    .enter();

  texts.append("text")
    .text(function() {
      return dataset[0].metric;
    })
    .attr('id', "Name")
    .attr('transform', "translate(" + ((width + margin.left) / 6) + ", " + ((height + margin.top) / 1.5) + ")")
    .attr("font-size", 25)
    .style("fill", "#000000");

  var trX = 180 - 210 * Math.cos(percToRad(percent / 2));
  var trY = 195 - 210 * Math.sin(percToRad(percent / 2));
  // (180, 195) are the coordinates of the center of the gauge.

  displayValue = function() {
    texts.append("text")
      .text(function() {
        return dataset[0].value;
      })
      .attr('id', "Value")
      .attr('transform', "translate(" + trX + ", " + trY + ")")
      .attr("font-size", 18)
      .style("fill", '#000000');
  }

  texts.append("text")
    .text(function() {
      return 0;
    })
    .attr('id', 'scale0')
    .attr('transform', "translate(" + ((width + margin.left) / 100) + ", " + ((height + margin.top) / 2) + ")")
    .attr("font-size", 15)
    .style("fill", "#000000");

  texts.append("text")
    .text(function() {
      return gaugeMaxValue / 2;
    })
    .attr('id', 'scale10')
    .attr('transform', "translate(" + ((width + margin.left) / 2.15) + ", " + ((height + margin.top) / 30) + ")")
    .attr("font-size", 15)
    .style("fill", "#000000");

  texts.append("text")
    .text(function() {
      return gaugeMaxValue;
    })
    .attr('id', 'scale20')
    .attr('transform', "translate(" + ((width + margin.left) / 1.03) + ", " + ((height + margin.top) / 2) + ")")
    .attr("font-size", 15)
    .style("fill", "#000000");

  var Needle = (function() {

    //Helper function that returns the `d` value for moving the needle
    var recalcPointerPos = function(perc) {
      var centerX, centerY, leftX, leftY, rightX, rightY, thetaRad, topX, topY;
      thetaRad = percToRad(perc / 2);
      centerX = 0;
      centerY = 0;
      topX = centerX - this.len * Math.cos(thetaRad);
      topY = centerY - this.len * Math.sin(thetaRad);
      leftX = centerX - this.radius * Math.cos(thetaRad - Math.PI / 2);
      leftY = centerY - this.radius * Math.sin(thetaRad - Math.PI / 2);
      rightX = centerX - this.radius * Math.cos(thetaRad + Math.PI / 2);
      rightY = centerY - this.radius * Math.sin(thetaRad + Math.PI / 2);

      return "M " + leftX + " " + leftY + " L " + topX + " " + topY + " L " + rightX + " " + rightY;

    };

    function Needle(el) {
      this.el = el;
      this.len = width / 2.5;
      this.radius = this.len / 8;
    }

    Needle.prototype.render = function() {
      this.el.append('circle').attr('class', 'needle-center').attr('cx', 0).attr('cy', 0).attr('r', this.radius);

      return this.el.append('path').attr('class', 'needle').attr('id', 'client-needle').attr('d', recalcPointerPos.call(this, 0));

    };

    Needle.prototype.moveTo = function(perc) {
      var self,
        oldValue = this.perc || 0;

      this.perc = perc;
      self = this;

      // Reset pointer position
      this.el.transition().delay(100).ease('quad').duration(200).select('.needle').tween('reset-progress', function() {
        return function(percentOfPercent) {
          var progress = (1 - percentOfPercent) * oldValue;

          repaintGauge(progress);
          return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
        };
      });

      this.el.transition().delay(300).ease('bounce').duration(1500).select('.needle').tween('progress', function() {
        return function(percentOfPercent) {
          var progress = percentOfPercent * perc;

          repaintGauge(progress);
          return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
        };
      });

    };

    return Needle;

  })();

  needle = new Needle(chart);
  needle.render();
  needle.moveTo(percent);

  setTimeout(displayValue, 1350);

}

非常感谢任何帮助/建议,

由于

2 个答案:

答案 0 :(得分:3)

你想看看的是{Bodock写的How selections work。阅读本文后,输入更新退出选项周围的所有内容都将变得更加清晰。

简而言之:

  1. 您使用selectAll('li')
  2. 创建了一系列元素
  3. 通过调用data([...])
  4. 加入选择数据数组
  5. 现在D3将已经在DOM中的内容与已加入的数据进行比较。以这种方式处理的每个DOM元素都有__data__属性,允许D3 绑定数据项到元素。
  6. 在您加入数据后,您会通过致电enter()收到输入选择。这是尚未绑定到所选DOM元素的每个数据元素。通常,您使用输入选择来创建新元素,例如通过append()
  7. 致电exit()即表示您收到退出选择。这些都是已存在的DOM元素,在连接后不再具有关联的数据项。通常,您使用退出选择来删除remove()
  8. 的DOM元素
  9. 所谓的更新选择是在使用data()加入选择后返回的一件事。您需要将更新选择存储在变量中,这样即使在调用enter()exit()后您也可以访问它。
  10. 注意d3v3和d3v4之间的区别:

    d3v3 中,当您已经通过回车选择添加了元素时,更新选择也包括那些新创建的DOM元素。了解更新选择在您创建新元素后发生变化至关重要。

    但是,使用 d3v4 时,不再为真。更改日志显示

      

    "此外,selection.append不再将输入节点合并到更新选择中;使用selection.merge在数据连接后组合输入和更新。"

答案 1 :(得分:0)

重要的是要知道绑定数据后可以执行三种不同的操作。处理添加,删除和修改未更改(或之前已添加)的内容。

以下是创建和操作简单列表的示例:http://jsbin.com/sekuhamico/edit?html,css,js,output

var update = () => {

  // bind data to list elements
  // think of listWithData as a virtual representation of 
  // the array of list items you will later see in the
  // DOM. d3.js does not handle the mapping from this 
  // virtual structure to the DOM for you. It is your task
  // to define what is to happen with elements that are
  // added, removed or updated.
  var listWithData = ul.selectAll('li').data(listItems);

  // handle additions
  // by calling enter() on our virtual list, you get the
  // subset of entries which need to be added to the DOM
  // as their are not yet present there.
  listWithData.enter().append('li').text(i => i.text).on('click', i => toggle(i));

  // handle removal
  // by calling exit() on our virtual list, you get the
  // subset of entries which need to be removed from the
  // DOM as they are not longer present in the virtual list.
  listWithData.exit().remove();

  // update existing
  // acting directly on the virtual list will update any
  // elements currently present in the DOM. If you would
  // execute this line before calling exit(), you would 
  // also manipulate those items to be removed. If you
  // would even call it before calling enter() you would
  // miss on updating the newly added element.
  listWithData.attr('class', i => i.active ? 'active' : '');

};

请注意,实际上您可能需要为您的商品添加某种ID。为确保删除正确的商品,您不会遇到订购问题。

解释

更新功能一无所知,甚至一无所知。它不知道,也不关心是否有新的数据元素或旧的数据元素已被删除。但这两件事都可能发生。因此,我们分别通过调用enter()exit()来处理这两种情况。 d3函数enter()exit()为我们提供了应添加或删除的列表元素的子集。最后,我们需要处理现有数据的变化。



var listItems = [{ text: 1, active: false}, { text: 2, active: true}];

var ul = d3.select('#id').append('ul');


var update = () => {

    var listWithData = ul.selectAll('li').data(listItems);
  
    // add new 
    listWithData.enter().append('li').text(i => i.text).on('click', i => toggle(i));

    // remove old
    listWithData.exit().remove();

    // update existing
    listWithData.attr('class', i => i.active ? 'active' : '');
  
};

update();

$('#add').click(() => {
  listItems.push({
    text: listItems.length+1,
    active: false
  });
  update();
});

var toggle = (i) => {
  i.active = !i.active;
  update();
};

li.active {
  background-color:lightblue;
}

li {
  padding: 5px;
}

<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <div id="id"></div>
  <button id="add">Add</button>
</body>
</html>
&#13;
&#13;
&#13;