D3 - circle packing multiple data - javascript

I am a bit new to D3 and still have some problems with understanding it.
I am using this tutorial Zoomable Circle Packing:
However, I don't know how to load multiple data sets.
For example I need something like (you can see on jsfiddle) but when the button is pressed, a different .JSON file is loaded (the names in both files are the same, but values are different).
The solution might be "Thinking with Joins" by mbostock but I really dont know how to use it.
Any help would be appreciated.

You can use function to call loading of json file like this:
var callJson = function (json) {
d3.json(json, function(error, root) {
if (error) return console.error(error);
svg.selectAll("circle").remove();
svg.selectAll("text").remove();
var focus = root,
nodes = pack.nodes(root),
view;
var circle = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.style("fill", function(d) { return d.children ? color(d.depth) : null; })
.on("click", function(d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); });
var text = svg.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; })
.style("display", function(d) { return d.parent === root ? null : "none"; })
.text(function(d) { return d.name; });
var node = svg.selectAll("circle,text");
d3.select("body")
.style("background", color(-1))
.on("click", function() { zoom(root); });
zoomTo([root.x, root.y, root.r * 2 + margin]);
function zoom(d) {
var focus0 = focus; focus = d;
var transition = d3.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", function(d) {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
return function(t) { zoomTo(i(t)); };
});
transition.selectAll("text")
.filter(function(d) { return d.parent === focus || this.style.display === "inline"; })
.style("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; })
.each("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
.each("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
}
function zoomTo(v) {
var k = diameter / v[2]; view = v;
node.attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; });
circle.attr("r", function(d) { return d.r * k; });
}
});
};
... and then you call it with callJson("flare.json");
Here is working example with multiple json files - http://bl.ocks.org/chule/74e95deeadd353e42034

Related

d3js switching to version 4 : can't change node and node neighbors radius on click anymore

