Node size proportional to the number of children in D3

I created this network click-expand-collapse - http://jsfiddle.net/5Lv8gkqv/

var width = 960, height = 500, root = { "name": "Chocolate", "tag":"class", "children": [ { "name": "Wafer", "tag":"subclass", "children": [ { "name": "Nestle", "tag":"company", "children": [ {"name": "KitKat", "tag":"product"} ] } ] }, { "name": "White", "tag":"subclass", "children": [ { "name": "Nestle", "tag":"company", "children": [ {"name": "Milkybar", "tag":"product"} ] } ] }, { "name": "Caramel", "tag":"subclass", "children": [ { "name": "Nestle", "tag":"company", "children": [ {"name": "BarOne", "tag":"product"} ] } ] }, { "name": "Milk", "tag":"subclass", "children": [ { "name": "Nestle", "tag":"company", "children": [ {"name": "Nestle Milk", "tag":"product"} ] }, { "name": "Cadbury", "tag":"company", "children": [ {"name": "Dairy Milk", "tag":"product"} ] } ] } ] }; var force = d3.layout.force() .linkDistance(150) .charge(-120) .gravity(.05) .size([width, height]) .on("tick", tick); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); var link = svg.selectAll(".link"), node = svg.selectAll(".node"); flatten(root); //to set ids setParents(root, null); collapseAll(root); root.children = root._children; root._children = null; update(); function update() { var nodes = flatten(root), links = d3.layout.tree().links(nodes); // Restart the force layout. force .nodes(nodes) .links(links) .start(); // Update links. link = link.data(links, function(d) { return d.target.id; }); link.exit().remove(); link.enter().insert("line", ".node") .attr("class", "link"); // Update nodes. node = node.data(nodes, function(d) { return d.id; }); node.exit().remove(); var nodeEnter = node.enter().append("g") .attr("class", "node") .on("click", click) .call(force.drag); nodeEnter.append("circle") .attr("r", function(d) { return Math.sqrt(d.size) / 10 || 4.5; }); nodeEnter.append("text") .attr("dy", ".35em") .text(function(d) { return d.name; }); node.select("circle") .style("fill", color); } function tick() { link.attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); node.attr("transform", function(d) { return "translate(" + dx + "," + dy + ")"; }); } function color(d) { return d._children ? "#3182bd" // collapsed package : d.children ? "#c6dbef" // expanded package : "#fd8d3c"; // leaf node } // Toggle children on click. function click(d) { if (d3.event.defaultPrevented) return; // ignore drag if (d.children) { collapseAll(d); } else { if (d._parent){ d._parent.children.forEach(function(e){ if (e != d){ collapseAll(e); } }); } d.children = d._children; d._children = null; } update(); } function collapseAll(d){ if (d.children){ d.children.forEach(collapseAll); d._children = d.children; d.children = null; } else if (d._childred){ d._children.forEach(collapseAll); } } // Returns a list of all nodes under the root. function flatten(root) { var nodes = [], i = 0; function recurse(node) { if (node.children) node.children.forEach(recurse); if (!node.id) node.id = ++i; nodes.push(node); } recurse(root); return nodes; } function setParents(d, p){ d._parent = p; if (d.children) { d.children.forEach(function(e){ setParents(e,d);}); } else if (d._children) { d._children.forEach(function(e){ setParents(e,d);}); } } 

Now the thing is, I was wondering if it is possible to have a node-size proportional to the number of children. Thus, the parent node will be the largest circle, and the node sheet will be the smallest, and the intermediate size of the node will depend on the number of children that each one has.

+5
source share
3 answers

You can use d3.scale.linear to calculate the radius of nodes proportional to the number of children. d3.scale also helps to find the radius between the range. Here is the updated fiddle

  var minRadius = 10; var maxRadius = 15; var scale = d3.scale.linear().range([minRadius,maxRadius]); nodeEnter.append("circle") .attr("r", function(d) { if(d.children) return scale(d.children.length); else if(d._children) return scale(d._children.length); else return minRadius; }); 
+7
source

Update : Now children that are not displayed are taken into account using the idea from @Gilsha's answer.


Of course, you can set each <circle> radius proportional to the number of children they have:

 node.select("circle") .attr("r", function(d){ var numKids = 0; if (d.children) numKids += d.children.length; if (d._children) numKids += d._children.length; return 10 * (numKids + 1); }) 

where r is some radius. Using r=10 and switching to the "wafer" node, you get the following: enter image description here

+2
source

For a complete solution, you will actually want to use a recursive function to first compute all the children (and not just the first-level children) of each node. For instance:

 var bubble_up_total_children = function(node) { var child, _i, _len, _ref; if (node.children && node.children.length > 0) { _ref = node.children; for (_i = 0, _len = _ref.length; _i < _len; _i++) { child = _ref[_i]; bubble_up_total_children(child); } node.total_children = node.children.length + node.children.reduce(function(a, b) { return a.total_children + b.total_children; }); } else { node.total_children = 0; } }; bubble_up_total_children(root); 

Now you can use d3.scale, as already described in @Gilsha's answer, to calculate the size based on the new total_children attribute for each node.

+2
source

Source: https://habr.com/ru/post/1206717/


All Articles