Home > Software design >  D3 (v3.4) edge bundle chart and update button to load a new dataset
D3 (v3.4) edge bundle chart and update button to load a new dataset

Time:06-29

I have a D3 (v3.4) edge bundling chart and I want to update the chart with a new dataset by clicking an 'update' button. I'd like to see the chart update displaying data from the data2.json (and not data1.json). I currently have an update button and the start of an updateData() function that simply reads in the new dataset, though I'm not sure what parts of my code I need to include in this function to update via transition. I'm aware that certain parts of the chart need to be re-rendered and it isn't a case of simply loading in a new dataset. I was using this Update d3.js data with button press example as a basis though because it's a line chart the transition updates are a lot simpler and so less helpful to my edge bundle chart. My code below represents a minimal reproducible example..

HTML/JS

<!DOCTYPE html>
<html>

<head>

    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

    <script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'></script>

    <script src="https://d3js.org/d3.v4.js"></script>

    <style>

        .node {
            font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
            fill: rgb(0, 0, 0, .6);
          }
          
          .link {
            stroke: steelblue;
            fill: none;
            pointer-events: none;
          }   
    </style>

</head>

<body>

    <div ></div>

        <div  id="chart">

            <div id="my_dataviz"></div>
                <script>

                var diameter = 760,
                    radius = diameter / 2;
                    innerRadius = radius - 160;
                
                var cluster = d3.cluster()
                    .size([360, innerRadius]);
                
                var line = d3.radialLine()
                    .curve(d3.curveBundle.beta(0.85))
                    .radius(function(d) { return d.y; })
                    .angle(function(d) { return d.x / 180 * Math.PI; });
                
                var svg = d3.select("body").append("svg")
                    .attr("width", diameter)
                    .attr("height", diameter)
                    .append("g")
                    .attr("transform", "translate("   radius   ","   radius   ")");
                
                var link = svg.append("g").selectAll(".link"),
                    node = svg.append("g").selectAll(".node");
                
                d3.json("data1.json", function(error, classes) {
                    if (error) throw error;
                
                    var root = packageHierarchy(classes)
                        .sum(function(d) { return d.size; });
                
                  cluster(root);
                
                  link = link
                      .data(packageImports(root.leaves()))
                      .enter().append("path")
                      .each(function(d) { d.source = d[0], d.target = d[d.length - 1]; })
                      .attr("class", "link")
                      .attr("d", line);
                
                  node = node
                      .data(root.leaves())
                      .enter().append("text")
                      .attr("class", "node")
                      .attr("dy", "0.31em")
                      .attr("transform", function(d) { return "rotate("   (d.x - 90)   ")translate("   (d.y   8)   ",0)"   (d.x < 180 ? "" : "rotate(180)"); })
                      .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
                      .text(function(d) { return d.data.key; })
                      .on("mouseover", mouseovered)
                      .on("mouseout", mouseouted);
                });
                
                function mouseovered(d) {
                  node
                      .each(function(n) { n.target = n.source = false; });
                
                  link
                      .classed("link--target", function(l) { if (l.target === d) return l.source.source = true; })
                      .classed("link--source", function(l) { if (l.source === d) return l.target.target = true; })
                      .filter(function(l) { return l.target === d || l.source === d; })
                      .raise();
                  node
                      .classed("node--target", function(n) { return n.target; })
                      .classed("node--source", function(n) { return n.source; });
                }
                
                function mouseouted(d) {
                  link
                      .classed("link--target", false)
                      .classed("link--source", false);
                
                  node
                      .classed("node--target", false)
                      .classed("node--source", false);
                }
                
                // Lazily construct the package hierarchy from class names.
                function packageHierarchy(classes) {
                  var map = {};
                
                  function find(name, data) {
                    var node = map[name], i;
                    if (!node) {
                      node = map[name] = data || {name: name, children: []};
                      if (name.length) {
                        node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
                        node.parent.children.push(node);
                        node.key = name.substring(i   1);
                      }
                    }
                    return node;
                  }
                
                  classes.forEach(function(d) {
                    find(d.name, d);
                  });
                
                  return d3.hierarchy(map[""]);
                }
                
                // Return a list of imports for the given array of nodes.
                function packageImports(nodes) {
                  var map = {},
                      imports = [];
                
                  // Compute a map from name to node.
                  nodes.forEach(function(d) {
                    map[d.data.name] = d;
                  });
                
                  // For each import, construct a link from the source to target node.
                  nodes.forEach(function(d) {
                    if (d.data.imports) d.data.imports.forEach(function(i) {
                      imports.push(map[d.data.name].path(map[i]));
                    });
                  });
                
                  return imports;
                }

                // update data function
                function updateData() {

                  d3.json("data1.json", function(error, classes) {
                    if (error) throw error;
                
                    var root = packageHierarchy(classes)
                        .sum(function(d) { return d.size; });
                  
                    var svg = d3.select("body").transition();

                  });
                }
                </script>
                
            </div>

        </div>