I want to switch some code from d3js version 3 to version 4.
The graph gave the possibility to change node and connected nodes opacity and radius on click. However, as node'circle are not defined the same way on version 4, radius are not to be changed the same way. Here is the changes performed:
var node = svg.selectAll('.node')
.data(nodes)
.enter().append('g')
.attr('class', 'node')
//.attr('r', 15)
//.style('fill', function(d) {
// return color(d.degree);
//})
.call(d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
)
.on('click', connectedNodes);
node.append('circle')
.attr('r', 15)
.style('fill', function(d) {
return color(d.degree);
});
and here is the function used to change node and its neighbors on click:
function connectedNodes() {
if (toggle == 0) {
var d = d3.select(this).node().__data__;
node.style("opacity", function(o) {
return neighboring(d, o) | neighboring(o, d) ? 1 : 0.3;
});
node.attr('r', function(o) {
return neighboring(d, o) | neighboring(o, d) ? 20 : 15;
});
link.style("opacity", function(o) {
return d.index == o.source.index | d.index == o.target.index ? 1 : 0.8;
});
link.style('stroke-width', function(o) {
return d.index == o.source.index | d.index == o.target.index ? 3 : 0.8;
});
toggle = 1;
}
}
the block node.attr('r', function(o) does not work anymore (as opposite as node.style('opacity, function(o)) as the circles are not defined the same way.
How can I still update node and connected nodes radius on click ? I have seen some examples on how to do this but none applied as I want not only clicked node to be bigger, but also connected ones, and I don't know how to retrieve circle property from node attributes.
here is the complete html (javascript embedded), and here is the graph.json which is used by the script. Both on the same folder, and python -m SimpleHTTPServer 8080 to serve these files.
Many thanks!
I have tried to hardcode a higher value, regardless of neighboring, still no change, value not taken into account.
node.attr('r', function(o) {
// return neighboring(d, o) || neighboring(o, d) ? 20 : 15;
return 25;
});
I wonder if you have ever been able to run the paste bin you show.
There are so many problems with {} and () that my browser refuses to run it.
you have nothing beneath the g nodes so you replace them with the circles. title works as a child of circle, text isn't.
what is an div with weight:800px?
update the cx and cy of the circles, transform also works but this is neater.
reset all the node r attributes for toggle==1
var width = 800, height = 600;
var color = d3.scaleOrdinal(d3.schemeCategory10);
var simulation = d3.forceSimulation()
.force('link', d3.forceLink().id(function (d) { return d.id;}).distance(100).strength(1))
.force('charge', d3.forceManyBody())
.force('center', d3.forceCenter(width/2, height/2));
var svg = d3.select('#canvas').select('svg');
if (svg.empty()) {
svg = d3.select('#canvas').append('svg')
.attr('width', width)
.attr('height', height);
}
d3.json('graph.json', function(error, graph) {
if (error) throw error;
var links = graph.links, nodes = graph.nodes;
var link = svg.selectAll('.link')
.data(graph.links)
.enter().append('line')
.attr('class', 'link');
var node = svg.selectAll('.node')
.data(nodes)
.enter().append('circle')
.attr('class', 'node')
.attr('r', 15)
.style('fill', function(d) { return color(d.degree); })
.call(d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended)
)
.on('click', connectedNodes);
simulation
.nodes(nodes)
.on('tick', ticked);
simulation.force('link').links(links);
function ticked() {
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("cx", function (d) {return d.x;})
.attr("cy", function (d) {return d.y;})
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
node.append('title')
.text(function(d) {
return "Node: " + d.id + "\n" + "Degree: " + d.degree + "\n" + "Katz: " + d.katz;
});
var toggle = 0;
var linkedByIndex = {};
for (var i = 0; i < graph.nodes.length; i++) {
linkedByIndex[i + "," + i] = 1;
}
graph.links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
function neighboring(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
function connectedNodes() {
if (toggle == 0) {
var d = d3.select(this).node().__data__;
node.style("opacity", function(o) {
return neighboring(d, o) || neighboring(o, d) ? 1 : 0.3;
})
.attr('r', function(o) {
return neighboring(d, o) || neighboring(o, d) ? 20 : 15;
});
link.style("opacity", function(o) {
return d.index == o.source.index || d.index == o.target.index ? 1 : 0.8;
})
.style('stroke-width', function(o) {
return d.index == o.source.index || d.index == o.target.index ? 3 : 0.8;
});
toggle = 1;
} else {
node.style('opacity', 1)
.attr('r', 15);
link.style('opacity', 1)
.style('stroke-width', 1);
toggle = 0;
}
}
}
);

D3.js: Text labels dissapear when I click on the second svg element

I am new to D3.js and trying to make a visualization in which I am facing a problem wherein, I have two Bubble Charts in my display as two separate SVG elements, as shown below:
SVG Elements
Now, the problem is that when I click on one of the SVG elements, the text labels from the second disappear and vice-versa, as shown:
On Clicking one of the charts
and:
When I click on the SVG as well, it disappears
The code for the above is as follows:
<script type="text/javascript">
var svg = d3.select("#svg1"),
margin = 20,
diameter = +svg.attr("width"),
g = svg.append("g").attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
var color = d3.scaleLinear()
.domain([-1, 5])
.range(["hsl(200,80%,80%)", "hsl(128,30%,90%)"])
.interpolate(d3.interpolateHcl);
var pack = d3.pack()
.size([diameter - margin, diameter - margin])
.padding(2);
d3.json("flare.json", function(error, root) {
if (error) throw error;
root = d3.hierarchy(root)
.sum(function(d) { return d.size; })
.sort(function(a, b) { return b.value - a.value; });
var focus = root,
nodes = pack(root).descendants(),
view;
var circle = g.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.style("fill", function(d,i) {
console.log(d.data.name);
return d.data.color ? d.data.color : "ff99bb"; })
.on("click", function(d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); });
var text = g.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; })
.style("display", function(d) { return d.parent === root ? "inline" : "none"; })
.style("font-size", function(d){ return d.parent === root ? "12px" : "24px";})
.text(function(d) { return d.data.name; });
var node = g.selectAll("circle,text");
svg
.style("background", "#ffffff ") // change color of the square
.on("click", function() { zoom(root); });
zoomTo([root.x, root.y, root.r * 2 + margin]);
function zoom(d) {
var focus0 = focus; focus = d;
var transition = d3.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", function(d) {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
return function(t) { zoomTo(i(t)); };
});
transition.selectAll("text")
.filter(function(d) { return d.parent === focus || this.style.display === "inline"; })
.style("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; })
.on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
.on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
}
function zoomTo(v) {
var k = diameter / v[2]; view = v;
node.attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; });
circle.attr("r", function(d) { return d.r * k; });
}
});
///////////////////////////////////////////////////////SVG2///////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
var svg2 = d3.select("#svg2"),
margin2 = 20,
diameter2 = +svg2.attr("width"),
g2 = svg2.append("g").attr("transform", "translate(" + diameter2 / 2 + "," + diameter2 / 2 + ")");
var color2 = d3.scaleLinear()
.domain([-1, 5])
.range(["hsl(200,80%,80%)", "hsl(128,30%,90%)"])
.interpolate(d3.interpolateHcl);
var pack2 = d3.pack()
.size([diameter2 - margin2, diameter2 - margin2])
.padding(2);
d3.json("flare2.json", function(error, root2) {
if (error) throw error;
root2 = d3.hierarchy(root2)
.sum(function(d) { return d.size; })
.sort(function(a, b) { return b.value - a.value; });
var focus2 = root2,
nodes2 = pack(root2).descendants(),
view;
var circle2 = g2.selectAll("circle")
.data(nodes2)
.enter().append("circle")
.attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.style("fill", function(d,i) {
console.log(d.data.name);
return d.data.color ? d.data.color : "#ddccff "; })
.on("click", function(d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); });
var text2 = g2.selectAll("text")
.data(nodes2)
.enter().append("text")
.attr("class", "label2")
.style("fill-opacity", function(d) { return d.parent === root2 ? 1 : 0; })
.style("display", function(d) { return d.parent === root2 ? "inline" : "none"; })
.style("font-size", function(d){ return d.parent === root2 ? "12px" : "24px";})
.text(function(d) { return d.data.name; });
var node2 = g2.selectAll("circle,text");
svg2
.style("background", "#ffffff ") // change color of the square
.on("click", function() { zoom(root2); });
zoomTo([root2.x, root2.y, root2.r * 2 + margin2]);
function zoom(d) {
var focus1 = focus; focus = d;
var transition2 = d3.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", function(d) {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin2]);
return function(t) { zoomTo(i(t)); };
});
transition2.selectAll("text")
.filter(function(d) { return d.parent === focus || this.style.display === "inline"; })
.style("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; })
.on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
.on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
}
function zoomTo(v) {
var k = diameter2 / v[2]; view = v;
node2.attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; });
circle2.attr("r", function(d) { return d.r * k; });
}
});
</script>
What mistake am I doing in this?
Can someone please help me how to make it correct? Thanks in advance!
dcluo is right that the issue is with the line noted, but the reason is that the following code
transition.selectAll("text")
.filter(function(d) { return d.parent === focus || this.style.display === "inline"; })
.style("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; })
.on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
.on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
and the corresponding code in the second svg isn't selective enough.
if you would change the
selectAll("text")
to
selectAll("text.label")
in the first svg zoom method and
selectAll("text.label2")
in the second svg zoom method
that would only change the opacity in only the nodes for the respective svg containers.
selectAll is just like any other javascript selection method like jQuery's $('input') or plain javascript document.getElementsByTagName("UL").
text is actually a tag name and there is no context passed when it runs to know that it should only run in the parent svg.
Take a look at https://bost.ocks.org/mike/selection/
I believe
.style("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; })
is responsible for this behavior.

Zoomable Circle Packing changes to JSON on click

This entire day I've tried to make this (https://bl.ocks.org/mbostock/7607535) work for my use case. In the page mentioned earlier the author saves the data as a JSON, but I want to allow the user to make changes to the JSON.
I am building out a tool that allows a user to input a country he or she is interested in into a form. I make an ajax request to get this form's information and then I pass it to Flask so that Flask can query my Cassandra database. Once Flask queries my Cassandra database it sends the information back to my AJAX request. Once I have the information in AJAX I try to update my current dictionary so that another bubble for the country the user picked shows up, but this does not work. I am able to make a correct chart for the first entry, but once a user enters a second entry my chart gets messed up. In order to see what is happening please look at my website here: http://ec2-52-27-239-181.us-west-2.compute.amazonaws.com/overview
Here is my current code:
function add_simple(json,dict_country){ //updates the dictionary
function event_children(event_dict){ //returns a list with event children
var time_lst = []
var event_names = Object.keys(event_dict)
for (var i = 0; i<event_names.length; i++){
var event_type = {}
event_type["name"] = event_names[i]
event_type["children"] = []
event_info = event_dict[event_names[i]]
for (var key in event_info){
key_obj = {}
key_obj["name"] = key + " => " + event_info[key]
key_obj["size"] = 2000
event_type["children"].push(key_obj)
}
time_lst.push(event_type)
}
return time_lst
}
var country = dict_country["Country"]
var timeframe = dict_country["Timeframe"]
var date = dict_country["Date"]
var total = dict_country["Total"]
var info = dict_country["Overview"]
json["children"].push({"name":country,"children":event_children(info)})
return json
}
var poot = {"name":"flare","children":[]}
$( "#target" ).click(function() {
var data = {};
data.country = $("#country").val();
data.timeframe = $("#timeframe").val();
data.eventdate = $("#eventdate").val();
$.ajax({
type : "POST",
url : "/overview",
data: JSON.stringify(data, null, '\t'),
contentType: 'application/json;charset=UTF-8',
success: function(result) {
poot = add_simple(poot,result["result"])
console.log(poot)
createCircles(poot);
}
});
})
var svg = d3.select("svg"),
margin = 20,
diameter = +svg.attr("width"),
g = svg.append("g").attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
var color = d3.scaleLinear()
.domain([-1, 5])
.range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl);
var pack = d3.pack()
.size([diameter - margin, diameter - margin])
.padding(2);
function createCircles(root){
root = d3.hierarchy(root)
.sum(function(d) { return d.size; })
.sort(function(a, b) { return b.value - a.value; });
var focus = root,
nodes = pack(root).descendants(),
view;
var circle = g.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.style("fill", function(d) { return d.children ? color(d.depth) : null; })
.on("click", function(d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); });
var text = g.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; })
.style("display", function(d) { return d.parent === root ? "inline" : "none"; })
.text(function(d) { return d.data.name; });
var node = g.selectAll("circle,text");
svg
.style("background", color(-1))
.on("click", function() { zoom(root); });
zoomTo([root.x, root.y, root.r * 2 + margin]);
function zoom(d) {
var focus0 = focus; focus = d;
var transition = d3.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", function(d) {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
return function(t) { zoomTo(i(t)); };
});
transition.selectAll("text")
.filter(function(d) { return d.parent === focus || this.style.display === "inline"; })
.style("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; })
.on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
.on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
}
function zoomTo(v) {
var k = diameter / v[2]; view = v;
node.attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; });
circle.attr("r", function(d) { return d.r * k; });
}
}
//});
I greatly appreciated any help.

