Simon Budin Simon Budin - 13 days ago 5
Javascript Question

Sub selection deletion with animations

I have a set of nodes data

var dataNodes = [
{ id: 1, x: 10, y:30, text: "node 1", muteText: false },
{ id: 2, x: 30, y:50, text: "node 2", muteText: false },
{ id: 3, x: 50, y:70, text: "node 3", muteText: false }
];


I add the elements in the DOM using this kind of function (not my real code because of a lot of business complexity) :

function redraw(newData) {
var node = d3
.select("body")
.selectAll("g.node")
.data(dataNodes)
.transition().duration(500)
.attr("transform", d => "translate(" + d.x + "," + d.y + ")");

node.enter()
.append("g")
.attr("class", "node")
.attr("transform", d => "translate(" + d.x + "," + d.y + ")")
.append("text")
.text(d => d.text)
.style("opacity", "0")
.transition().duration(500)
.style("opacity", "1");

node.exit()
.style("opacity", "0");
}


I want to be able to do all the following when the data get updated:


  • add entering nodes

  • make already existing nodes move with a transition

  • hide exitings nodes (opacity 0) because they may reappear

  • when nodes get their "muteText" property changed to true, make the inner text disapear



I'm quite confortable with the 3 first requiremeents but I really don't know how to do the last one : how can I remove (or even change) sub elements based on a filtered set of data ? Can I use the filter in the d3.data function to do it ?

Let me know if my question is unclear.

Answer

If you want to filter, do it on your update selection:

  var node = svg
    .selectAll("g.node")
    .data(someData);

  var nodeE = node.enter()
    .append("g")
    .attr("class", "node");

  nodeE.append("text")
    .text(d => d.text);

  // node is now UPDATE + ENTER
  node = nodeE.merge(node);

  // filter the text and set how you care
  node.filter(function(d) {
      return d.muteText
    })
    .select("text")
    .style("opacity", 1)
    .transition()
    .style("opacity", 0);

  node.filter(function(d) {
      return !d.muteText
    })
    .select("text")
    .style("opacity", 0)
    .transition()
    .style("opacity", 1);

Here's a running example:

<!DOCTYPE html>
<html>

<head>
  <script data-require="d3@4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
  <style>
    text {
      fill: black;
      font-family: arial;
    }
  </style>
</head>

<body>
  <script>
    var dataNodes = [{
      id: 1,
      x: 10,
      y: 30,
      text: "node 1",
      muteText: false
    }, {
      id: 2,
      x: 30,
      y: 50,
      text: "node 2",
      muteText: false
    }, {
      id: 3,
      x: 50,
      y: 70,
      text: "node 3",
      muteText: false
    }];

    var svg = d3.select('body')
      .append('svg')
      .attr('width', 500)
      .attr('height', 500);

    redraw([{
        id: 1,
        x: 10,
        y: 30,
        text: "node 1",
        muteText: false
      }, {
        id: 2,
        x: 30,
        y: 50,
        text: "node 2",
        muteText: false
      }, {
        id: 3,
        x: 50,
        y: 70,
        text: "node 3",
        muteText: false
      }]);
    
    setTimeout(function() {
      redraw([{
        id: 1,
        x: 10,
        y: 30,
        text: "node 1",
        muteText: true
      }, {
        id: 2,
        x: 100,
        y: 50,
        text: "node 2",
        muteText: false
      }, {
        id: 3,
        x: 50,
        y: 70,
        text: "node 3",
        muteText: true
      }])
    }, 2000)
    
    setTimeout(function() {
      redraw([{
        id: 1,
        x: 10,
        y: 30,
        text: "node 1",
        muteText: true
      }, {
        id: 2,
        x: 100,
        y: 50,
        text: "node 2",
        muteText: false
      }, {
        id: 3,
        x: 50,
        y: 70,
        text: "node 3",
        muteText: false
      },{
        id: 4,
        x: 60,
        y: 90,
        text: "node 4",
        muteText: false
      }])
    }, 4000)

    function redraw(someData) {

      var node = svg
        .selectAll("g.node")
        .data(someData);

      var nodeE = node.enter()
        .append("g")
        .attr("class", "node")
        .attr("transform", d => "translate(" + d.x + "," + d.y + ")");

      nodeE.append("text")
        .text(d => d.text)
        .style("opacity", 0)
        .transition()
        .style("opacity", 1);

      node = nodeE.merge(node);

      node.exit()
        .style("opacity", "0");

      node.transition().duration(500)
        .attr("transform", d => "translate(" + d.x + "," + d.y + ")");

      node.filter(function(d) {
          return d.muteText
        })
        .select("text")
        .transition()
        .style("opacity", 0);    

    }
  </script>
</body>

</html>