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
Related
I'm still new in D3.js , currently I had hand over with the project using D3.js.
May I ask about how to change the colour Scale to image ?
The Nodes and Legends is related, the Node color is based on Legends.
Here with my JSON.
"colourScale":"d3.scaleOrdinal().domain([\"Low\",\"Medium\",\"High\",\"Very High\"]).range([\"#009a44\",\"#eaaa00\",\"#f68d2e\",\"#bc204b\"]);",
and here with my Javascript
// draw nodes
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.style("fill", function(d) {
if(d['group']==='Low')
{
return "#009a44";
}
else if(d['group']==='Medium')
{
return "#eaaa00";
}
else if(d['group']==='High')
{
return "#f68d2e";
}
else{
return "#bc204b";
}
})
.style("opacity", options.opacity)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click)
.call(drag);
Here with my screenshot of result.
First of all, I'd recommend you to read the this documentation that could provide you with some insights on how to manage colors with d3.js.
Actually you have all the required information in your JSON. So firstly you should create your scale object. Here is an example using the data provided in your JSON.
var myData = ["Low","Medium","High","Very High"];
// Defining colors
var color = d3.scaleOrdinal()
.domain(myData)
.range(["#009a44","#eaaa00","#f68d2e","#bc204b"]) // Your data
.unknown('black') // Fallback solution
After that you should apply this object to your d3 structure in some similar manner:
d3
... // Here goes your svg structure creation
.style('fill', function(d) { // Applying your original scale
return color(d.group);
});
Here you can find a complete example of your node graph with legend
var myData = ["Low","Medium","High","Very High"];
var graph = {
"nodes": [
{"id": "Myriel", "group": 'Low'},
{"id": "Napoleon", "group": 'Low'},
{"id": "Mlle", "group": 'Medium'},
{"id": "Mme", "group": 'Medium'},
{"id": "CountessdeLo", "group": 'High'},
{"id": "Geborand", "group": 'Very High'},
{"id": "Champtercier", "group": 'Very High'},
{"id": "Cravatte", "group": 'Random'},
],
"links": [
]
}
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
// Defining colors
var color = d3.scaleOrdinal()
.domain(myData)
.range(["#009a44","#eaaa00","#f68d2e","#bc204b"]) // Your data
.unknown('black') // Fallback solution
var linearScale = d3.scaleLinear()
.domain([0, myData.length - 1])
.range([0, 100]);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(graph.nodes)
.enter().append("g")
var circles = node.append("circle")
.attr("r", 20)
.attr("fill", function(d) { return color(d.group); });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
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("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
}
d3.select('#wrapper')
.selectAll('text')
.data(myData)
.enter()
.append('text')
.attr('y', function(d, i) {
return linearScale(i);
})
.text(function(d) {
return d;
})
.style('fill', function(d) {
return color(d);
});
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
text {
font-family: sans-serif;
font-size: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"></script>
<svg width="960" height="600">
<g id="wrapper" transform="translate(100, 40)">
</g>
</svg>
Please, let me know if my answer is not clear for you or you need some additional information or help.
I am working with d3 force diagrams at the moment, I am wanting to plot my child nodes around a parent node equally spaced, so for example if I have a parent node, and 4 linked child nodes, I would want each those node positioned at 90 degree intervals? Is that possible?
Here is my current force code,
app.force
.nodes(nodes)
.links(app.edges)
.on("tick", tick)
.start();
function tick(e) {
// console.log(link);
var k = 6 * e.alpha;
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; });
linkText
.attr("x", function(d) {
return ((d.source.x + d.target.x)/2);
})
.attr("y", function(d) {
return ((d.source.y + d.target.y)/2);
});
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node
.attr("cx", function(d) { return d.x })
.attr("cy", function(d) { return d.y })
}
app.force = d3.layout.force()
.charge(-300)
.linkDistance(85)
.size([width, height]);
//Where we will draw our visualisation
app.svg = d3.select(".visualisation").append("svg")
.attr('width', width)
.attr('height', height);
d3.layout.force() was not created with such customisation in mind. Of course you can set some parameters, but most of the positions are automatic calculated, and changing them can be very difficult (unless you create your own force function). Version 4.x is better in that matter, but not much.
In your specific case, you can set a very high (mathematically speaking, "very low", since it is negative) charge:
var force = d3.layout.force()
.charge(-3000)
But even doing that the angles are not exactly right angles, and they vary: if you click "run snippet" you can get an almost perfect cross, but if you click it again it's not that perfect the next time. And it will not work as expected if you have data with several levels of depth.
Here is a demo:
<script src="https://d3js.org/d3.v2.min.js?2.9.3"></script>
<style>
.link {
stroke: #aaa;
}
.node text {
stroke: #333;
cursor: pointer;
}
.node circle {
stroke: #fff;
stroke-width: 3px;
fill: #555;
}
</style>
<body>
<script>
var width = 400,
height = 300
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.distance(50)
.charge(-3000)
.size([width, height]);
var json = {
"nodes": [{
"name": "node1"
}, {
"name": "node2"
}, {
"name": "node3"
}, {
"name": "node4"
}, {
"name": "node5"
}],
"links": [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}, {
"source": 0,
"target": 3
}, {
"source": 0,
"target": 4
}]
};
force
.nodes(json.nodes)
.links(json.links)
.start();
var link = svg.selectAll(".link")
.data(json.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", 2);
var node = svg.selectAll(".node")
.data(json.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", 8);
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.name
});
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("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
</script>
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>
I'm attemtping to draw three circles and draw connected lines between each of these circles.
The end goal is to configure what circles are connected using json configuration but prior to this im just trying to connect the circles using the callbacks and hard code values.
Here is what I have so far :
<!DOCTYPE html>
<html>
<head>
<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" : 100 , r : 20},
{"name":"2","group":1, "x" : 200, "y" : 150 ,r : 30},
{"name":"3","group":2 , "x" : 300, "y" : 250 , r : 50}
],
"links":[
{"source":1,"target":0,"value":1}
]
}
var width = 2000;
var height = 500;
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.links)
.enter().append("line")
.attr("x1", function(d) { return 50 })
.attr("y1", function(d) { return 50 })
.attr("x2", function(d) { return 100 })
.attr("y2", function(d) { return 100 })
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 })
</script>
</body>
</html>
But no lines are being drawn. Each circle should contain a single line connecting it to the other circle.
I'm just hard coding the x1,y1,x2,y2 co-ordinates but I will be using the
co-ordinates of the other circles in order to determine the postions of the lines. Why are the lines not being drawn ? Is there standard d3 methods I should be utilising in order to connect these circles ?
fiddle : http://jsfiddle.net/zzz8svuq/10/
Update :
Here is the updated code which draws connected lines between circles as configured in dataset graph.nodes :
<!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: 50, r: 10 , connected : "2"},
{name: "2", "group": 1, x: 200, y: 90, r: 15, connected : "1"},
{name: "3", "group": 2, x: 300, y: 230, r: 25, connected : "1"}
]
}
$( document ).ready(function() {
var width = 2000;
var height = 500;
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
});
});
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>
You need to make sure that the lines have a stroke color or else they will be drawn white and you won't be able to see them.
var lines = svg.attr("class", "line")
.selectAll("line").data(graph.links)
.enter().append("line")
.style("stroke", "gray") // <<<<< Add a color
.attr("x1", function(d) { return 50 })
.attr("y1", function(d) { return 50 })
.attr("x2", function(d) { return 100 })
.attr("y2", function(d) { return 100 })
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.