How to manipulate circles in d3 bubble diagram? - javascript

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

Related

updating/modifying d3 word cloud with new data

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!')
});

Add circle to pack layout

I'm stuck with this in the past 2 weeks. I've been browsing all StackOverflow questions about updating the pack layout and similars and I read the d3js documentation about it and the update pattern, but none of them solve this problem and I really can't append a new element to the pack layout without redrawing everything.
Demo: http://jsfiddle.net/v74qp3a7/1/
var diameter = 300;
// The layout I'm using now
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.value(function(d) { return 1 });
// The basic container
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(2,2)");
// My initial data source
var data = {
name: "Languages",
children: [{
name: "Functional",
children: [
{ name: "OCaml" },
{ name: "Haskell" },
{ name: "Erlang" }
]
}, {
name: "Imperative",
children: [
{ name: "BASIC" },
{ name: "Clipper" }
]
}]
};
// The node basis
var node = svg.datum(data).selectAll(".node")
.data(pack.nodes)
.enter().append("circle")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("r", function(d) {
return d.r;
});
function addToRoot(obj) {
// Modify the current data object
data.children.push(obj);
// I try to modify by entering the node and applying a transition
svg.datum(data).selectAll(".node")
.data(pack.nodes)
.enter().append("circle")
.transition()
.duration(500)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("r", function(d) {
return d.r;
});
}
d3.select(self.frameElement).style("height", diameter + "px");
I read about enter and exit, but I didn't figure out how I can update the data object (not the data properties, that is pretty simple). I've been trying to use the three update patterns and got no results.
How can I add a simple element and update the graphical interface without redrawing everything when I call addToRoot({ name: "foo" })?
First, your selector relies on a class that you're never assigning to the circles in question. Hence, your selection will always be empty and simply add everything again. Second, you need to handle the update selection as well, not just the enter selection.
var sel = svg.datum(data).selectAll(".node")
.data(pack.nodes);
sel.enter().append("circle")
.classed("node", true);
sel
.transition()
.duration(500)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("r", function(d) {
return d.r;
});
Complete demo here.

Stacked D3JS Bubble Chart

