d3 force directed graph, links not being drawn - javascript

I'm new to d3 and haven't much web frontend development experience. For a web application I have I'm trying to draw a force directed graph. I've been trying the last few hours to get it to work. I've been looking at lots of different code example and what I'm doing looks very similar. I eventually got nodes to draw but the links between the nodes don't show up and I was trying different things and nothing seems to work. I don't know why my code wouldn't draw the edges.
From printing the nodes and links to the console I saw that the nodes got extra attributes like the d3 docs had mentioned but the links never seem to get these attributes. Below is my javascript file and the JSON file. I reduced the JSON file to only 3 entries to try and make it easier to solve the problem.
var height = 1080;
var width = 1920;
var color = d3.scale.category20();
var force = d3.layout.force()
.linkDistance(-120)
.linkStrength(30)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("/static/javascript/language_data.json", function(data){
force
.nodes(data.languages)
.links(data.language_pairs)
.start();
var link = svg.selectAll(".link")
.data(data.language_pairs)
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(data.languages)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
node.append("title")
.text(function(d) { return d.language; });
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; });
});
});
Here is the JSON file:
From looking at few examples my understanding is that the source and target are index positions from the list of nodes.
{
"languages":[
{"language": "TypeScript", "group": 1},
{"language": "Java", "group": 2},
{"language": "VHDL", "group": 3}
],
"language_pairs":[
{"source": "0", "target": "1", "value": 5},
{"source": "1", "target": "2", "value": 5},
{"source": "2", "target": "0", "value": 5}
]
}
Sorry if I left out anything! Thanks for any help!

Two issues:
1.) Your "language_pairs" source/target indexes are strings and not numbers. Get rid of the quotes:
"language_pairs":[
{"source": 0, "target": 1, "value": 5},
{"source": 1, "target": 2, "value": 5},
{"source": 2, "target": 0, "value": 5}
]
2.) Your linkDistance and linkStrength parameters don't make sense:
var force = d3.layout.force()
.linkDistance(-120) // negative distance?
.linkStrength(30) // according to the docs, this must be between 0 and 1?
.size([width, height]);
Here's an example that fixes these problems.

Related

Why the data can't be bind with JSON file?

This is JSON file named Unit2Vec_tSNE.json and we can get the data points from pos element.
{
"No 0": {
"dur": 135,
"name": "00000001_0",
"lab": "sil",
"pos": [
17.64800262451172,
-1.794445514678955
]
},
"No 1": {
"dur": 28,
"name": "00000001_1",
"lab": "uo",
"pos": [
-17.94196891784668,
-0.8764857649803162
]
},
"No 2": {
"dur": 21,
"name": "00000001_2",
"lab": "x",
"pos": [
2.7473323345184326,
13.970715522766113
]
}
}
The JavaScript code is the following and I try use .data(dataset) function to bind the JSON data to points.
But very strangely, it displays nothing and the console.log('Here!!!!!!!!!') of .attr("cx", function(d) doesn't run.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3: A simple scatterplot, setting radii from data</title>
<script type="text/javascript" src="../d3.js"></script>
<style type="text/css">
/* No style rules here yet */
</style>
</head>
<body>
<script type="text/javascript">
//Width and height
var w = 500;
var h = 100;
var dataset; // a global
d3.json("Unit2Vec_tSNE.json", function(error, json) {
if (error) return console.warn(error);
dataset = json;
visualize();
});
function visualize() {
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
console.log(dataset); //work at here
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
console.log('Here!!!!!!!!!'); //not work at here
return d.pos[0];
})
.attr("cy", function(d) {
return d.pos[1];
})
.attr("r", 1);
}
</script>
</body>
</html>
The points can't be seen and console.log('Here!!!!!!!!!'); doesn't run.
Why? How to fix it? Thanks.
I am a new man to D3.js. Because I want to use it do an interactive project for my AI experiment, so display this points (in real application, there are 450000 points) is needed.
It is because dataset is an object and not array.
Quoting the d3 API :
selection.data([data[, key]]) Joins the specified array of data with the selected elements
So, if you change the structure of your JSON accordingly, you will see that your console.log is correctly executed.
You will have to tweak your code though to make it compatible in order to display the circles.
Demo with a correct format for the dataset variable:
var dataset = [
{"No 0": {
"dur": 135,
"name": "00000001_0",
"lab": "sil",
"pos": [
17.64800262451172,
-1.794445514678955
]
}},
{"No 1": {
"dur": 28,
"name": "00000001_1",
"lab": "uo",
"pos": [
-17.94196891784668,
-0.8764857649803162
]
}},
{"No 2": {
"dur": 21,
"name": "00000001_2",
"lab": "x",
"pos": [
2.7473323345184326,
13.970715522766113
]
}}
];
//Width and height
var w = 500;
var h = 100;
visualize();
function visualize()
{
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
console.log(dataset); //work at here
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
console.log('Here!!!!!!!!!'); //work at here now too
// return d.pos[0]; // edit this according to the new structure
})
.attr("cy", function(d) {
// return d.pos[1]; // edit this according to the new structure
})
.attr("r", 1);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Sankey Data formatting, presentation