Circle Pack Layout D3 - remove add nodes on new data

I have a situation where I want users to browse 3 different data sets using circle pack layout. I am using Bostock's Zoomable circle packing.
I have added 3 options additionally which loads data and creates new nodes. Here chartid is passed from these elements.
function changeDataSet(chartid)
{
console.log(chartid);
//console.log(nodes);
//add new chart data depending on the selected option
if(chartid === "plevels")
{
root = JSON.parse(newKmap_slevels);
focus = root;
nodes = pack.nodes(root);
}
else if (chartid === "pduration")
{
root = JSON.parse(newKmap_sduration);
focus = root;
nodes = pack.nodes(root);
}
else
{
root = JSON.parse(newKmap_stype);
focus = root;
nodes = pack.nodes(root);
}
refresh();
}
Then I have the refresh function, but I am not understnading how to remove existting nodes, and add new ones based on the new data set. Showing some transition animations would be nice also.
Currently I am trying to remove and recreate the initial elements, but the chart goes blank when I do that.
var refresh = function() {
//var nodes = pack.nodes(root);
var duration = 10;
console.log(nodes);
d3.select("#conf_knowledge_map").selectAll("g")
.remove();
svg
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
circle = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.style("fill", function(d) { return d.children ? color(d.depth) : null; })
.on("click", function(d) {
if (focus !== d) zoom(d), d3.event.stopPropagation();
});
text = svg.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; })
.style("display", function(d) { return d.parent === root ? "inline" : "none"; })
.text(function(d) {
//console.log(d);
if( d.size )
{
return d.name + ":" + d.size;
}
else
return d.name;
});
}
So my question is how can I remove and then create new nodes on click?
UPDATE
I was able to remove all nodes and add the new nodes based on the new data, but now on click to zoom the layout is all messed up. The transform function is not applied to the new nodes somehow.
var refresh = function() {
svg.selectAll(".node").remove();
svg.selectAll(".label").remove();
var nodes = pack.nodes(root);
focus = root;
circle = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.attr("r", function(d) { return d.r;})
.attr("cx", function(d) {
console.log(d);
// if(d.depth === 0)
// return 0;
// else
// return d.x - (diameter/2);
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
// .attr("transform", "translate(" + "x" + "," + "y" + ")")
.style("fill", function(d) { return d.children ? color(d.depth) : null; })
.on("click", function(d) {
// d.x = d.x - (diameter/2);
// d.y = d.y - (diameter/2);
if (focus !== d) zoom(d), d3.event.stopPropagation();
});
text = svg.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.attr("x", function(d) {return d.x - (diameter/2);})
.attr("y", function(d) {return d.y - (diameter/2);})
.style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; })
.style("display", function(d) { return d.parent === root ? "inline" : "none"; })
// .attr("transform", "translate(" + "x" + "," + "y" + ")")
.text(function(d) {
//console.log(d);
if( d.size )
{
return d.name + ":" + d.size;
}
else
return d.name;
});
}
How can I transform the new nodes to be in place and for the zoom to work properly?
UPDATE 2
Now the transform is working properly after attaching 'g' element to the circles, and showing all the nodes and text correctly. The only problem now is that the zoom does not work when I click on the circles!!
circle = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.attr("r", function(d) { return d.r;})
.attr("cx", function(d) {
if(d.depth === 0)
return 0;
else
return d.x - (diameter/2);
})
.attr("cy", function(d) {
if(d.depth === 0)
return 0;
else
return d.y - (diameter/2);
})
.style("fill", function(d) { return d.children ? color(d.depth) : null;})
.on("click", function(d) {
if (focus !== d) zoom(d); d3.event.stopPropagation();
})
.append("g")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
;
How to make the zoom work??
It so happens that I was making the mistake of simultaneously creating three independent circle pack layouts, with mixed statements one after the other. This was problematic as when you have to select svg sections, different elements were all getting selected and wrongly attached with different events.
So I decided to separate the three implementations and create the three layouts one after the other, only after each one finished. This way I kept the section selection and attaching events etc, separate and then load them as and when required.