Hey I'm using D3JS as a chart library and I'm really interested in exploiting the cool features in the Bubble Chart. On the main D3JS chart site the following Bubble Chart is used to compare two sets of data:
Bubble Chart.
I was wondering if anybody actually had any idea of how to create a bubble chart like this, I'm struggling to get it work past using a size variable.
I just really want to be able to compare two sets of data for example:
Hostnames (45,955,158) VS Active Sites (21,335,629)
The code I am using is below, I use JSON to retrieve my data, I'm a major newbie when it comes to js and even more so this jQuery library so would appreciate any help.
index.html
<div class="four columns browserstats2003">
<h3>Browser Stats 2003</h3>
</div>
<div class="four columns mobilephonestats">
<h3>Smartphone Sales 2003</h3>
<p>The first smartphone had not been released in 2003.</p>
<div id=""></div>
</div>
mobile.json
{
"name": "flare",
"children": [
{
"name": "analytics",
"children": [
{
"name": "cluster",
"children": [
{"name": "Smartphone Sales", "size": 11111},
{"name": "Smartphone Salesa", "size": 2111}
]
}
]
}
]
}
js/js.js
// JavaScript Document
$(document).ready(function () {
// 2003 bubble chart
var diameter = 360,
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(".mobilephonestats").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
d3.json("mobile.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");
// end bubble year
});
In the example you provided, he's definitely using a force layout, which is a bit more complicated that the Bubble Chart you're using. You'll have to take into consideration things like collisions and animation.
Why not just have a look at the JavaScript he used to generate it.
Jim Vallandingham wrote an extensive tutorial on Bubble Clouds, which should get you started.
To create the split in the middle of the cirlce as a way of doing some form of data comparison, "clip-paths" are the way forward:
Append two circles, one for each data-set.
Append two clip-path, one for each data-set
Append a rectange to each clip-path.
Set the rectangle x attributes and width, which defines the position of the split in the middle. This has to be a function of the data.
Crop the rectangles and circles
Here's the code:
var nodeEnter = node.enter().append("a")
.attr("class", "g-node")
.call(force.drag);
var democratEnter = nodeEnter.append("g")
.attr("class", "g-democrat");
democratEnter.append("clipPath") // clip-path to crop the rectangle
.attr("id", function(d) { return "g-clip-democrat-" + d.id; })
.append("rect");
democratEnter.append("circle");
var republicanEnter = nodeEnter.append("g")
.attr("class", "g-republican");
republicanEnter.append("clipPath") // Clip-path to crop the rectangle
.attr("id", function(d) { return "g-clip-republican-" + d.id; })
.append("rect");
republicanEnter.append("circle");
node.selectAll("rect")
.attr("y", function(d) { return -d.r - clipPadding; })
.attr("height", function(d) { return 2 * d.r + 2 * clipPadding; });
// Defining the x-attr and width of the rectangle, which effectively splits the circle
node.select(".g-democrat rect")
.attr("x", function(d) { return -d.r - clipPadding; })
.attr("width", function(d) { return 2 * d.r * d.k + clipPadding; });
node.select(".g-republican rect")
.attr("x", function(d) { return -d.r + 2 * d.r * d.k; })
.attr("width", function(d) { return 2 * d.r; });
// Setting the clip-path to crop the circles
node.select(".g-democrat circle")
.attr("clip-path", function(d) { return d.k < 1 ? "url(#g-clip-democrat-" + d.id + ")" : null; });
node.select(".g-republican circle")
.attr("clip-path", function(d) { return d.k > 0 ? "url(#g-clip-republican-" + d.id + ")" : null; });
This should generate something like this:
<g class="g-democrat">
<clipPath id="g-clip-democrat-43">
<rect y="-63.36487389363757" height="126.72974778727514" x="-63.36487389363757" width="59.449375597303515">
</rect>
</clipPath>
<circle clip-path="url(#g-clip-democrat-43)" r="59.36487389363757">
</circle></g>
<g class="g-republican">
<clipPath id="g-clip-republican-43">
<rect y="-63.36487389363757" height="126.72974778727514" x="-3.915498296334057" width="118.72974778727514">
</rect>
</clipPath>
<circle clip-path="url(#g-clip-republican-43)" r="59.36487389363757">
</circle></g>

d3 / svg layering issue when adding new nodes after a time