I'm trying to implement the Sankey plugin with d3. I'm not getting the output I expected though. Hopefully someone can help.
I've reduced down to simplest possible dataset.
There are 3 nodes. I would expect one link from Entry to Exit, and one going from Entry to Zone1, then Exit. Here is the set:
const graph = {
nodes: [
{'node': 0, 'name': 'Entry'},
{'node': 1, 'name': 'Zone 1'},
{'node': 2, 'name': 'Exit'}
],
links: [
{"source": 0, "target": 1, "value": 2},
{"source": 1, "target": 2, "value": 2},
{"source": 0, "target": 2, "value": 4}
]
}
However I'm just getting one link pathway, and also it looks kind of mashed up
Here is the link code:
var link = svg.append("g").selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function (d) {
return Math.max(1, d.dy);
})
.sort(function (a, b) {
return b.dy - a.dy;
});
link.append("title")
.text(function (d) {
return d.source.name + " → " +
d.target.name + "\n" + format(d.value);
});
There was no explicit stroke color in the link code. I just needed to add:
.attr("stroke", "#CDCDCD")

jSon to D3js grouping data from nested arrays

I'm very new to doing anything with d3 and jSon. Here is a pice of data I'm trying to get out from json and I would just like to know if I'm even on the right path.
Basically each status group would have more servers inside than just one like at the moment and the idea would be to get rectangle graph for one server and list these nicely next to each other.
I've been reading a lot of tutorials and trying to browse for similiar kind of issues other people might've had, but so far had really no luck...
jSon data I'm trying to pull out
[
{
"status": "ok",
"servers":
[
{
"id": "VR01",
"servername": "Server_1",
"cpu": 45, "mem": 25,
"diskIO": 0, "bandwith": 200
}
]
},
{
"status": "attention",
"servers":
[
{
"id": "VR10",
"servername": "Server_10",
"cpu": 55, "mem": 35,
"diskIO": 1, "bandwith": 2000
}
]
},
{
"status": "warning",
"servers":
[
{
"id": "VR02",
"servername": "Server_02",
"cpu": 98, "mem": 85,
"diskIO": 1,
"bandwith": 2000
}
]
},
{
"status": "dead",
"servers":
[
{
"id": "VR20",
"servername": "Server_20",
"cpu": 0, "mem": 0,
"diskIO": 0,
"bandwith": 0
}
]
}
]
the D3 bit
<script>
var width = ("width", 1000);
var height = ("height", 800);
d3.json("mydata.json", function(data) {
var canvas = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
var status = function sortData(data){
for (i = 0; i < d.length; i++) {
if(d.status ==="ok")
canvas.selectAll("rect")
.data(d.server)
.enter()
.append("rect")
.attr("x", 25)
.attr("y", function(d, i){return 25 * i;})
.attr("fill", "purple")
}
}
})
</script>
Really appreciate any suggestions you might have!
I think that it would be better to use nested selections to create your dashboard.
// Create one group for each server group
var serverGroup = svg.selectAll('g')
.data(data)
.enter()
.append('g')
.attr('transform', function(d, i) { return 'translate(0, ' + 50 * i + ')');
// Create the inner elements for each group
var servers = serverGroup.selectAll('rect')
.data(function(d) { return d.servers; })
.enter()
.append('rect')
// ... more settings here ...
This will create three groups, one for each group of servers and translate each one vertically. Each group contains the group data, so we can use the group data to create elements inside each group. Also, you can add a title, background color and other settings for each group using this structure. This article contains the concepts that you need to work on your problem: How Selections Work. Regards,

simple multi line graph with d3js

I have the following array
var data = [
[{"time": 1, "value": 2.1}, {"time": 2, "value": 1.1}],{"time": 3, "value": 3.1}],
[{"time": 1, "value": 5.3}, {"time": 2, "value": 0.1}, {"time": 3, "value": 6.1}]
];
and I need to find the maximum time and value out of the entire array. the code that doesn't quite work is
var x = d3.scale.linear()
.domain([1, d3.max(data, function(d,i){ return d[i].time;})])
.range([0, width]);
for some reason I get a maximum time of 2, and not 3. even if I use a larger dataset with more point I still don't get the actual maximum value.
Any help is appreciated.
Your data is an array or arrays. If you want the "max of the maxes", you'll need to account for the nesting. One way to do it:
.domain([1, d3.max(data, function(arrayOfDs, i) {
return d3.max(arrayOfDs, function(d, i) { return d.time; });
})])