</body>

  <div id="option">
    <input name="updateButton"
    type="button"
    value="Update"
    onclick="updateData()"
    />
  </div>

</html>

data1.json

[
    {
        "name": "flare.A.dataA",
        "imports": [
            "flare.B.dataB"

        ]
    },
    {
        "name": "flare.B.dataB",
        "imports": [
            "flare.A.dataA"
        ]
    }
]

data2.json

[
    {
        "name": "flare.A.newdataA",
        "imports": [
            "flare.B.newdataB"

        ]
    },
    {
        "name": "flare.B.newdataB",
        "imports": [
            "flare.A.newdataA"
        ]
    }
]

I can't find any examples of an edge bundle chart with an update button. Any help is appreciated!

CodePudding user response:

Try with this:

transition().duration(1000)
                .ease("elastic")
                .call(draw);

D3 transitions on enter/update/remove

Also

D3.js update with transition

CodePudding user response:

I tried editing your code and was able to achieve this. To update the data, we need to make use of enter, update and exit methods. In v3 I remember, when we enter the new data and this will create any missing links or nodes and using exit(), we can remove nodes, links which are not present. After enter, we append the new nodes,links and later update its attributes. We can actually use the same function to create and update which you can try. I dont know why the newDataB is not coming as node in below function. Something wrong with my sample data it seems. Your new sample data is working fine with below code.

var diameter = 760,
  radius = diameter / 2;
innerRadius = radius - 160;

var cluster = d3.cluster()
  .size([360, innerRadius]);

var line = d3.radialLine()
  .curve(d3.curveBundle.beta(0.85))
  .radius(function(d) {
    return d.y;
  })
  .angle(function(d) {
    return d.x / 180 * Math.PI;
  });

var svg = d3.select("body").append("svg")
  .attr("width", diameter)
  .attr("height", diameter)
  .append("g")
  .attr("transform", "translate("   radius   ","   radius   ")");

var link = svg.append("g").selectAll(".link"),
  node = svg.append("g").selectAll(".node");

{
  let classes = [{
      "name": "flare.A.dataA",
      "size": 1000,
      "imports": [
        "flare.B.dataB"

      ]
    },
    {
      "name": "flare.B.dataB",
      "size": 1200,
      "imports": [
        "flare.A.dataA"
      ]
    }
  ];

  var root = packageHierarchy(classes)
    .sum(function(d) {
      return d.size;
    });

  cluster(root);

  link = link
    .data(packageImports(root.leaves()))
    .enter().append("path")
    .each(function(d) {
      d.source = d[0], d.target = d[d.length - 1];
    })
    .attr("class", "link")
    .attr("d", line);

  node = node
    .data(root.leaves())
    .enter().append("text")
    .attr("class", "node")
    .attr("dy", "0.31em")
    .attr("transform", function(d) {
      return "rotate("   (d.x - 90)   ")translate("   (d.y   8)   ",0)"   (d.x < 180 ? "" : "rotate(180)");
    })
    .attr("text-anchor", function(d) {
      return d.x < 180 ? "start" : "end";
    })
    .text(function(d) {
      return d.data.key;
    })
    .on("mouseover", mouseovered)
    .on("mouseout", mouseouted);
};