I am having an annoying issue with my d3 force directed map I'm building where I initially render the page with the nodes and links I need, then periodically check for new information via ajax. When I need a new node and link I draw them, which is fine
However because of the way SVG layers elements, new links draw over older nodes, so where I have nodes as circles and draw lines between them, any new nodes added draw over the top of the circles on older nodes. See image below:
(http://i40.tinypic.com/4fx25j.gif)
I know it is not technically a d3 issue but there must be a way of fixing this. I did try deleting all the circles and redrawing them, but the issue is that the svg:g node it is attached to is too low in the layers so it is still drawn over.
Demo at jsfiddle - look at the following section
draw() {
...
}
as that is where the magic happens.
http://jsfiddle.net/zuzzy/uwAhy/
I have simulated the ajax using a 5 second timer, it was easier for the demo.
Any ideas?
As far as I am aware, you can only control the depth of an SVG element by it's position in the DOM.
So what might work for you is to create two groups <g id='lines'> and <g id='circles'>.
When you append your elements, all of the lines should be added to the first group, and all of the circles to the second.
You might have to alter the way that you add the elements, but so long as you make sure that the lines group appears before the circles group then you should be golden.
I apologise if this totally does not fit your implementation. I ran into a very similar problem and found the only resolution for me was to draw the 'lower' elements first.
Worked first time! I had already grouped all my elements under one so I just replaced:
var vis = d3.select("body")
.append("svg:svg")
.attr("pointer-events", "all");
.append('svg:g')
where i used vis.xxxx to render both links and circles, with
var vis = d3.select("body")
.append("svg:svg")
.attr("pointer-events", "all");
var linkvis = vis.append('svg:g')
.attr("id","link_elements");
vis = vis.append('svg:g')
.attr("id","node_elements");
and referred to linkvis when drawing the links and vis drawing the circles.
(NB I know this should be a comment but I couldn't fit it in and I thought it might be helpful for someone. #Paul's answer has been marked as the answer)
Another way of resolving this issue would be to use insert method as shown in following code.
link.enter().insert("line",".node"); //Inserts link element before the first DOM element with class node.
Posting just because this may be helpful for other users who search solution for this question.
//Settings:
//width, height and the default radius of the circles
var w = 1024,
h = 768,
r = 10;
//test data - usually this is recieved via ajax
//Initial Data:
var hosts = eval({
"ITEM003": {
"name": "ITEM003",
"parents": [],
"status": 0,
"hostgroup": "Secure"
},
"ITEM004": {
"name": "ITEM004",
"parents": [],
"status": 0,
"hostgroup": "Secure"
},
"CORE": {
"name": "CORE",
"parents": ["ITEM004", "ITEM003"],
"status": 0,
"hostgroup": "DMZ"
}
});
var mylinks = eval({
"0": ["CORE", "ITEM004"],
"1": ["CORE", "ITEM003"]
});
//Data after update
var updated_hosts = eval({
"ITEM003": {
"name": "ITEM003",
"parents": [],
"status": 0,
"hostgroup": "Secure"
},
"ITEM004": {
"name": "ITEM004",
"parents": [],
"status": 0,
"hostgroup": "Secure"
},
"CORE": {
"name": "CORE",
"parents": ["ITEM004", "ITEM003"],
"status": 0,
"hostgroup": "DMZ"
},
"REMOTE": {
"name": "REMOTE",
"parents": [],
"status": 0,
"hostgroup": ""
}
});
var updated_mylinks = eval({
"0": ["CORE", "ITEM004"],
"1": ["CORE", "ITEM003"],
"2": ["CORE", "REMOTE"]
});
//I define these here so they carry between functions - not really necessary in this jsfiddle probably
window.link = undefined;
window.node = undefined;
//make up my node object
window.nodeArray = [];
window.node_hash = [];
for (var key in hosts) {
var a = {
id: "node_" + hosts[key].name,
labelText: hosts[key].name,
status: hosts[key].status,
hostgroup: hosts[key].hostgroup,
class: "node realnode",
iconimage: hosts[key].iconimage,
added: true
};
nodeArray.push(a);
node_hash[key] = a;
}
//make up my link object
window.linkArray = [];
for (var key in mylinks) {
var linkcolor = "#47CC60";
var a = {
source: node_hash[mylinks[key][0]],
target: node_hash[mylinks[key][1]],
color: linkcolor,
class: "link reallink"
};
linkArray.push(a);
}
//make up my node text objects
//these are just more nodes with a different class
//we will append text to them later
//we also add the links to the linkArray now to bind them to their real nodes
window.text_hash = [];
for (var key in hosts) {
var a = {
id: "label_" + hosts[key].name,
text: hosts[key].name,
color: "#ffffff",
size: "6",
class: "node label",
added: true
};
nodeArray.push(a);
text_hash[key] = a;
}
//because the text labels are in the same order as the
//original nodes we know that node_hash[0] has label text_hash[0]
//it doesn't matter which we iterate through here
for (var key in text_hash) {
var a = {
source: node_hash[key],
target: text_hash[key],
class: "link label"
};
linkArray.push(a);
}
//set up the environment in a div called graph using the settings baove
window.vis = d3.select("body")
.append("svg:svg")
.attr("height", 500)
.attr("width", 500)
.attr("pointer-events", "all")
.append('svg:g')
//object to interact with the force libraries in d3
//the settings here set how the nodes interact
//seems a bit overcomplicated but it stops the diagram going nuts!
window.force = d3.layout.force()
.friction("0.7")
.gravity(function(d, i) {
if (d.class == "link reallink") {
return "0.95";
} else {
return "0.1";
}
})
.charge(function(d, i) {
if (d.class == "link reallink") {
return "-1500";
} else {
return "-300";
}
})
.linkDistance(function(d) {
if (d.class == "link reallink") {
return "120";
} else {
return "35";
}
})
.linkStrength(function(d) {
if (d.class == "link reallink") {
return "8";
} else {
return "6";
}
})
.nodes(nodeArray)
.links(linkArray)
.on("tick", tick)
node = vis.selectAll(".node");
link = vis.selectAll(".link");
//create the objects and run it
draw();
for (key in nodeArray) {
nodeArray[key].added = false;
}
//wait 5 seconds then update the diagram TO ADD A NODE
setTimeout(function() {
//update the objects
//vis.selectAll("g.node").data(nodeArray).exit().transition().ease("elastic").remove();
//vis.selectAll("line").data(linkArray).exit().transition().ease("elastic").remove();
var a = {
id: "node_REMOTE",
labelText: "REMOTE",
status: "0",
hostgroup: "",
class: "node realnode",
iconimage: "",
added: true
};
nodeArray.push(a);
node_hash["REMOTE"] = a;
var linkcolor = "#47CC60";
var a = {
source: node_hash["CORE"],
target: node_hash["REMOTE"],
color: linkcolor,
class: "link reallink"
};
linkArray.push(a);
//make up my node text objects
var a = {
id: "label_REMOTE",
text: "REMOTE",
color: "#000000",
size: "6",
class: "node label",
added: true
};
nodeArray.push(a);
text_hash["REMOTE"] = a;
var a = {
source: node_hash["REMOTE"],
target: text_hash["REMOTE"],
class: "link label"
};
linkArray.push(a);
//redraw it
draw();
}, 5000);
//----- functions for drawing and tick below
function draw() {
link = link.data(force.links(), function(d) {
return d.source.id + "-" + d.target.id;
});
node = node.data(force.nodes(), function(d) {
return d.id;
});
//create the link object using the links object in the json
//link = vis.selectAll("line").data(linkArray);
link.enter().insert("line", ".node")
.attr("stroke-width", '0')
.transition()
.duration(1000)
.ease("bounce")
.attr("stroke-width", function(d, i) {
if (d.class == 'link reallink') {
return '3';
} else {
return '0';
};
})
.style("stroke", function(d, i) {
return d.color;
})
.attr("class", function(d, i) {
return d.class;
});
//node = vis.selectAll("g").data(nodeArray);
node.enter().append("svg:g")
.attr("class", function(d) {
return d.class
})
.attr("id", function(d) {
return d.id
})
.call(force.drag);
//append to each node an svg circle element
vis.selectAll(".realnode").filter(function(d) {
return d.added;
})
.append("svg:circle")
.attr("r", "0")
.transition()
.duration(1000)
.ease("bounce")
.attr("r", "6")
.style("fill", "#000000")
.style("stroke", function(d) {
return d.color;
})
.style("stroke-width", "4");
//append to each node the attached text desc
vis.selectAll(".label").filter(function(d) {
return d.added;
})
.append("svg:text")
.attr("text-anchor", "middle")
.attr("fill", "black")
.style("pointer-events", "none")
.attr("font-size", "9px")
.attr("font-weight", "100")
.text(function(d) {
return d.text;
})
.attr("transform", "rotate(180)")
.transition()
.duration(1000)
.ease("bounce")
.attr("transform", "rotate(0)");
node.exit().transition().ease("elastic").remove();
link.exit().transition().ease("elastic").remove();
//activate it all - initiate the nodes and links
force.start();
}
function tick() {
node.attr("cx", function(d) {
return d.x = Math.max(r + 15, Math.min(w - r - 15, d.x));
})
.attr("cy", function(d) {
return d.y = Math.max(r + 15, Math.min(h - r - 15, d.y));
})
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
link.data(linkArray).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;
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

How to show d3.js circle pack graph in a Rails application?

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] %>;

Categories

Resources