I've been using the sample code from this d3 project to learn how to display d3 graphs and I can't seem to get text to show up in the middle of the circles (similar to this example and this example). I've looked at other examples and have tried adding
node.append("title").text("Node Name To Display")
and
node.append("text")
.attr("text-anchor", "middle")
.attr("dy", ".3em").text("Node Name To Display")
right after node is defined but the only results I see is "Node Name To Display" is showing up when I hover over each node. It's not showing up as text inside the circle. Do I have to write my own svg text object and determine the coordinates of that it needs to be placed at based on the coordinates of radius of the circle? From the other two examples, it would seem like d3 already takes cares of this somehow. I just don't know the right attribute to call/set.
There are lots of examples showing how to add labels to graph and tree visualizations, but I'd probably start with this one as the simplest:
http://bl.ocks.org/950642
You haven’t posted a link to your code, but I'm guessing that node refers to a selection of SVG circle elements. You can’t add text elements to circle elements because circle elements are not containers; adding a text element to a circle will be ignored.
Typically you use a G element to group a circle element (or an image element, as above) and a text element for each node. The resulting structure looks like this:
<g class="node" transform="translate(130,492)">
<circle r="4.5"/>
<text dx="12" dy=".35em">Gavroche</text>
</g>
Use a data-join to create the G elements for each node, and then use selection.append to add a circle and a text element for each. Something like this:
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", 4.5);
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
One downside of this approach is that you may want the labels to be drawn on top of the circles. Since SVG does not yet support z-index, elements are drawn in document order; so, the above approach causes a label to be drawn above its circle, but it may be drawn under other circles. You can fix this by using two data-joins and creating separate groups for circles and labels, like so:
<g class="nodes">
<circle transform="translate(130,492)" r="4.5"/>
<circle transform="translate(110,249)" r="4.5"/>
…
</g>
<g class="labels">
<text transform="translate(130,492)" dx="12" dy=".35em">Gavroche</text>
<text transform="translate(110,249)" dx="12" dy=".35em">Valjean</text>
…
</g>
And the corresponding JavaScript:
var circle = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 4.5)
.call(force.drag);
var text = svg.append("g")
.attr("class", "labels")
.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
This technique is used in the Mobile Patent Suits example (with an additional text element used to create a white shadow).
I found this guide very useful in trying to accomplish something similar :
https://www.dashingd3js.com/svg-text-element
Based on above link this code will generate circle labels :
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body style="overflow: hidden;">
<div id="canvas" style="overflow: hidden;"></div>
<script type="text/javascript">
var graph = {
"nodes": [
{name: "1", "group": 1, x: 100, y: 90, r: 10 , connected : "2"},
{name: "2", "group": 1, x: 200, y: 50, r: 15, connected : "1"},
{name: "3", "group": 2, x: 200, y: 130, r: 25, connected : "1"}
]
}
$( document ).ready(function() {
var width = 2000;
var height = 2000;
var svg = d3.select("#canvas").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var lines = svg.attr("class", "line")
.selectAll("line").data(graph.nodes)
.enter().append("line")
.style("stroke", "gray") // <<<<< Add a color
.attr("x1", function (d, i) {
return d.x
})
.attr("y1", function (d) {
return d.y
})
.attr("x2", function (d) {
return findAttribute(d.connected).x
})
.attr("y2", function (d) {
return findAttribute(d.connected).y
})
var circles = svg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", function (d, i) {
return d.r
})
.attr("cx", function (d, i) {
return d.x
})
.attr("cy", function (d, i) {
return d.y
});
var text = svg.selectAll("text")
.data(graph.nodes)
.enter()
.append("text");
var textLabels = text
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.text( function (d) { return d.name })
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
});
function findAttribute(name) {
for (var i = 0, len = graph.nodes.length; i < len; i++) {
if (graph.nodes[i].name === name)
return graph.nodes[i]; // Return as soon as the object is found
}
return null; // The object was not found
}
</script>
</body>
</html>
If you want to grow the nodes to fit large labels, you can use the getBBox property of an SVG text node after you've drawn it. Here's how I did it, for a list of nodes with fixed coordinates, and two possible shapes:
nodes.forEach(function(v) {
var nd;
var cx = v.coord[0];
var cy = v.coord[1];
switch (v.shape) {
case "circle":
nd = svg.append("circle");
break;
case "rectangle":
nd = svg.append("rect");
break;
}
var w = 10;
var h = 10;
if (v.label != "") {
var lText = svg.append("text");
lText.attr("x", cx)
.attr("y", cy + 5)
.attr("class", "labelText")
.text(v.label);
var bbox = lText.node().getBBox();
w = Math.max(w,bbox.width);
h = Math.max(h,bbox.height);
}
var pad = 4;
switch (v.shape) {
case "circle":
nd.attr("cx", cx)
.attr("cy", cy)
.attr("r", Math.sqrt(w*w + h*h)/2 + pad);
break;
case "rectangle":
nd.attr("x", cx - w/2 - pad)
.attr("y", cy - h/2 - pad)
.attr("width", w + 2*pad)
.attr("height", h + 2*pad);
break;
}
});
Note that the shape is added, the text is added, then the shape is positioned, in order to get the text to show on top.
Related
I have an svg graph using d3.js, but I'm having trouble with the viewbox. As I have it now, the distance under the graph (30px) is fine, but there is still too much space between the text above the graph (48 px).
I don't have much experience with viewbox yet, so I researched and the best answer I found was at Find svg viewbox that trim whitespace around where the solution was to use the bounding box. That answer involves creating a button to click to show the graph correctly presented, but I want it to be positioned without additional user input.
Here is the html code above the graph:
<h1 class="h1_info">First Entry</h1><br>
<div class="zebra_01">This is the general text to describe the issue.<br><br>
With no further improvements:<ul><li>One item was this tall<br>
</li><li>Another item was that tall</li><li>
<span class="span_00">The first is taller than the second</span></li></ul>
</div>
<svg viewBox="0 0 700 100" preserveAspectRatio="xMinYMid meet" class="d3svg"></svg>
<script src="JS\D3_BarChart.js"></script>
Other html code starts here, 30px below the graph.
Here is the javascript:
var dataArray = [23, 13];
var colors = [ "red", "green" ];
var names = [ "First Name", "Second Name" ];
var widths = [ "5", "700" ]
var dists = ["45", "40"]
var svg = d3.select("svg.d3svg")
.attr("height", "auto")
.attr("width", "100%")
var bar = svg.selectAll("g")
.data(dataArray)
.enter().append("g")
var gradient = svg
.append("linearGradient")
.attr("y1", "0%")
.attr("y2", "20%")
.attr("x1", "0%")
.attr("x2", "25%")
.attr("id", "gradient")
.attr("gradientUnits", "userSpaceOnUse")
gradient
.append("stop")
.attr('class', 'start')
.attr("offset", "0%")
.attr("stop-color", "red")
.attr("stop-opacity", 1);
gradient
.append("stop")
.attr('class', 'end')
.attr("offset", "100%")
.attr("stop-color", "green")
.attr("stop-opacity", 1);
var rect = bar.append('rect')
.attr("height", "7")
.attr("width", function(d, i) { return widths[i] })
.attr("y", function(d, i) { return (i * dists[i]) + 30 })
.attr("x", "0")
.attr("fill", "url(#gradient)")
var text = bar.append('text')
.attr("class", "text-svg")
.text (function(d, i) { return names[i] })
.attr("x", "0")
.attr("y", function(d, i) { return (i * dists[i]) + 55 });
So my question is whether using bounding box is the correct solution, and how can it be set without using a button?
Thanks for any help on this.
Looking at the chart, the first element starts at y =30. You can change the viewBox to be consistent with this fact:
viewBox="0 30 700 100"
Alternatively, if you are not sure about the y coordinates of the first element, you can use d3 to programmatically adjust the viewBox. Here is the relevant piece of your code:
var rect = bar.append('rect')
.attr("height", "7")
.attr("width", function(d, i) { return widths[i] })
.attr("y", function(d, i) { return (i * dists[i]) + 30 })
.attr("x", "0")
.attr("fill", "url(#gradient)")
svg.attr("viewBox", `0 ${rect.attr("y")} 700 100`) //add this. It uses es6 way to interpolate a string
The last line takes the y attribute of the rect (the topmost element in this case) and changes the viewBox accordingly.
I've been using the sample code from this d3 project to learn how to display d3 graphs and I can't seem to get text to show up in the middle of the circles (similar to this example and this example). I've looked at other examples and have tried adding
node.append("title").text("Node Name To Display")
and
node.append("text")
.attr("text-anchor", "middle")
.attr("dy", ".3em").text("Node Name To Display")
right after node is defined but the only results I see is "Node Name To Display" is showing up when I hover over each node. It's not showing up as text inside the circle. Do I have to write my own svg text object and determine the coordinates of that it needs to be placed at based on the coordinates of radius of the circle? From the other two examples, it would seem like d3 already takes cares of this somehow. I just don't know the right attribute to call/set.
There are lots of examples showing how to add labels to graph and tree visualizations, but I'd probably start with this one as the simplest:
http://bl.ocks.org/950642
You haven’t posted a link to your code, but I'm guessing that node refers to a selection of SVG circle elements. You can’t add text elements to circle elements because circle elements are not containers; adding a text element to a circle will be ignored.
Typically you use a G element to group a circle element (or an image element, as above) and a text element for each node. The resulting structure looks like this:
<g class="node" transform="translate(130,492)">
<circle r="4.5"/>
<text dx="12" dy=".35em">Gavroche</text>
</g>
Use a data-join to create the G elements for each node, and then use selection.append to add a circle and a text element for each. Something like this:
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", 4.5);
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
One downside of this approach is that you may want the labels to be drawn on top of the circles. Since SVG does not yet support z-index, elements are drawn in document order; so, the above approach causes a label to be drawn above its circle, but it may be drawn under other circles. You can fix this by using two data-joins and creating separate groups for circles and labels, like so:
<g class="nodes">
<circle transform="translate(130,492)" r="4.5"/>
<circle transform="translate(110,249)" r="4.5"/>
…
</g>
<g class="labels">
<text transform="translate(130,492)" dx="12" dy=".35em">Gavroche</text>
<text transform="translate(110,249)" dx="12" dy=".35em">Valjean</text>
…
</g>
And the corresponding JavaScript:
var circle = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 4.5)
.call(force.drag);
var text = svg.append("g")
.attr("class", "labels")
.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
This technique is used in the Mobile Patent Suits example (with an additional text element used to create a white shadow).
I found this guide very useful in trying to accomplish something similar :
https://www.dashingd3js.com/svg-text-element
Based on above link this code will generate circle labels :
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body style="overflow: hidden;">
<div id="canvas" style="overflow: hidden;"></div>
<script type="text/javascript">
var graph = {
"nodes": [
{name: "1", "group": 1, x: 100, y: 90, r: 10 , connected : "2"},
{name: "2", "group": 1, x: 200, y: 50, r: 15, connected : "1"},
{name: "3", "group": 2, x: 200, y: 130, r: 25, connected : "1"}
]
}
$( document ).ready(function() {
var width = 2000;
var height = 2000;
var svg = d3.select("#canvas").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var lines = svg.attr("class", "line")
.selectAll("line").data(graph.nodes)
.enter().append("line")
.style("stroke", "gray") // <<<<< Add a color
.attr("x1", function (d, i) {
return d.x
})
.attr("y1", function (d) {
return d.y
})
.attr("x2", function (d) {
return findAttribute(d.connected).x
})
.attr("y2", function (d) {
return findAttribute(d.connected).y
})
var circles = svg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", function (d, i) {
return d.r
})
.attr("cx", function (d, i) {
return d.x
})
.attr("cy", function (d, i) {
return d.y
});
var text = svg.selectAll("text")
.data(graph.nodes)
.enter()
.append("text");
var textLabels = text
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.text( function (d) { return d.name })
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
});
function findAttribute(name) {
for (var i = 0, len = graph.nodes.length; i < len; i++) {
if (graph.nodes[i].name === name)
return graph.nodes[i]; // Return as soon as the object is found
}
return null; // The object was not found
}
</script>
</body>
</html>
If you want to grow the nodes to fit large labels, you can use the getBBox property of an SVG text node after you've drawn it. Here's how I did it, for a list of nodes with fixed coordinates, and two possible shapes:
nodes.forEach(function(v) {
var nd;
var cx = v.coord[0];
var cy = v.coord[1];
switch (v.shape) {
case "circle":
nd = svg.append("circle");
break;
case "rectangle":
nd = svg.append("rect");
break;
}
var w = 10;
var h = 10;
if (v.label != "") {
var lText = svg.append("text");
lText.attr("x", cx)
.attr("y", cy + 5)
.attr("class", "labelText")
.text(v.label);
var bbox = lText.node().getBBox();
w = Math.max(w,bbox.width);
h = Math.max(h,bbox.height);
}
var pad = 4;
switch (v.shape) {
case "circle":
nd.attr("cx", cx)
.attr("cy", cy)
.attr("r", Math.sqrt(w*w + h*h)/2 + pad);
break;
case "rectangle":
nd.attr("x", cx - w/2 - pad)
.attr("y", cy - h/2 - pad)
.attr("width", w + 2*pad)
.attr("height", h + 2*pad);
break;
}
});
Note that the shape is added, the text is added, then the shape is positioned, in order to get the text to show on top.
I have a bunch of static circles and I want to connect them with lines (it's a dependency graph). All the examples I see are done with d3's ready-made layouts and I'm not sure how to approach this efficiently. I also want to highlight lines related to a node when I mouse-over that node, as well as fade any other shapes/lines.
This is what I have for now: (it just draws evenly spaced and sized circles according to area size given)
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script>
</head>
<body style="overflow: hidden;">
<div id="drawarea" style="overflow: hidden;"></div>
<script type="text/javascript">
var dataset = [],
i = 0;
for(i=0; i<45; i++){
dataset.push(Math.round(Math.random()*100));
}
var width = 5000,
height = 3000;
var svg = d3.select("#drawarea").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom))
.append("g");
var div_area = width*height,
num_nodes = dataset.length,
node_area = div_area/num_nodes*0.7,
node_to_padding_ratio = 0.50,
node_dia_inc_pad = Math.sqrt(node_area),
node_radius_wo_pad = node_dia_inc_pad/2*node_to_padding_ratio,
node_padding = node_dia_inc_pad/2*(1-node_to_padding_ratio),
nodes_in_width = parseInt(width/(node_dia_inc_pad)),
nodes_in_height = parseInt(height/(node_dia_inc_pad));
svg.selectAll("circle")
.data(dataset)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", node_radius_wo_pad)
.attr("cx", function(d, i){ return 2*node_radius_wo_pad+i%nodes_in_width*node_dia_inc_pad;})
.attr("cy", function(d, i){ return 2*node_radius_wo_pad+(parseInt(i/nodes_in_width))*node_dia_inc_pad})
.on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
.on("mouseout", function(){d3.select(this).style("fill", "white");})
function zoom() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
</script>
</body>
</html>
EDIT: My revised code:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script>
</head>
<body style="overflow: hidden;">
<div id="canvas" style="overflow: hidden;"></div>
<script type="text/javascript">
var graph = {
"nodes":[
{"name":"Myriel","group":1},
{"name":"Napoleon","group":1}
],
"links":[
{"source":1,"target":0,"value":1}
]
}
var width = 2000,
height = 1000;
var svg = d3.select("#canvas").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom))
.append("g");
var div_area = width*height,
num_nodes = graph.nodes.length,
node_area = div_area/num_nodes,
node_to_padding_ratio = 0.50,
node_dia_inc_pad = Math.sqrt(node_area),
node_radius_wo_pad = node_dia_inc_pad/2*node_to_padding_ratio,
node_padding = node_dia_inc_pad/2*(1-node_to_padding_ratio),
nodes_in_width = parseInt(width/(node_dia_inc_pad)),
nodes_in_height = parseInt(height/(node_dia_inc_pad));
var xScale = d3.scale.linear()
.domain([0,nodes_in_width])
.range([node_radius_wo_pad,width-node_radius_wo_pad]);
var yScale = d3.scale.linear()
.domain([0,nodes_in_height])
.range([node_radius_wo_pad,height-node_radius_wo_pad]);
var lines = svg.attr("class", "line")
.selectAll("line").data(graph.links)
.enter().append("line")
.attr("x1", function(d) { return xScale(d.source%nodes_in_width); })
.attr("y1", function(d) { return yScale(parseInt(d.source/nodes_in_width)); })
.attr("x2", function(d) { return xScale(d.target%nodes_in_width); })
.attr("y2", function(d) { return yScale(parseInt(d.target/nodes_in_width)); })
.attr("src", function(d) { return d.source; })
.attr("trgt", function(d) { return d.target; })
.style("stroke", "grey");
var circles = svg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", node_radius_wo_pad)
.attr("cx", function(d, i){ return xScale(i%nodes_in_width);})
.attr("cy", function(d, i){ return yScale(parseInt(i/nodes_in_width));})
.attr("index", function(d, i){return i;})
.on("mouseover", function(){
var that = this;
lines.filter(function() {
return d3.select(this).attr("src") == d3.select(that).attr("index");
}).style("stroke", "red");
lines.filter(function() {
return d3.select(this).attr("trgt") == d3.select(that).attr("index");
}).style("stroke", "green");
lines.filter(function() {
return (d3.select(this).attr("trgt") != d3.select(that).attr("index") && d3.select(this).attr("src") != d3.select(that).attr("index"));
}).style("display", "none");
d3.select(this).style("fill", "aliceblue");
})
.on("mouseout", function(){
lines.style("stroke", "grey")
.style("display", "block");
d3.select(this).style("fill", "white");
});
function zoom() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
</script>
</body>
</html>
What I want to do now is have the circles the lines point to and from be colored similarly. I'm not sure how to make the reference to them from the "mouseover" event of a circle though. Will do some testing...
You haven't specified how your nodes are connected, so I'm assuming that everything is connected to everything. The principle is the same as for any other layout -- you take the data you have that determines the links and pass it to .data(). In your code, the coordinates aren't part of the data, which makes it a bit more verbose, but still quite straightforward.
To add the links, I'm using a nested selection -- I'm adding a g element for each node and underneath the connections to all the other nodes.
var lines = svg.selectAll("g.line").data(dataset)
.enter().append("g").attr("class", "line")
.selectAll("line").data(dataset)
.enter().append("line")
.attr("x1", function(d, i) { return 2*node_radius_wo_pad+i%nodes_in_width*node_dia_inc_pad; })
.attr("y1", function(d, i) { return 2*node_radius_wo_pad+(parseInt(i/nodes_in_width))*node_dia_inc_pad; })
.attr("x2", function(d, i, j) { return 2*node_radius_wo_pad+j%nodes_in_width*node_dia_inc_pad; })
.attr("y2", function(d, i, j) { return 2*node_radius_wo_pad+(parseInt(j/nodes_in_width))*node_dia_inc_pad; });
This adds a line for every pair of nodes. Note that it will add links between the same nodes (which you won't be able to see) and 2 links between each pair of nodes -- once starting at one node and once at the other. I haven't filtered out these cases here to keep the code simple. In your particular application, I'm guessing that the connections are determined in another way anyway.
To highlight the links that are connected a particular node on highlight, I'm using the links variable that contains all of them and filtering out the ones whose start coordinates are different from the coordinates of the circle. The filtered selection is then painted red.
.on("mouseover", function(){
var that = this;
lines.filter(function() {
return d3.select(this).attr("x1") == d3.select(that).attr("cx") && d3.select(this).attr("y1") == d3.select(that).attr("cy");
}).style("stroke", "red");
d3.select(this).style("fill", "aliceblue");
})
If the coordinates are part of the data, everything will become a bit easier and look more like the examples you may have seen for the force layout for example. I would recommend to create a data structure much like what's used there for your links, with source and target attributes that determine the source and target nodes.
Complete example here.
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>
I have the following JavaScript code that uses the D3.js library to draw a tree (it follows the standard structure one can find in the various online tutorials):
var json = {
"name": "A",
"children": [{
"name": "B"
}]
};
var tree = d3.layout.tree().size([200, 200]);
var nodes = tree.nodes(json);
var vis = d3.select("#chart").attr("width", 300)
.attr("height", 300)
.append("svg:g")
.attr("transform", "translate(40, 40)");
var diagonal = d3.svg.diagonal();
var link = vis.selectAll("path.link").data(tree.links(nodes)).enter()
.append("svg:path")
.attr("class", "link")
.attr("d", diagonal);
var node = vis.selectAll("g.node").data(nodes).enter().append("svg:g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("svg:circle").attr("r", 10);
node.append("svg:text").attr("dx", function (d) {
return 10;
})
.attr("dy", 10)
.attr("text-anchor", function (d) {
return "start";
})
.text(function (d) {
return d.name;
});
JSFIDDLE
It works mostly fine, except for trees in which a vertex has an odd number of children (1, 3, ...); in this case, the edge for the odd vertex will not be drawn (e.g., in the above example, the edge between A and B is not displayed). What am I missing?
You are missing the style for the node links. Something variation of this:
<style>
.link {
fill: none;
stroke: #ccc;
stroke-width: 4.5px;
}
</style>
Or, set it on the link itself:
.attr("d", diagonal).attr({ 'fill': 'none', 'stroke': 'grey', 'stroke-width': 4 });
It depends on odd vs. even number because by default a path gets no stroke and a fill color of black. So a straight line doesn't show up but the curved ones get filled.