function mouseovered(d) {
  node
    .each(function(n) {
      n.target = n.source = false;
    });

  link
    .classed("link--target", function(l) {
      if (l.target === d) return l.source.source = true;
    })
    .classed("link--source", function(l) {
      if (l.source === d) return l.target.target = true;
    })
    .filter(function(l) {
      return l.target === d || l.source === d;
    })
    .raise();
  node
    .classed("node--target", function(n) {
      return n.target;
    })
    .classed("node--source", function(n) {
      return n.source;
    });
}

function mouseouted(d) {
  link
    .classed("link--target", false)
    .classed("link--source", false);

  node
    .classed("node--target", false)
    .classed("node--source", false);
}

// Lazily construct the package hierarchy from class names.
function packageHierarchy(classes) {
  var map = {};

  function find(name, data) {
    var node = map[name],
      i;
    if (!node) {
      node = map[name] = data || {
        name: name,
        children: []
      };
      if (name.length) {
        node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
        node.parent.children.push(node);
        node.key = name.substring(i   1);
      }
    }
    return node;
  }

  classes.forEach(function(d) {
    find(d.name, d);
  });

  return d3.hierarchy(map[""]);
}

// Return a list of imports for the given array of nodes.
function packageImports(nodes) {
  var map = {},
    imports = [];

  // Compute a map from name to node.
  nodes.forEach(function(d) {
    map[d.data.name] = d;
  });

  // For each import, construct a link from the source to target node.
  nodes.forEach(function(d) {
    if (d.data.imports) d.data.imports.forEach(function(i) {
      imports.push(map[d.data.name].path(map[i]));
    });
  });

  return imports;
}

// update data function
function updateData() {


  let classes = [{
      "name": "flare.A.newdataA",
      "size": 1000,
      "imports": [
        "flare.B.newdataB"

      ]
    },
    {
      "name": "flare.B.newdataB",
      "size": 1400,
      "imports": [
        "flare.A.newdataA"
      ]
    },
    {
      "name": "flare.A.newdataC",
      "size": 1200,
      "imports": [
        "flare.A.newdataA"
      ]
    }
  ];

  var root = packageHierarchy(classes)
    .sum(function(d) {
      return d.size;
    });

  cluster(root);

  var nodes = root.leaves();
  var links = packageImports(root.leaves());

  let link = svg.selectAll('.link').data(links);
  link.enter().append('g').append("path").each(function(d) {
    d.source = d[0], d.target = d[d.length - 1];
  }); 
  link.attr("class", "link").transition()
  .duration(1000).attr("d", line);
  link.exit().remove();

  let node = svg.selectAll('.node').data(nodes);
  node.enter().append('g').append("text");
  node.attr("transform", function(d) {
      return "rotate("   (d.x - 90)   ")translate("   (d.y   8)   ",0)"   (d.x < 180 ? "" : "rotate(180)");
    })
    .attr("text-anchor", function(d) {
      return d.x < 180 ? "start" : "end";
    })
    .attr("class", "node")
    .attr("dy", ".31em")
    .text(function(d) {
      return d.data.key;
    });


  node.exit().remove();

}
<!DOCTYPE html>
<html>

<head>

  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

  <script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'></script>

  <script src="https://d3js.org/d3.v4.js"></script>

  <style>
    .node {
      font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
      fill: rgb(0, 0, 0, .6);
    }
    
    .link {
      stroke: steelblue;
      fill: none;
      pointer-events: none;
    }
  </style>

</head>

<body>

  <div ></div>

  <div  id="chart">

    <div id="my_dataviz"></div>
  </div>

  </div>

</body>


<div id="option">
  <input name="updateButton" type="button" value="Update" onclick="updateData()" />
</div>

</html>

  • Related