Going to need a bubble chart for something I was working on and was using http://bl.ocks.org/mbostock/4063269 as an example. My data will be coming in as a flat format, so I wouldn't need to process the node tree and flatten it, so I removed that part.
Problem is that after removing that and simplifying the rest, it doesn't seem to do anything with the data. I'm guessing I have the data formatted incorrectly somehow, but I'm not sure.
http://tributary.io/inlet/b54cdb7104c40b1d7df3
I get no errors on it running, but I obviously have to be missing something here, right?
Even though your data is flat, the pack layout expects hierarchical data. You have to give it at least one children of your root node:
var json = {
"data": {
"children": [{ //<-- needs a child...
"name": "test",
"value": 55
}, {
"name": "test2",
"value": 34
}]
}
};
Full code:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
</head>
<body>
<script>
var json = {
"data": {
"children": [{
"name": "test",
"value": 55
}, {
"name": "test2",
"value": 34
}]
}
};
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");
var node = svg.selectAll(".node")
.data(bubble.nodes(json.data))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("title")
.text(function(d) {
return d.name + ": " + format(d.value);
});
node.append("circle")
.attr("r", function(d) {
return d.r;
})
.style("fill", function(d) {
return color(d.name);
});
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) {
return d.name;
});
</script>
</body>
</html>
Related
I have this problem, I am trying to use the "data" I get from the SPARQL query to make a graph that looks like this:
Bubble Chart
I can do it if I download first the .json file and then use it to make the graph, but not in this way.
<?xml version="1.0" encoding="ISO-8859-1" ?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="es">
<head>
<title>Contratos Zaragoza</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<script src="d3/d3.min.js"></script>
<style>
.boton{margin-top:1em;}
</style>
</head>
<body>
<script src="js/jquery-1.10.2.min.js"></script>
<p id="grafico"></p>
</body>
</html>
And here it is the javascript where the problem should be:
<script type="text/javascript">
var SPARQL_ENDPOINT = 'http://datos.zaragoza.es/sparql';
var query = 'PREFIX pproc: <http://contsem.unizar.es/def/sector- publico/pproc#>\
PREFIX dcterms: <http://purl.org/dc/terms/>\
SELECT DISTINCT ?CIF COUNT(?Titulo) as ?Contratos \
WHERE {\
?uri a <http://contsem.unizar.es/def/sector-publico/pproc#Contract>.\
?uri dcterms:title ?Titulo.\
?uri <http://purl.org/procurement/public-contracts#tender> ?a.\
?a <http://purl.org/procurement/public-contracts#supplier> ?empresaid.\
?empresaid <http://www.w3.org/ns/org#identifier> ?CIF.\
}\
ORDER BY desc(?Contratos)\
LIMIT 50';
$.getJSON(SPARQL_ENDPOINT + '?query=' + encodeURIComponent(query) + '&format=application%2Fsparql-results%2Bjson&timeout=0')
.success(function(data) {
var diameter = 1300,
format = d3.format(",d");
var color = d3.scale.ordinal()
.domain(["Sqoop", "Pig", "Apache", "a", "b", "c", "d", "e", "f", "g"])
.range(["steelblue", "pink", "lightgreen", "violet", "orangered", "green", "orange", "skyblue", "gray", "aqua"]);
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(10);
var svg = d3.select("#chart").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(data.results))
.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.value + ": " + d.value; });
node.append("circle")
.attr("r", function (d) {
return d.r;
})
.style("fill", function (d, i) {
return color(i);
});
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.value + ": " + d.value; });
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.bindings) node.bindings.forEach(function (child) {
recurse(node.CIF, child);
});
else classes.push({
packageName: name,
className: node.CIF,
value: node.Contratos.value
});
}
recurse(null, root);
return {
children: classes
};
}
d3.select(self.frameElement).style("height", diameter + "px");
});
</script>
I do not understand why it does not work that way because in both cases the object is the same.
Any comment would be very helpful.
I was using D3.js to plot a network of pie charts using a force-directed layout using the example here. Now I would like to plot the network of pies at pre-calculated coordinates and I am unsure how to proceed. I have added two node attributes (x,y) for plotting, now I need to access them within my javascript.
I would also like to add mouse over labels to my pie charts, so I have added a variable labels, but am unsure about how to access those as well, but if I could get help with the xy coordinates, I bet I could figure out the mouse-over bits.
Thanks in advance!
Here is the html file:
<!DOCTYPE html>
<html lang="en">
<head>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #808080;
stroke-opacity: .6;
}
</style>
</head>
<body>
<script type="text/javascript">
graph = { "nodes":[{"proportions": [
{"group":1, "value": 25 },
{"group":2, "value": 0 },
{"group":3, "value": 0 },
{"group":4, "value": 0 }],"x":-315.838,"y":-500},{"proportions": [
{"group":1, "value": 0 },
{"group":2, "value": 25 },
{"group":3, "value": 0 },
{"group":4, "value": 0 }],"x":500,"y":-315.851},{"proportions": [
{"group":1, "value": 0 },
{"group":2, "value": 0 },
{"group":3, "value": 25 },
{"group":4, "value": 0 }],"x":315.838,"y":500},{"proportions": [
{"group":1, "value": 0 },
{"group":2, "value": 0 },
{"group":3, "value": 0 },
{"group":4, "value": 25 }],"x":-500,"y":315.851}],"links": [{ "source":0, "target":1, "length":900, "width":9},
{ "source":0, "target":3, "length":900, "width":9},
{ "source":1, "target":2, "length":900, "width":9},
{ "source":2, "target":3, "length":900, "width":9}]
}
var labels = ['mycave1','mycave2','mycave3','mycave4'];
var width = 4000,
height = 1000,
radius = 100,
color = d3.scale.category10();
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.value; });
var arc = d3.svg.arc()
.outerRadius(radius)
.innerRadius(0);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.charge(-120)
.linkDistance(4 * radius)
.size([width, height]);
force.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node");
node.selectAll("path")
.data(function(d, i) {return pie(d.proportions); })
.enter()
.append("svg:path")
.attr("d", arc)
.attr("fill", function(d, i) { return color(d.data.group); });;
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"});
});
</script>
</body>
</html>
You can reposition the entire pie charts if you define each one as a g element and use the transform:translate attribute. Your code would look something like this:
var pies = svg.selectAll('.pie')
.data(graph.nodes)
.enter()
.append("g")
.attr("class", "node")
.attr('transform', function(d){
return 'translate(' + d.x + ',' + d.y + ')';
});
Here's a fiddle of that in your code: fiddle
Only one node is visible in that because the other nodes have negative x/y attributes and are being translated off the page.
All of the data associated with a node will be visible when you have that node in your selection, while only the data of the individual slice will be visible when you select all of the slices. Also note that g elements don't have x and y attributes, only a transform attribute. Source: MDN
I have a graph structure that stored in json format that looks like this:
{
"links": [
{
"source": 1,
"target": 0,
"value": 1
},
{
"source": 2,
"target": 0,
"value": 1
},
{
"source": 3,
"target": 0,
"value": 1
}
],
"nodes": [
{
"group": 3,
"name": "justintimberlake"
},
{
"group": 2,
"name": "Anastacia Lyn Newton"
},
{
"group": 2,
"name": "Maria Do Carmo"
}
],
"time": [
{
"source": 1,
"time": 6.854456018518518
},
{
"source": 2,
"time": 6.320115740740741
},
{
"source": 3,
"time": 5.962986111111111
}
]
}
And I have D3 code that draws this network:
<!DOCTYPE html xmlns:xlink="http://www.w3.org/1999/xlink">
<meta charset="utf-8">
<style>
// style here
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<div id="animviz"></div>
<script>
d3.json("post000.json", function(error, graph) {
var vv = window,
w = vv.innerWidth,
h = vv.innerHeight;
var svg = d3.select("#animviz")
.append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.call(d3.behavior.zoom().scaleExtent([0, 8]).on("zoom", zoom))
.append("g");
var color = d3.scale.category10();
var force = d3.layout.force()
.charge(-200)
.linkDistance(50)
.size([w, h]);
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.attr("transform", function(d) { return "translate(" + d + ")"; });
function zoom() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
var myMouseoverFunction = function() {
var circle = d3.select(this);
circle.transition().duration(100)
.attr("r", 20 )
node.append("title")
.text(function(d) { return d.name});
}
var myMouseoutFunction = function() {
var circle = d3.select(this);
circle.transition().duration(500)
.attr("r", 10 );
}
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 10)
.style("fill", function(d) { return color(d.group); })
.call(force.drag)
.on("mouseover", myMouseoverFunction)
.on("mouseout", myMouseoutFunction);
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
});
</script>
</body>
What I want is to draw this graph node by node according to time parameter (i.e. source: 1 should be drawn after 6.854456018518518 sec after node = 0 was drawn).
If it's not possible to draw them after special number of seconds, I'd like at least to draw them in order, so that I can see how nodes appear one after the other.
I checked similar questions (here, here, and here) and this tutorial but wasn't able to solve my problem. Ideally I would love to have similar to this but for my data from json file and not in infinite loop.
How can I draw a graph stored in json node by node?
one way to achieve this is to create nodes with radius = 0, and then use delay for showing each node (giving it radius = 12):
node.attr("r", 0);
var totalDelay = 0;
node
.transition()
.duration(0)
.delay(function(d, i) {
totalDelay += graph.time[i].time * 1000;
return totalDelay
})
.attr("r", 12);
See this jsFiddle
The problem with this solution is that all the links appear immediately, without waiting for its nodes to appear.
Added:
to deal with links problem, you may want to redraw graph after each interval, every time adding one node, and calculating the array of links for the nodes, displayed in each iteration:
var i = 0;
function redraw() {
if (i === graph.time.length) return;
setTimeout(function() {
var nodes = graph.nodes.slice(0, i + 1);
var links = graph.links.filter(function(link) {
return (link.source <= i && link.target <= i)
});
draw(nodes, links);
i += 1;
redraw();
}, graph.time[i].time * 1000);
}
See improved jsFiddle
For big datasets might be more efficient to keep the same nodes array and do nodes.push(graph.nodes[i]), instead of creating a new array in each iteration.
Using D3 I display a bunch of circles in different sizes, each filled with text. I'm stuck with finding the correct font size so that the text fits correct in the circle, depending of it's size and the length of the text. Long text should possibly be broken up in more lines. Here is my code:
var data = {
"name": "",
"children": [
{ "name": "This is a tag", "value": 242 },
{ "name": "Circle", "value": 162 },
{ "name": "Tree", "value": 80 },
{ "name": "My sentence is very long and needs breaks", "value": 80 },
]
}
var diameter = 300,
format = d3.format(",d");
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(data, function(error, root) {
var node = svg.selectAll(".node")
.data(bubble.nodes(data)
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return '#f88' });
// text part
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.style("font-size", function(d) { return Math.round(d.r/3)+'px'; })
.text(function(d) { return d.name.substring(0, d.r / 3); });
});
d3.select(self.frameElement).style("height", diameter + "px");
I have created a fiddle as well on http://jsfiddle.net/L4nMx/
I think I should calculate the width of the text and modify the font size until it matches the circle's size or something like that. Or is there any "strech" function to do this the easy way?
This solution is fine for me for now. It's not accurate maths but fits anyway.
See it in action on http://jsfiddle.net/L4nMx/3/
.style("font-size", function(d) {
var len = d.name.substring(0, d.r / 3).length;
var size = d.r/3;
size *= 10 / len;
size += 1;
return Math.round(size)+'px';
})
.text(function(d) {
var text = d.name.substring(0, d.r / 3);
return text;
});
Next step would be to break long text into multiple lines so you could enlarge font sizes in such cases but I didn't manage to solve this. It's not easy in SVG because simple line breaks are not possible. Maybe the wrapping solutions from the comments in the question can be added here - somehow...
There is this bl.ocks https://bl.ocks.org/mbostock/1846692
which is basically
node.append("circle")
.attr("r", function(d) { return d.r; });
node.append("text")
.text(function(d) { return d.name; })
.style("font-size", function(d) { return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 24) + "px"; })
.attr("dy", ".35em");
I have done the bubble chart. But I'm not getting different colors for each bubble. How could I do that? How could I give different colors for each circle? And the circle data with the highest value should always come at the center surrounded by other bubbles.
Snippet:
var diameter = 200,
format = d3.format(",d"),
color = ["#7b6888", "#ccc", "#aaa", "#6b486b"];
var bubble = d3.layout.pack()
.size([diameter, diameter]);
var svg = d3.select("#bubbleCharts").append("svg")
.attr("width", diameter + 10)
.attr("height", diameter)
.attr("class", "bubble");
var a;
d3.json("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 + 20 + "," + d.y + ")"; });
node.append("circle")
.attr("r", function(d) { return d.r+ 7; })
.style("fill", function(d) {
for(a in color){
return color[a]; };} );
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.value+"%"; });
});
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, value: node.value});
}
recurse(null, root);
return {children: classes};
}
My data is:
{
"name": "Spending Activity",
"children": [
{"name": "Petrol", "value": 60},
{"name": "Travel", "value": 10},
{"name": "Medical", "value": 25},
{"name": "Shopping", "value": 5}
]
}
You probably want a colour scale to get the fill colours:
var colour = d3.scale.category20();
Then you can set the fill colour like this:
node.append("circle")
.attr("r", function(d) { return d.r+ 7; })
.style("fill", function(d, i) { return colour(i); });
As for the positions of the nodes, the pack layout doesn't provide any functionality to affect that, i.e. you cannot force a particular circle to be in the center.
This reference card may be handy while choosing built-in D3 color sets:
Alternatively, you can use colorbrewer color sets: (see example here)