first I have made this Web API from where I am getting tree structure object.
private TreeService _studentServices = new TreeService();
private static List<TreeNode> FillRecursive(ICollection<SalaryDetail> flatObjects, int? Refid = null)
{
return flatObjects.Where(x => x.Refid.Equals(Refid)).Select(item => new TreeNode
{
Name = item.Name,
Id = item.Id,
Salary = item.Salary,
Refid = item.Refid,
Children = FillRecursive(flatObjects, item.Id)
}).ToList();
}
// GET api/values
public List<TreeNode> Get()
{
ICollection<SalaryDetail> salarydetails = _studentServices.GetAllSalaryDetails();
var tree = FillRecursive(salarydetails, null);
return tree;
}
then I called This API in D3 as given below to show the data as a D3.js circle packing graph.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/d3js/3.5.16/d3.js"></script>
<script>
var diameter = 700,
format = d3.format(",d");
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.value(function (d) { return d.salary; });
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(2,2)");
d3.json("http://localhost:56935/api/values", function (error, root) {
var node = svg.datum(root).selectAll(".node")
.data(pack.nodes)
.enter().append("g")
.attr("class", function (d) { return d.children ? "node" : "leaf node"; })
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("title")
.text(function (d) { return d.name + (d.children ? "" : ": " + format(d.salary)); });
node.append("circle")
.attr("r", function (d) { return d.r; });
node.filter(function (d) { return !d.children; }).append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function (d) { return d.name.substring(0, d.r / 3); })
.style("font-size", 20);
});
d3.select(self.frameElement).style("height", diameter + "px");
</script>
</head>
<body>
</body>
Although I am getting data through D3.json() function but not my circle packing graph, Not even getting single error. Please help me out to make these graph. where am I lacking.
The structure of the json returning by Web Api is given below
[{"Id":1,"Name":"James","Salary":null,"Refid":null,"Children":[{"Id":2,"Name":"David","Salary":null,"Refid":1,"Children":[{"Id":3,"Name":"Richard","Salary":null,"Refid":2,"Children":[{"Id":4,"Name":"John","Salary":1000,"Refid":3,"Children":[]},{"Id":5,"Name":"Robert","Salary":4000,"Refid":3,"Children":[]},{"Id":6,"Name":"Paul","Salary":6000,"Refid":3,"Children":[]}]},{"Id":7,"Name":"Kevin","Salary":null,"Refid":2,"Children":[{"Id":8,"Name":"Jason","Salary":5000,"Refid":7,"Children":[]},{"Id":9,"Name":"Mark","Salary":null,"Refid":7,"Children":[{"Id":10,"Name":"Thomas","Salary":1000,"Refid":9,"Children":[]},{"Id":11,"Name":"Donald","Salary":1000,"Refid":9,"Children":[]}]}]}]}]}]
1. Instead of returning a List at your endpoint, return an object (the first element from your current returning list). From D3 docs for pack layout:
the input argument to the layout is the root node of the hierarchy
2. Either rename the Children -> children property name (by default D3 expects hierarchical data to be under children key), or define a different key for your data like this:
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.value(function (d) { return d.Salary; }) // Note that according to your data structure, your `Salary` should start with a capital letter.
.children(function (d) { return d.Children; }); // Define how to obtain children data.
Related
I want to create a Treemap with d3 but the enitities I want to show don't have a hierarchy. Is it still possible to create a Treemap?
My data includes crimes in Germany.
The I just wanted to show in the beginning all the crimes (Treemap not zoomed). then if I click on one of the boxes it will show me how many of them are male or female.
Sounds so easy but I really don't get it with my example.
I have tried so many examples but I dont get it because of the hierarchy.
First screenshot
Seccond Screenshot
You can arrange tabular data in a hierarchy using d3.nest(). This function groups the data under multiple key fields, similar to a GROUP BY statement in SQL - more detail can be found in the nest documentation. For an introduction to using d3.nest(), see this guide.
D3's treemap requires data to be arranged in a hierarchy with a root node, so you should nest all table entries under a single 'root' value as well as any fields that you wish to use to subdivide the treemap. The following code will arrange your data into a hierarchy that splits it by Sexus only:
var nested_data = d3.nest()
// Create a root node by nesting all data under the single value "root":
.key(function () { return "root"; })
// Nest by other fields of interest:
.key(function (d) { return d.Sexus; })
.entries(data);
For example, the following code shows a single rectangle in the treemap for all crimes and displays the number of crimes by Sexus when the user clicks on the rectangle:
<script src="js/d3.v4.js"></script>
<script>
var margin = { top: 40, right: 10, bottom: 10, left: 10 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var color = d3.scaleOrdinal().range(d3.schemeCategory20c);
var treemap = d3.treemap().size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", (width + margin.left + margin.right) + "px")
.attr("height", (height + margin.top + margin.bottom) + "px");
d3.csv("data/GermanyCrimeData.csv", function (error, data) {
if (error) throw error;
var nested_data = d3.nest()
// Create a root node by nesting all data under the single value "root":
.key(function () { return "root"; })
// Nest by other fields of interest:
.key(function (d) { return d.Sexus; })
.entries(data);
var root = d3.hierarchy(nested_data[0], function (d) { return d.values; })
.sum(function (d) { return d.Tatverdaechtige_Insgesamt_deutsch; });
var tree = treemap(root);
var nodes = svg.selectAll(".node")
.data([tree]) // Bind an array that contains the root node only.
.enter().append("g")
.attr("class", "node")
.attr('transform', function (d) {
return 'translate(' + [d.x0, d.y0] + ')'
})
.on("click", function () {
d3.select(this).select("text").style("visibility", "visible");
//See https://stackoverflow.com/questions/44074031/d3-show-hide-text-of-only-clicked-node
});
nodes.append("rect")
.attr("width", function (d) { return Math.max(0, d.x1 - d.x0 - 1) + "px"; })
.attr("height", function (d) { return Math.max(0, d.y1 - d.y0 - 1) + "px"; })
.attr("fill", function (d) { return color(d.data.key); });
nodes.append("text")
.attr("dx", 4)
.attr("dy", 14)
.style("visibility", "hidden") //Initially the text is not visible
.text(function (d) {
// Create an array of Tatverdaechtige_Insgesamt_deutsch counts for each Sexus:
var array = d.children.map(function (elt) { // Here, map is a native Javascript function, not a D3 function
// Return "Sexus: Tatverdaechtige_Insgesamt_deutsch":
return elt.data.key + ": " + elt.value;
// Note that d.data.key and d.value were created by d3.nest()
});
// Return a string representation of the array:
return d.data.key + " - " + array.join(", ");
});
});
</script>
To subdivide the treemap by a given field, the data must be nested by that field and tree.children must be bound to the DOM instead of [tree]:
//...
d3.csv("data/GermanyCrimeData.csv", function (error, data) {
if (error) throw error;
var nested_data = d3.nest()
.key(function () { return "root"; })
.key(function (d) { return d.Straftat; })
.key(function (d) { return d.Sexus; })
.entries(data);
var root = d3.hierarchy(nested_data[0], function (d) { return d.values; })
.sum(function (d) { return d.Tatverdaechtige_Insgesamt_deutsch; });
var tree = treemap(root);
var nodes = svg.selectAll(".node")
.data(tree.children) // Bind the children of the tree's root node
.enter().append("g")
.attr("class", "node")
.attr('transform', function (d) {
return 'translate(' + [d.x0, d.y0] + ')'
})
.on("click", function () {
d3.select(this).select("text").style("visibility", "visible");
//See https://stackoverflow.com/questions/44074031/d3-show-hide-text-of-only-clicked-node
});
//...
A simple way to make the treemap zoomable is to place the code that draws the treemap in a function, then use on("click", ...) to re-trigger the function whenever a rectangle is clicked, zooming in on the chosen node:
//...
d3.csv("data/GermanyCrimeData.csv", function (error, data) {
if (error) throw error;
var nested_data = d3.nest()
.key(function () { return "root"; })
.key(function (d) { return d.Stadt_Landkreis; })
.key(function (d) { return d.Straftat; })
.key(function (d) { return d.Sexus; })
.entries(data);
function drawTreemap(root) {
var hierarchical_data = d3.hierarchy(root, function (d) { return d.values; })
.sum(function (d) { return d.Tatverdaechtige_Insgesamt_deutsch; });
var tree = treemap(hierarchical_data);
// Clear the contents of the <svg>:
svg.selectAll("*").remove();
// Now populate the <svg>:
var nodes = svg.selectAll(".node")
.data(tree.children) //Bind the children of the root node in the tree
.enter().append("g")
.attr("class", "node")
.attr('transform', function (d) {
return 'translate(' + [d.x0, d.y0] + ')'
})
.on("click", function (d) {
if (d.children) { //The user clicked on a node that has children
drawTreemap(d.data);
} else { // The user clicked on a node that has no children (i.e. a leaf node).
// Do nothing.
}
});
nodes.append("rect")
.attr("width", function (d) { return Math.max(0, d.x1 - d.x0 - 1) + "px"; })
.attr("height", function (d) { return Math.max(0, d.y1 - d.y0 - 1) + "px"; })
.attr("fill", function (d) { return color(d.data.key); });
nodes.append("text")
.attr("dx", 4)
.attr("dy", 14)
//.style("visibility", "hidden") //Initially the text is not visible
.text(function (d) {
if (d.children) {
return d.data.key;
} else {
return d.data.Stadt_Landkreis + ", "
+ d.data.Straftat + ", "
+ d.data.Sexus + ": "
+ d.data.Tatverdaechtige_Insgesamt_deutsch;
}
});
}; // end of drawTreemap()
// Now call drawTreemap for the first time:
drawTreemap(nested_data[0]);
});
</script>
Finally, to animate the transitions, the following code is a zoomable treemap based on Mike Bostock's example, where flare.json has been replaced with the data from your CSV file, arranged with d3.nest() and d3.hierarchy() (this example uses D3 v3):
<!DOCTYPE html>
<html>
<head>
<style>
#chart {
width: 960px;
height: 500px;
background: #ddd;
}
text {
pointer-events: none;
}
.grandparent text {
font-weight: bold;
}
rect {
fill: none;
stroke: #fff;
}
rect.parent,
.grandparent rect {
stroke-width: 2px;
}
.grandparent rect {
fill: orange;
}
.grandparent:hover rect {
fill: #ee9700;
}
.children rect.parent,
.grandparent rect {
cursor: pointer;
}
.children rect.parent {
fill: #bbb;
fill-opacity: .5;
}
.children:hover rect.child {
fill: #bbb;
}
</style>
</head>
<body>
<script src="js/d3.v3.js"></script>
<script>
var margin = {top: 20, right: 0, bottom: 0, left: 0},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
formatNumber = d3.format(",d"),
transitioning;
var x = d3.scale.linear()
.domain([0, width])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, height])
.range([0, height]);
var treemap = d3.layout.treemap()
.children(function(d, depth) { return depth ? null : d._children; })
.sort(function(a, b) { return a.value - b.value; })
.ratio(height / width * 0.5 * (1 + Math.sqrt(5)))
.round(false);
var svg = d3.select("body").append("svg")
.attr("id", "chart") // Apply the #chart style to the <svg> element
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.bottom + margin.top)
.style("margin-left", -margin.left + "px")
.style("margin.right", -margin.right + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.style("shape-rendering", "crispEdges");
var grandparent = svg.append("g")
.attr("class", "grandparent");
grandparent.append("rect")
.attr("y", -margin.top)
.attr("width", width)
.attr("height", margin.top);
grandparent.append("text")
.attr("x", 6)
.attr("y", 6 - margin.top)
.attr("dy", ".75em");
d3.csv("data/GermanyCrimeData.csv", function (error, data) {
if (error) throw error;
var nested_data = d3.nest()
.key(function () { return "root"; })
.key(function (d) { return d.Stadt_Landkreis; })
.key(function (d) { return d.Straftat; })
.key(function (d) { return d.Sexus; })
.entries(data);
// In D3 v3, the syntax for arranging the data in a hierarchy is different:
var my_hierarchy = d3.layout.hierarchy()
.children(function (d) { return d.values; })
.value(function (d) { return d.Tatverdaechtige_Insgesamt_deutsch; });
// Apply the hierarchy to the nested data:
my_hierarchy(nested_data[0]); // This changes nested_data in-place
var root = nested_data[0];
initialize(root);
// my_hierarchy() has already added the aggregate values to each node
// in the tree, so instead we only need to add d._children to each node:
addUnderscoreChildren(root);
layout(root);
display(root);
function initialize(root) {
root.x = root.y = 0;
root.dx = width;
root.dy = height;
root.depth = 0;
}
// NEW FUNCTION:
function addUnderscoreChildren(d) {
if (d.children) {
// d has children
d._children = d.children;
d.children.forEach(addUnderscoreChildren);
}
}
/* NOT NEEDED:
// Aggregate the values for internal nodes. This is normally done by the
// treemap layout, but not here because of our custom implementation.
// We also take a snapshot of the original children (_children) to avoid
// the children being overwritten when when layout is computed.
function accumulate(d) {
return (d._children = d.children)
? d.value = d.children.reduce(function(p, v) { return p + accumulate(v); }, 0)
: d.value;
}*/
// Compute the treemap layout recursively such that each group of siblings
// uses the same size (1×1) rather than the dimensions of the parent cell.
// This optimizes the layout for the current zoom state. Note that a wrapper
// object is created for the parent node for each group of siblings so that
// the parent’s dimensions are not discarded as we recurse. Since each group
// of sibling was laid out in 1×1, we must rescale to fit using absolute
// coordinates. This lets us use a viewport to zoom.
function layout(d) {
if (d._children) {
treemap.nodes({_children: d._children});
d._children.forEach(function(c) {
c.x = d.x + c.x * d.dx;
c.y = d.y + c.y * d.dy;
c.dx *= d.dx;
c.dy *= d.dy;
c.parent = d;
layout(c);
});
}
}
function display(d) {
grandparent
.datum(d.parent)
.on("click", transition)
.select("text")
.text(name(d));
var g1 = svg.insert("g", ".grandparent")
.datum(d)
.attr("class", "depth");
var g = g1.selectAll("g")
.data(d._children)
.enter().append("g");
g.filter(function(d) { return d._children; })
.classed("children", true)
.on("click", transition);
g.selectAll(".child")
.data(function(d) { return d._children || [d]; })
.enter().append("rect")
.attr("class", "child")
.call(rect);
g.append("rect")
.attr("class", "parent")
.call(rect)
.append("title")
.text(function(d) { return formatNumber(d.value); });
g.append("text")
.attr("dy", ".75em")
// Replace d.name with d.key; use a function to provide
// a key for leaf nodes, because nest() does not set their
// 'key' property:
.text(function(d) { return d.key || keyOfLeafNode(d); })
.call(text);
// NEW FUNCTION:
function keyOfLeafNode(d) {
return d.Stadt_Landkreis + " "
+ d.Straftat + " "
+ d.Sexus + ": "
+ d.value;
}
function transition(d) {
if (transitioning || !d) return;
transitioning = true;
var g2 = display(d),
t1 = g1.transition().duration(750),
t2 = g2.transition().duration(750);
// Update the domain only after entering new elements.
x.domain([d.x, d.x + d.dx]);
y.domain([d.y, d.y + d.dy]);
// Enable anti-aliasing during the transition.
svg.style("shape-rendering", null);
// Draw child nodes on top of parent nodes.
svg.selectAll(".depth").sort(function(a, b) { return a.depth - b.depth; });
// Fade-in entering text.
g2.selectAll("text").style("fill-opacity", 0);
// Transition to the new view.
t1.selectAll("text").call(text).style("fill-opacity", 0);
t2.selectAll("text").call(text).style("fill-opacity", 1);
t1.selectAll("rect").call(rect);
t2.selectAll("rect").call(rect);
// Remove the old node when the transition is finished.
t1.remove().each("end", function() {
svg.style("shape-rendering", "crispEdges");
transitioning = false;
});
}
return g;
}
function text(text) {
text.attr("x", function(d) { return x(d.x) + 6; })
.attr("y", function(d) { return y(d.y) + 6; });
}
function rect(rect) {
rect.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y); })
.attr("width", function(d) { return x(d.x + d.dx) - x(d.x); })
.attr("height", function(d) { return y(d.y + d.dy) - y(d.y); });
}
function name(d) {
// d3.nest() gives each node the property 'key' instead of 'name',
// so replace 'd.name' with 'd.key':
return d.parent
? name(d.parent) + "." + d.key
: d.key;
}
});
</script>
</body>
</html>
Question: Can someone explain what is happening here?
The var root is set where it calls a function (classes), but when that function is defined it is passing in the root object. This is what I don't understand.
Some extra details:
Because of the words I would enter into a search engine, I can't find exactly what I'm looking for. Just giving me the name of what this is called is what I'm looking for, so I can further research it.
I have just learned about recursion.
This code is not mine, it's from here. It is running fine on my local server.
<center></center>
<script>
var diameter = 960,
format = d3.format(",d"),
color = d3.scaleOrdinal(d3.schemeCategory20c);
var bubble = d3.pack()
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("center").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
d3.json("flare.json", function(error, data) {
if (error) throw error;
var root = d3.hierarchy(classes(data))
.sum(function(d) { return d.value; })
.sort(function(a, b) { return b.value - a.value; });
bubble(root);
var node = svg.selectAll(".node")
.data(root.children)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.data.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) {
return color(d.data.packageName);
});
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.className.substring(0, d.r / 3); });
});
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children) node.children.forEach(function(child) { recurse(node.name, child); });
else classes.push({packageName: name, className: node.name, value: node.size});
console.log([classes]);
}
recurse(null, root);
return {children: classes};
}
d3.select(self.frameElement).style("height", diameter + "px");
</script>
The var root is set where it calls a function (classes), but when that function is defined it is passing in the root object.
No, it simply has a parameter that is named root. This has nothing to do with the root variable outside of the function.
You could rename the function parameter for example foobar (function classes(foobar), and inside recurse(null, foobar);), and things would still work exactly the same.
Your confusion is completely understandable, considering there are two uses of root, and they don't refer to the same thing.
Note that classes is invoked by classes(data), so the root in the classes function refers to the data loaded in flare.json, while the root in your data loading function is a d3 object, returned by d3.hierarchy.
I would recommend renaming the root parameter in classes so there's less confusion.
I'm trying to figure out how to modify and update from a selection of data with d3.js wordcloud.
Currently, I'm showing the top 10 results from a selection of data depending on indexed keys. I'd like to be able to switch this data depending on the keys, or if I want the top 10 or bottom 10 words.
here is a plnk so far;
http://plnkr.co/edit/cDTeGDaOoO5bXBZTHlhV?p=preview
I've been trying to refer to these guides, General Update Pattern, III and Animated d3 word cloud. However, I'm struggling to comprehend how to introduce a final update function, as almost all guides referring to this usually use a setTimeout to demonstrate how to update, and my brain just won't make the connection.
Any advice is most welcome!
Cheers,
(code here)
var width = 455;
var height = 310;
var fontScale = d3.scale.linear().range([0, 30]);
var fill = d3.scale.category20();
var svg = d3.select("#vis").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")")
// .selectAll("text")
d3.json("data.json", function(error, data) {
if (error) {
console.log(error)
}
else {
data = data
}
function sortObject(obj) {
var newValue = [];
var orgS = "MC";
var dateS = "Jan";
for (var question = 0; question < data.questions.length; question++) {
var organization = data.organizations.indexOf(orgS);
var date = data.dates.indexOf(dateS);
newValue.push({
label: data.questions[question],
value: data.values[question][organization][date]
});
}
newValue.sort(function(a, b) {
return b.value - a.value;
});
newValue.splice(10, 50)
return newValue;
}
var newValue = sortObject();
fontScale.domain([
d3.min(newValue, function(d) {
return d.value
}),
d3.max(newValue, function(d) {
return d.value
}),
]);
d3.layout.cloud().size([width, height])
.words(newValue)
.rotate(0)
.text(function(d) {
return d.label;
})
.font("Impact")
.fontSize(function(d) {
return fontScale(d.value)
})
.on("end", draw)
.start();
function draw(words) {
var selectVis = svg.selectAll("text")
.data(words)
selectVis
.enter().append("text")
.style("font-size", function(d) {
return fontScale(d.value)
})
.style("font-family", "Impact")
.style("fill", function(d, i) {
return fill(i);
})
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) {
return d.label;
})
selectVis
.transition()
.duration(600)
.style("font-size", function(d) {
return fontScale(d.value)
})
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.style("fill-opacity", 1);
selectVis.exit()
.transition()
.duration(200)
.style('fill-opacity', 1e-6)
.attr('font-size', 1)
.remove();
}
});
I did not see any update function within your code so I added that functionality in order to watch how the update works.
// Add a select elemnt to the page
var dropDown = d3.select("#drop")
.append("select")
.attr("name", "food-venues");
// Join with your venues
var foodVenues = data.organizations.map(function(d, i) {
return d;
})
// Append the venues as options
var options = dropDown.selectAll("option")
.data(foodVenues)
.enter()
.append("option")
.text(function(d) {
return d;
})
.attr("value", function(d) {
return d;
})
// On change call the update function
dropDown.on("change", update);
In order for a d3 word cloud to update correctly you need to calculate again the layout with the desired data
function update() {
// Using your function and the value of the venue to filter data
var filteredData = sortObject(data, this.value);
// Calculate the new domain with the new values
fontScale.domain([
d3.min(newValue, function(d) {
return d.value
}),
d3.max(newValue, function(d) {
return d.value
}),
]);
// Calculate the layout with new values
d3.layout.cloud()
.size([width, height])
.words(filteredData)
.rotate(0)
.text(function(d) {
return d.label;
})
.font("Impact")
.fontSize(function(d) {
return fontScale(d.value)
})
.on("end", draw)
.start();
}
I modified your sortObject function to receive an extra parameter which is the desired venue:
function sortObject(obj, venue) {
var newValue = [];
var orgS = venue || "MC";
// ....
}
Here is the working plnkr: http://plnkr.co/edit/B20h2bNRkyTtfs4SxE0v?p=preview
You should be able to use this approach to update with your desired restrictions. You may be able to add a checkbox with a event listener that will trigger the update function.
In your html:
<input checked type="checkbox" id="top" value="true"> <label for="top">Show top words</label>
In your javascript:
var topCheckbox = d3.select('#top')
.on("change", function() {
console.log('update!')
});
Sorry for the silly question, I'm just a poor d3 newbie...
I have the following demo bubble diagram on JSFiddle: what I am trying to achieve is to increase the radius of the circles whenever I click over them and, after that, to adjust the pack layout accordingly.
This is the code in JSFiddle for your convenience:
text {
font: 20px sans-serif;
}
<!DOCTYPE html>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
var root = {
"name": "Root",
"children": [
{
"name": "Leaf One",
"children": null,
"size": 1
},
{
"name": "Leaf Two",
"children": null,
"size": 1
},
{
"name": "Leaf Three",
"children": null,
"size": 1
}
],
"size": 1
};
var diameter = 400,
format = d3.format(",d"),
color = d3.scale.category20c();
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
// not needed in JSfiddle, data is hard-coded:
// d3.json("data.json", function(error, root) {
// if (error) throw error;
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root))
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.packageName); });
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); });
node.on("click", function(e, i){
var circle = svg.select("circle");
circle.attr("value", function(d) {
d.value += 1;
return d.value;
});
circle.attr("r", function(d) {
d.r += 1.0;
return d.r;
});
});
// });
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children) node.children.forEach(function(child) { recurse(node.name, child); });
else classes.push({packageName: name, className: node.name, value: node.size});
}
recurse(null, root);
return {children: classes};
}
d3.select(self.frameElement).style("height", diameter + "px");
</script>
I tried to do that via the node.on("click", ...) method but I got somehow stuck, as the modified circle is always the first one: what is the best way to select the circle I clicked?
Besides, how can I force the d3 pack layout to refresh after I modify the circle radius?
first, you have to remember that in event handlers the this is bound to the current DOM element (in out case - the <g>). so you can select clicked <g> by d3.select(this), and then select the circle within:
var circle = d3.select(this).select("circle");
(by just doing svg.select("circle") you select the first circle in the DOM, which always happens to be the same one)
in order to refresh the layout you have to update the underlying data, recalculate layout, recompute data join, and update values:
// store the underlying data in a value
var classedRoot = classes(root);
// ...your node creating code here
node.on("click", function(d) {
// update the data in classedRoot
d.value += 1;
// recalculate layout
var data = bubble.nodes(classedRoot);
// recompute data join
node.data(data, function(d) { return d.className; }) // use key function
// update values
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.select("circle")
.attr("r", function(d) { return d.r });
});
note the use of key function here to properly bind updated data with the circles already present. the value you use must be unique for all circles. i used className here, but if it's not unique, you'll have to use some kind of Id.
here's updated jsFiddle
I have been trying for long to make this : http://bl.ocks.org/mbostock/4063269#flare.json example work in my sample Rails app to learn about d3.js. But, I am getting hard time to make it work.
I put the following code in my index.html.erb file :
<script type="text/javascript">
var diameter = 960,
format = d3.format(",d"),
color = d3.scale.category20c();
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
d3.json("assets/data/flare.json", function(error, root) {
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root))
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.packageName); });
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); });
});
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children) node.children.forEach(function(child) { recurse(node.name, child); });
else classes.push({packageName: name, className: node.name, value: node.size});
}
recurse(null, root);
return {children: classes};
}
d3.select(self.frameElement).style("height", diameter + "px");
</script>
I put the flare.json file inside my app/assets/data directory. But, it seems like the javascript can not load the flare.json file from that location. I am just not sure how to make this work :( How to specify the location of the json file so that the javascript can load it and the code works? Any suggestion would be much much helpful.
Instead of loading the json file via d3.json, I rendered its contents on the javascript file and used a variable to store it.
var root = <%= render partial: 'controller_view/flare.json', formats: [:json] %>;