Zoomable Circle Packing changes to JSON on click - javascript

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.

Related

Modify an existing node in a D3 Force-Directed Graph without jumpy transitions

I have a continuous stream of processed data flowing in from the database to the client-side where I am rendering a force-directed graph using D3.
After a lot of tries, the graph seems to be updating. Basically, the circle radius updates from the new data for now.
However, every time the simulation.nodes(nodes_data) receives nodes_data, the entire graph re-initializes and ends up being a jumpy transition.
I have tried changing different force.alphaTarget values.
This is the function I use to initialize the simulation.
function simulation_routine(simulation, nodes_data, links_data, width, height){
simulation.nodes(nodes_data);
var link_force = d3.forceLink(links_data)
.id(function(d) {
return d.event_id;
})
.strength(function(d){
prob_value = d.prob_value;
return 2*prob_value;
})
.distance(100);
var charge_force = d3.forceManyBody()
.strength(-100);
var center_force = d3.forceCenter(width / 2, height / 2);
simulation
.force("charge_force", charge_force)
.force("center_force", center_force)
.force("links",link_force)
;
simulation.alphaTarget(0.3).restart();
console.log("Restarted Simulation");
return simulation;
}
This is the function I use to update the simulation with new nodes_data.
function simulation_update(simulation, nodes_data, links_data, node, link){
simulation.nodes(nodes_data);
var link_force = d3.forceLink(links_data)
.id(function(d) {
return d.event_id;
})
.strength(function(d){
prob_value = d.prob_value;
return 2*prob_value;
})
.distance(100);
simulation.force("links", link_force);
simulation.alphaTarget(0.3);
return simulation;
}
This is the node updating function
function nodes_update(simulation, nodes_data, links_data){
var svg = d3.select("svg");
var g = svg.selectAll(".everything");
var node = g.selectAll(".nodes").selectAll('g');
var link = g.selectAll(".links").selectAll('line');
nodes_routine(node, link, nodes_data, links_data, simulation);
}
function nodes_routine(node, link, nodes_data, links_data, simulation){
var t = d3.transition().duration(750);
node = node.data(nodes_data, function(d){return d.event_id;});
node.exit().remove();
var newNodes = node.enter().append("g");
node = newNodes
.attr('id',function(d){
return "node_id_"+d.event_id.toString();
})
.merge(node)
;
newNodes.append("circle")
.attr("r", function(d){
return d.event_radius-0.75;
})
.style("fill", function(d, i) {
return "url(#grad" + i + ")";
})
.on('mouseover.fade', fade(0.1))
.on('mouseover', function(d){
mouseover_event(d.event_name);
})
.on('mouseout.fade', fade(1))
;
node.select("circle")
.attr("r", function(d) {
return d.event_radius;
})
.style("fill", function(d, i) {
return "url(#grad" + i + ")";
});
newNodes.append("text")
.text(function(d) {
return d.event_name.toUpperCase();
})
.attr('dy', "0.35em")
.attr("dx", function(d) {
return d.event_radius+5||min_base_radius+5;}
)
.attr('fill','maroon')
;
newNodes.append("title")
.text(function(d) { return d.event_name; });
// Find connected nodes
const linkedByIndex = {};
links_data.forEach(d => {
linkedByIndex[`${d.source.index},${d.target.index}`] = 1;
});
//add drag capabilities
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
drag_handler(node);
function fade(opacity) {
return d => {
node.style('stroke-opacity', function (o) {
const thisOpacity = isConnected(d, o) ? 1 : opacity;
this.setAttribute('fill-opacity', thisOpacity);
return thisOpacity;
});
link.style('stroke-opacity', o => (o.source === d || o.target === d ? 1 : opacity));
link.attr("marker-end", function(o) {
return opacity === 1 || o.source === d || o.target === d
? 'url(#end-arrow)' : 'url(#end-arrow-fade)';
});
};
}
function isConnected(a, b) {
return linkedByIndex[`${a.index},${b.index}`] || linkedByIndex[`${b.index},${a.index}`] || a.index === b.index;
}
//Drag functions
//d is the node
function drag_start(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
//make sure you can't drag the circle outside the box
function drag_drag(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function drag_end(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
return node;
}
This is the function used to update the links
function links_routine(link, links_data){
link = link.data(links_data);
link.exit().remove();
var newLinks = link.enter().append("line");
link = newLinks
.attr("stroke-width", 2)
.attr("stroke-dasharray", function(d){
if(d.exists){
return 0;
}else{
return 5;
}
})
.style("stroke", linkColour)
.attr("marker-end", "url(#end-arrow)")
.merge(link);
function linkColour(d){
return "black";
}
return link;
}
function links_update(links_data){
var svg = d3.select("svg");
var g = svg.selectAll(".everything");
var link = g.selectAll(".links").selectAll('line');
links_routine(link, links_data);
}
I don't receive any error messages. It's just a jumpy graph transition. I want the graph to retain its original configuration and update the node radii at the existing position. I want to have a smooth experience.

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.

D3 exit(), remove() not working when the element is removed and added

I have a histogram that dynamically updates based on the input (country on map is clicked). The each bar has a mouseover info display.
While all the bars are update correctly at least visually, the mouseover menu will not appear on bars that were removed and added.
Here is the link to the project. For example, when I load histogram for Sweden after loading India, the mouseover display will not work correctly.
http://jbk1109.github.io/tennisMapWithPlayersListOnHover.html
//This is the function called when a country on the map is clicked
function drawBarChart(d){
selectCountryName = event['path'][0]['__data__']['name']
selectCountry = d.id
countryArray = d3.values(tennis).filter(function(d){
return d.COUNTRY_ID == selectCountry
})
/*
1. Select existing bars
2. Need the data array of existing bars.
3. remove the data.
4. remove the bar (the svg element)
*/
var addRect = d3.select('g.nations').selectAll('rect')
.data(countryArray)
console.log(addRect.exit().remove())
console.log(addRect.enter())
addRect.enter()
.append('rect')
addRect.transition().duration(650)
.attr({
x: function(d,i){
return(xScale(d.DATE))
// return (rectWidth(d.DATE))
},
y: function(d){
return (rectHeight + 50) - heightScale(d.COUNT)
},
width: function(d){
return (width-150)/yearArray.length
// return rectWidth.rangeBand()
},
height: function(d){
return heightScale(d.COUNT)
},
fill: 'steelblue'
}).attr('class','countryRect')
var y = d3.select('g.nations').selectAll('text.count')
.data(countryArray)
y.exit().remove()
y.enter()
.append('text')
y.transition().duration(650)
.attr('class','count')
.attr('x',function(d,i){
return(xScale(d.DATE) + ((width-150)/yearArray.length)/2)
})
.attr('y', function(d){
return (rectHeight + 50) - heightScale(d.COUNT) + 11
})
.text(function(d){
return d.COUNT
})
.attr('text-anchor','middle')
}
//This is where to histogram is create initially upon load.
var padding_right = 8
var nations = svg.append('g').attr("class","nations").attr('transform','translate('+ 30 +',10)')
var histSelection = nations.selectAll('rect.countryRect')
.data(function(){
return d3.values(tennis).filter(function(d){
return d.COUNTRY_ID == 840;
})
}).enter()
histSelection.append('rect')
.attr({
class: function(d){return "countryRect"},
x: function(d,i){
return(xScale(d.DATE))
// return (rectWidth(d.DATE))
},
y: function(d){
return (rectHeight + 50) - heightScale(d.COUNT)
},
width: function(d){
return (width-150)/yearArray.length
// return rectWidth.rangeBand()
},
height: function(d){
return heightScale(d.COUNT)},
fill: 'steelblue'
})
// Add player count to at the top of each bar
var countLabel = d3.select('g.nations').selectAll('text.count')
.data(function(){
return d3.values(tennis).filter(function(d){
return d.COUNTRY_ID == 840;
})
})
countLabel.enter()
.append('text')
.attr('class','count')
.attr('x',function(d,i){
return(xScale(d.DATE) + ((width-150)/yearArray.length)/2)
})
.attr('y', function(d){
return (rectHeight + 50) -heightScale(d.COUNT) +11
})
.text(function(d){
return d.COUNT
})
.attr('text-anchor','middle')
var x_axis = nations.append('g').attr("class","xAxis").attr("transform", "translate(0," + (rectHeight+50) + ")").call(xAxis)
**Add mouseover event
var selectRect = d3.select('g.nations').selectAll('.countryRect')
selectRect.on('mouseover',function(d){
console.log(d)
d3.select('.divContainer').style('left',this.x.baseVal.value).style('left',this.x.baseVal.value + 'px').style('display','block')
divContainer.style('top',this.y.baseVal.value).style('top',(this.y.baseVal.value+90) +'px')
var playerList = d.players;
playerList.sort(function(a,b){ return a.RANKING - b.RANKING})
var table = divContainer.append('table')
var headerRow = table.append('tr')
headerRow.append('th').html("Ranking").attr("class",'ranking')
headerRow.append('th').html("Name").attr("class",'name')
var players = table.append('tbody').selectAll('tr').data(playerList).enter()
var eachRow = players.append('tr')
var eachCell1 = eachRow.append('td').attr('class','rankingCell').html(function(d){return d.RANKING})
var eachCell2 = eachRow.append('td').attr('class','nameCell').html(function(d){return d.PLAYER_NAME})
// var addPlayers = players.append('text').text(function(d){
// return d.PLAYER_NAME
})
.on('mouseout',function(d){
d3.select('.divContainer').style('display','none')
d3.select('.divContainer').select('table').remove()
})

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

D3 - circle packing multiple data

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

Categories