Configure fixed-layout static graph in d3.js

I have a working code example (only the <script type="text/javascript"> part) of a static graph using d3.js as below:
/* Create graph data */
var nodes = [];
for (var i = 0; i < 13; i++)
{
var datum = {
"value": i
};
nodes.push(datum);
}
var links = [{"source": 0, "target": 1},
{"source": 1, "target": 2},
{"source": 2, "target": 0},
{"source": 1, "target": 3},
{"source": 3, "target": 2},
{"source": 3, "target": 4},
{"source": 4, "target": 5},
{"source": 5, "target": 6},
{"source": 5, "target": 7},
{"source": 6, "target": 7},
{"source": 6, "target": 8},
{"source": 7, "target": 8},
{"source": 9, "target": 4},
{"source": 9, "target": 11},
{"source": 9, "target": 10},
{"source": 10, "target": 11},
{"source": 11, "target": 12},
{"source": 12, "target": 10}];
/* Create force graph */
var w = 800;
var h = 500;
var size = nodes.length;
nodes.forEach(function(d, i) { d.x = d.y = w / size * i});
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("weight", h);
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.linkDistance(200)
.size([w, h]);
setTimeout(function() {
var n = 400
force.start();
for (var i = n * n; i > 0; --i) force.tick();
force.stop();
svg.selectAll("line")
.data(links)
.enter().append("line")
.attr("class", "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; });
svg.append("svg:g")
.selectAll("circle")
.data(nodes)
.enter().append("svg:circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 15);
svg.append("svg:g")
.selectAll("text")
.data(nodes)
.enter().append("svg:text")
.attr("class", "label")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.attr("text-anchor", "middle")
.attr("y", ".3em")
.text(function(d) { return d.value; });
}, 10);
and it produces this rather scrambled layout:
While it is technically the correct graph, the ideal layout should be something like this (ignoring the different visual graphics):
Note that the layout should be fixed so that reloading the page does not change the positioning of each node; the layout should also be static, in that there is no animation effect and the nodes are not draggable. Both requirements are already achieved in the script above.
So how should I further configure this d3 script to produce a layout shown in the second image?
First, increase the charge strength and reduce the link distance. Doing so places a greater emphasis on global structure rather than local connections. Also, if you increase the charge strength enough, the repulsive charge will push even directly-connected nodes farther apart, thus effectively increasing the link distance while giving better overall structure. (The downside of a stronger charge force is that graph initialization is more chaotic, but this shouldn’t be a problem for static layouts.)
Second, you may need to increase the number of iterations or add custom forces to get better results. Force layouts often work well on arbitrary graphs, but there’s no guarantee that they will produce an optimal (or even good) result. For any graph where you can make simplifying assumptions (for example, trees), there may be additional forces or constraints that you can apply to encourage the simulation to converge onto a better solution.

Categories