I have an example of force directed graph. And i want to show arrowheads, but no matter what i tried i can't see them.
Here is my javascript
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {
name: link.source
});
link.target = nodes[link.target] || (nodes[link.target] = {
name: link.target
});
});
var width = 960,
height = 500;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(100)
.charge(-300)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("svg:defs").selectAll("marker")
.data(["arrow"])
.enter().append("svg:marker")
.attr("id", "arrow")
.attr("viewBox","0 0 10 10")
.attr("refX","20")
.attr("refY","5")
.attr("markerUnits","strokeWidth")
.attr("markerWidth","9")
.attr("markerHeight","5")
.attr("orient","auto")
.append("svg:path")
.attr("d","M 0 0 L 10 5 L 0 10 z")
.attr("fill", "#f0f0f0");
var link = svg.append("svg:g").selectAll("line")
.data(force.links())
.enter().append("svg:line")
.attr("class", "link")
.attr("marker-mid", "url(#arrow)");
var node = svg.append("svg:g").selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", 8);
node.append("text")
.attr("x", -22)
.text(function(d) {
return d.name;
});
function tick() {
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;
});
link.attr("marker-mid", "url(#arrow)");
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
And here is my jsfiddle
From the MDN docu on marker-mid:
The marker-mid defines the arrowhead or polymarker that shall be drawn at every vertex other than the first and last vertex of the given element or basic shape.
So the marker will be inserted at every vertex, but the start and end.
A simple line segment, however, has no other vertices, but its start and end. Thus there are no markers shown in your code.
If you change your marker-mid to, e.g., marker-end, you will see the arrow heads (although right now, they are not that pretty): fiddle.
Another way, would be to change the line-elements to path elements and add a additional vertex in the mid. This, however, would require some more sufficient code.
Related
Question: Is it possible for me to make each node individually, and then use the force layout to connect them? If not, how would I go about pre-placing the nodes? And if so, can I get some help with the syntax, please?
Context: I am new to D3, and am trying to make a force-directed graph for only five nodes as part of the landing page for an academic project. I am using this example and this example, and sort of want to make a combination of the two by putting my nodes in the arrays.
For example, could I do something like:
var w = 1300;
var h = 10000;
//An area for svg elements
var svgArea = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
//All the node definitions
var nodeMain = svgArea.append("a")
.attr("height", 300)
.attr("width", 300)
.append("circle")
.attr("r", 300)
.attr("cx", 650)
.attr("cy", 700)
.attr("fill", "orange");
var nodeMedia = svgArea.append("a")
.attr("height", 200)
.attr("width", 200)
.append("circle")
.attr("r", 200)
.attr("cx", 250)
.attr("cy", 1150)
.attr("fill", "orange");
var nodeRef = svgArea.append("a")
.attr("height", 200)
.attr("width", 200)
.append("circle")
.attr("r", 200)
.attr("cx", 1050)
.attr("cy", 1150)
.attr("fill", "orange");
//Nodes for the visualization
var nodes = [nodeMain, nodeMedia, nodeRef];
//Connected using indices of the array
var edges = [{source: 1, target: 0}, {source: 2, target: 0}];
//Force-directed
var connect = d3.layout.force()
.size([w, h])
.gravity(1)
.distance(100)
.charge(-50);
connect.nodes(nodes).links(edges);
var orb = svgArea.selectAll(".node").data(nodes)
.enter().append("g")
.call(force.drag);
var link = svgArea.selectAll(".link").data(edges)
.enter()
.append("line")
.attr("class", "link");
connect.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.source.x})
.attr("y2", function(d) {return d.source.y});
orb.attr("transform", function(d){ return "translate(" + d.x + "," + d.y + ")";});
});
connect.start();
(And in the event I asked a really silly question, would anyone mind directing me to some D3 resources where I can learn more of the concepts/syntax without emulating/relying purely on examples?)
Thank you in advance, everyone!
I have made slight changes in your code snippet and added necessary comments. Share your queries if any.
var w = 500;
var h = 500;
//An area for svg elements
var svgArea = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
//Nodes for the visualization
var nodes = [{
name: "Main",
x: 80,
y: 10
}, {
name: "Media",
x: 15,
y: 40
}, {
name: "Reference",
x: 60,
y: 60
}];
//Connected using indices of the array
var edges = [{
source: 1,
target: 0
}, {
source: 2,
target: 0
}];
//Force-directed
var connect = d3.layout.force()
.size([w, h])
.gravity(1)
.distance(150)
.charge(-200);
connect
.nodes(nodes)
.links(edges);
//Creating links
var link = svgArea.selectAll(".link")
.data(edges)
.enter()
.append("line")
.attr("class", "link")
.style("stroke", "black");
//Creating nodes
var orb = svgArea.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node");
orb.append("circle")
.attr("r", 10)
.style("fill", "orange");
//Adding Labels
orb.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.name
});
//Adding images
orb.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", -10)
.attr("y", -10)
.attr("width", 20)
.attr("height", 20);
orb.on("click",function(d){
alert("clicked "+d.name);
});
connect.on("tick", function() {
//Updating the link positions during force simulation.
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
});
//Updating the node position during force simulation
orb.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
connect.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
D3 has a rich documentation which is available here: GitHub
Fiddle: https://jsfiddle.net/gilsha/kv05y1hq/
If it's of any help I have the following Plunkers...
http://plnkr.co/edit/TiKKmvydqXNipe103juL?p=preview
http://plnkr.co/edit/ZSmvH05nnAD6cYZb0EM4?p=preview
The first one is to show/hide groups of elements when clicked on.
The second is to demonstrate drag/zoom.
Also the data is externalised into a json file and read in using...
d3.json("data.json", function(error, graph) {
This should enable you to reduce your node definitions down to one function.
I am trying to set up a d3 force visualization with nodes and links. I have my nodes displaying properly but am having some trouble with links. Could someone take a look at my json file and then my code and guide me through the process of getting the links to display?
Here's the json data (sources and targets for links are at the bottom):
https://api.myjson.com/bins/4t8na
And here's the code for the visualization:
<script type= "text/javascript">
var w = 1000,
h = 650;
var svg = d3.select("body").append("svg")
.attr("height", 0)
.attr("width", 0)
.style("border", "1px solid black");
var data; // a global
var force = d3.layout.force()
.size([w, h])
.linkDistance([150])
.charge([-1050])
.gravity(0.5)
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
var circles = svg.selectAll(".node");
d3.json("https://api.myjson.com/bins/1rnhq", function(error, json) {
if (error) return console.warn(error);
data = json;
var nodes = data;
console.log(data);
force.nodes(data)//.links()
.start();
// Update nodes.
circles = circles.data(data);
circles.exit().remove();
var nodeEnter = circles.enter().append("g")
.attr("class", "node")
.style("fill", "#000")
.style("opacity", 0.75)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", click)
.call(force.drag);
nodeEnter.append("circle")
.attr("r", function(d) { return d.sector == "Academia" ? 1:5 });
nodeEnter.attr("cursor", "pointer");
//Update links
var links = svg.selectAll(".link")
.data(data.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", "1px");
links.exit().remove();
function mouseover() {
d3.select(this).select("circle").transition()
.duration(250)
.attr('r', 10);
}
function mouseout() {
d3.select(this).select("circle").transition()
.duration(250)
.attr('r', 5);
}
nodeEnter.append("text")
.attr("text-anchor", "middle")
.style("font-size", ".75em")
.attr("dy", "-0.85em").text(function (d) { return d.name });
var tooltip = svg.append("rect")
.attr("x", 1000)
.attr("y", 0)
.attr("width", 900)
.attr("height", 700)
.attr("opacity", 0.85);
function click() {
d3.select(tooltip).transition()
.duration(450)
.attr("x", 650)
};
});
function tick() {
links.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; });
circles.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
};
// create svg nodes for each json object: "sector"
// create svg nodes for each json object: "name"
// load links.json
// create svg links from links json file
// style links
// sort json objects by projects
// get google map: coastal virginia
// sort json objects: "name" by geography
// get googe map U.S.
</script>
The main problem is inside your JSON links
You have values like this:
{"source":52,"target":28},{"source":52,"target":29},{"source":52,"target":30},{"source":52,"target":31}
But there is no Node with index 52 thus everything was breaking on load.
However you code has lot of other errors like
circles.exit().remove();//this is incorrect coz circles in your case is not a selection
And many more :)
Working code here
Hope this helps!
I believe the problem is that data.links does not exist, what you have is data[#].links. So when you do .data(data.links) on your var links = ..., you are passing an undefined attribute there.
Try this:
var links = svg.selectAll(".link")
.data(data)
// ...
I am visualising a graph of relationships between people with d3. All nodes are connected to a single central node, and then have relationships with other nodes. I've got the basics working, but I'm struggling to work out how to set the parameters like linkDistance, linkStrength, gravity and charge.
Each edge has a rating from 0-5, which I'm then using to compute linkDistance using an inverse linear scale. The main problem is getting the relationships to be represented properly. The central node seems to be much further away than any other node, even though it has the shortest linkDistance with some other nodes. I'm also finding it difficult to find the right settings to get nodes to be an appropriate distance apart.
var h = 500, w = 1000
var color = d3.scale.category20()
var svg = d3.select("body")
.append("svg")
.attr({ height: h, width: w })
queue()
.defer(d3.json, "nodes.json")
.defer(d3.json, "links.json")
.await(makeDiag);
function makeDiag(error, nodes, links, table) {
links = links.filter(function(link) {
if (link.value) return true
})
var scale = d3.scale.linear().domain([0,5]).range([20,0])
var edges = svg.selectAll("line")
.data(links)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1)
/* Establish the dynamic force behavor of the nodes */
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([w,h])
.linkDistance(function(d) {
if (d.value == 0) return null
console.log('in',d.value,'out',scale(d.value))
return scale(d.value)
})
.charge(-1400)
.start();
/* Draw the edges/links between the nodes */
var texts = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.attr("fill", "black")
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.text(function(d) { return d.name; });
var nodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", function(d,i) { return 20 })
.attr("opacity", 0.7)
.style("fill", function(d,i) { return color(i); })
.call(force.drag);
/* Draw the nodes themselves */
/* Run the Force effect */
force.on("tick", function() {
edges.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; });
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
texts.attr("transform", function(d) {
return "translate(" + (d.x - 12.5) + "," + (d.y + 5) + ")";
});
});
};
jsFiddle
full screen result
I'm not an expert in d3 (as many of you know) however I am attempting to modify a working force network so that the nodes are fixed. So far I have been able to get the nodes to work but haven't been able to get the lines between them to work.
I've created a fiddle at http://jsfiddle.net/PatriciaW/Dnnve/
Any help will be very welcome.
var width = 960,
height = 700,
n = 100;
var categoryColour = {
"community group": "red",
"volunteer": "blue",
"organization": "green",
"air": "transparent"
};
var json2 = {"nodes":[{"node":{"name":"TCAN","x2":1,"y2":2,"description":"A network of organizations in Toronto devoted to climate change mitigation and adaptation.","category":"organization","size":3,"URL":"http:\/\/localhost:8888\/mydrupal\/groups\/tcan"}},{"node":{"name":"Rita ","x2":5,"y2":3,"description":"Rita is devoted to mitigating climate change and participates in many organizations.","category":"volunteer","size":2,"URL":"http:\/\/localhost:8888\/mydrupal\/groups\/rita"}},{"node":{"name":"Green 13","x2":5,"y2":4,"description":"Green 13","category":"community group","size":2,"URL":"http:\/\/localhost:8888\/mydrupal\/groups\/green-13"}},{"node":{"name":"ZCO","x2":3,"y2":1,"description":"Zero Carbon Ontario","category":"organization","size":2,"URL":"http:\/\/localhost:8888\/mydrupal\/groups\/zco"}},{"node":{"name":"Resilient Toronto","x2":3,"y2":5,"description":"","category":"organization","size":3,"URL":"http:\/\/localhost:8888\/mydrupal\/groups\/resilient-toronto"}},{"node":{"name":"City of Toronto","x2":3,"y2":3,"description":"","category":"organization","size":5,"URL":"http:\/\/localhost:8888\/mydrupal\/groups\/city-toronto"}}]};
var nodes=json2.nodes.map(function(json2) {
return json2.node;
});
var i = 0;
while (i < nodes.length) {
nodes[i].fixed=true;
nodes[i].x = (nodes[i].x2)*100;
nodes[i].y = (nodes[i].y2)*100;
i = i+ 1;
}
var json = {"connections":[{"connection":{"source":"Rita","target":"Resilient Toronto"}},{"connection":{"source":"TCAN","target":"Resilient Toronto"}},{"connection":{"source":"Resilient Toronto","target":"City of Toronto"}},{"connection":{"source":"Rita","target":"ZCO"}},{"connection":{"source":"Rita","target":"Green 13"}},{"connection":{"source":"Green 13","target":"TCAN"}},{"connection":{"source":"ZCO","target":"TCAN"}}]};
var links=json.connections.map(function(json) {
return json.connection;
});
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
svg = d3.select("#network")
.append("svg")
.attr("width", width)
.attr("height", height);
var link = svg.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle") // this is OK if use nodes or links below but defaults to links
.attr("r", 8)
.style("fill", function(nodes) {
return categoryColour [nodes.category];
})
node.append("text") // OK
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.name; });
function tick() {
link
.attr("x1", function(d) {
// console.log("d"); console.log(d); has source and target but not .x and y values?
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 + ")";
});
};
// Use a timeout to allow the rest of the page to load first.
setTimeout(function() {
force.tick();
console.log("timeout nodes "); console.log(nodes); //
svg.selectAll("line")
.data(links)
.enter().append("line")
.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.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 4.5);
}, 10);
I'm not an expert either but I think you are having issues because the source and target values in your connections should be referencing the respective positional array index for the node (not the name of the node).
A simple example, in your fiddle just change the first connection from...
{"connection":{"source":"Rita","target":"Resilient Toronto"}}
to...
{"connection":{"source":1,"target":4}}
1 and 4 being the index of the elements in the nodes array. Hope this is what you are looking for.
I updated the jsfiddle with the working code. Needs improvement but demonstrates the principle.
I have implemented the following graph with the edges rendered with d3.svg.diagonal(). However, when I try substituting the diagonal with d3.svg.line(), it doesn't appear to pull the target and source data. What am I missing? Is there something I don't understand about d3.svg.line?
The following is the code I am referring to, followed by the full code:
var line = d3.svg.line()
.x(function(d) { return d.lx; })
.y(function(d) { return d.ly; });
...
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",d3.svg.diagonal())
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
The entire code:
var margin = {top: 20, right: 20, bottom: 20, left: 20},
width =1500,
height = 1500,
diameter = Math.min(width, height),
radius = diameter / 2;
var balloon = d3.layout.balloon()
.size([width, height])
.value(function(d) { return d.size; })
.gap(50)
var line = d3.svg.line()
.x(function(d) { return d.lx; })
.y(function(d) { return d.ly; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + (margin.left + radius) + "," + (margin.top + radius) + ")")
root = "flare.json";
root.y0 = height / 2;
root.x0 = width / 2;
d3.json("flare.json", function(root) {
var nodes = balloon.nodes(root),
links = balloon.links(nodes);
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",d3.svg.diagonal())
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
var node = svg.selectAll("g.node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node");
node.append("circle")
.attr("r", function(d) { return d.r; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
node.append("text")
.attr("dx", function(d) { return d.x })
.attr("dy", function(d) { return d.y })
.attr("font-size", "5px")
.attr("fill", "white")
.style("text-anchor", function(d) { return d.children ? "middle" : "middle"; })
.text(function(d) { return d.name; })
});
A comparison of how the d attribute of the svg disappears when using "line."
Question is quite dated, but since I don't see an answer and someone might face the same problem, here it is.
The reason why simple replacement of diagonal with line is not working is because d3.svg.line and d3.svg.diagonal return different results:
d3.svg.diagonal returns function that accepts datum and its index and transforms it to path using projection. In other words diagonal.projection determines how the function will get points' coordinates from supplied datum.
d3.svg.line returns function that accepts an array of points of the line and transforms it to path. Methods line.x and line.y determine how coordinates of the point retreived from the single element of supplied array
D3 SVG-Shapes reference
SVG Paths and D3.js
So you can not use result of the d3.svg.line directly in d3 selections (at least when you want to draw multiple lines).
You need to wrap it in another function like this:
var line = d3.svg.line()
.x( function(point) { return point.lx; })
.y( function(point) { return point.ly; });
function lineData(d){
// i'm assuming here that supplied datum
// is a link between 'source' and 'target'
var points = [
{lx: d.source.x, ly: d.source.y},
{lx: d.target.x, ly: d.target.y}
];
return line(points);
}
// usage:
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",lineData)
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
Here's working version of jsFiddle mobeets posted: jsFiddle
I had the same problem...There's a jsFiddle here.
Note that changing line to diagonal will make it work.
Perhaps encapsulating the diagonal function and editing its parameters could work for you:
var diagonal = d3.svg.diagonal();
var new_diagonal = function (obj, a, b) {
//Here you may change the reference a bit.
var nobj = {
source : {
x: obj.source.x,
y: obj.source.y
},
target : {
x: obj.target.x,
y: obj.target.y
}
}
return diagonal.apply(this, [nobj, a, b]);
}
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",new_diagonal)
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
Just set the d attribute of link to line:
.attr("d", line)