Zoomable Circle Packing with Automatic Text Sizing in D3.js

I'm trying to merge two of Mike's examples: Zoomable Circle Packing + Automatic Text Sizing.
It works when initially displayed at the top-level. However, if you zoom in to the next level, the fonts are not sized correctly.
I'm not sure if I need to modify the transform, or modify the part which calculates the font size.
Here's my codepen: http://codepen.io/anon/pen/GJWqrL
var circleFill = function(d) {
if (d['color']) {
return d.color;
} else {
return d.children ? color(d.depth) : '#FFF';
}
}
var calculateTextFontSize = function(d) {
return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 11) + "px";
}
var margin = 20,
diameter = 960;
var color = d3.scale.linear()
.domain([-1, 18])
.range(["hsl(0,0%,100%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl);
var pack = d3.layout.pack()
.padding(2)
.size([diameter - margin, diameter - margin])
.value(function(d) {
return d.size;
})
var svg = d3.select("body").append("svg")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight)
.append("g")
.attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
var focus = root,
nodes = pack.nodes(root),
view;
var circle = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function(d) {
return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root";
})
.style("fill", circleFill)
.on("click", function(d) {
if (focus !== d) zoom(d), d3.event.stopPropagation();
});
circle.append("svg:title")
.text(function(d) {
return d.name;
})
var text = svg.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function(d) {
return d.parent === root ? 1 : 0;
})
.style("display", function(d) {
return d.parent === root ? null : "none";
})
.text(function(d) {
return d.name;
})
.style("font-size", calculateTextFontSize)
.attr("dy", ".35em");
var node = svg.selectAll("circle,text");
d3.select("body")
.style("background", color(-1))
.on("click", function() {
zoom(root);
});
zoomTo([root.x, root.y, root.r * 2 + margin]);
function zoom(d) {
var focus0 = focus;
focus = d;
var transition = d3.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", function(d) {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
return function(t) {
zoomTo(i(t));
};
});
transition.selectAll("text")
.filter(function(d) {
return d.parent === focus || this.style.display === "inline";
})
.style("fill-opacity", function(d) {
return d.parent === focus ? 1 : 0;
})
.each("start", function(d) {
if (d.parent === focus) this.style.display = "inline";
})
.each("end", function(d) {
if (d.parent !== focus) this.style.display = "none";
});
}
function zoomTo(v) {
var k = diameter / v[2];
view = v;
node.attr("transform", function(d) {
return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")";
});
circle.attr("r", function(d) {
return d.r * k;
});
}
d3.select(self.frameElement).style("height", diameter + "px");
Clicking the largest sub-circle in the "vis" circle illustrates the problem.
https://dl.dropboxusercontent.com/u/3040414/vis-circle.png
First give an id to the circle, here I am giving text name as the circle ID so that i can link the text and its circle via text name.
var circle = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function(d) {
return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root";
})
.style("fill", circleFill)
.attr("r", function(d) {
return d.r;
})
.attr("id", function(d) {
return d.name;//setting text name as the ID
})
.on("click", function(d) {
if (focus !== d) zoom(d), d3.event.stopPropagation();
});
On transition complete of zoom(d) function(i.e when you click on a circle and it zooms) add a timeout function which will recalculate the text font size based on the zoom.
setTimeout(function() {
d3.selectAll("text").filter(function(d) {
return d.parent === focus || this.style.display === "inline";
}).style("font-size", calculateTextFontSize);//calculate the font
}, 500)
Your calculateTextFontSize function will look like this(I am using the real DOM radius to calculate the font size):
var calculateTextFontSize = function(d) {
var id = d3.select(this).text();
var radius = 0;
if (d.fontsize){
//if fontsize is already calculated use that.
return d.fontsize;
}
if (!d.computed ) {
//if computed not present get & store the getComputedTextLength() of the text field
d.computed = this.getComputedTextLength();
if(d.computed != 0){
//if computed is not 0 then get the visual radius of DOM
var r = d3.selectAll("#" + id).attr("r");
//if radius present in DOM use that
if (r) {
radius = r;
}
//calculate the font size and store it in object for future
d.fontsize = (2 * radius - 8) / d.computed * 24 + "px";
return d.fontsize;
}
}
}
Working code here
I also had same problem as you and I tried this one and it works for me.
D3.js Auto font-sizing based on nodes individual radius/diameter

Categories