How to draw the force chart in d3 v4? - javascript

I'm new to d3 and when I learnt how to draw a force chart, I had some problems about it. And at first, let we see my code here:
<html>
<head>
<meta charset="UTF-8">
<title>the force chart</title>
</head>
 
<body>
<script src="http://d3js.org/d3.v4.min.js"></script>
    <script>
var width = 400;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
var nodes = [ { "id": "English" },{ "id": "Italy" },
{ "id": "America" },{ "id": "Canada" },
{ "id": "Australia" },{ "id": "Japan" },
{ "id": "China" } ];
var edges = [ { "source": 0 , "target": 1 } , { "source": 0 , "target": 2 },
{ "source": 0 , "target": 3 } , { "source": 1 , "target": 4 },
{ "source": 1 , "target": 5 } , { "source": 1 , "target": 6 }, ];
var force = d3.forceSimulation(nodes)
.force("link",d3.forceLink()
.id(function(d){return d.id})
.distance(function(d){return 150}).strength([-400]))
.force("charge",d3.forceManyBody())
.force("center",d3.forceCenter(width , height));
force.restart(); //start
var svg_edges = svg.selectAll("line")
.data(edges)
.enter()
.append("line")
.style("stroke","#ccc")
.style("stroke-width",1);
var color = d3.scaleOrdinal(d3.schemeCategory20);
//add nodes
var svg_nodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("r",20)
.style("fill",function(d,i){
return color(i);
})
.call(d3.drag()); //to drag the nodes
//add information
var svg_texts = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.style("fill", "black")
.attr("dx", 20)
.attr("dy", 8)
.text(function(d){
return d.id;
});
force.on("tick", function(){ //update the position of lines
svg_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; })
//update the position of nodes
svg_nodes.attr("cx",function(d){ return d.x; })
.attr("cy",function(d){ return d.y; });
//update the position of information
svg_texts.attr("x",function(d){ return d.x; })
.attr("y",function(d){ return d.y; });
});
    </script>    
</body>
</html>
I want to draw a picture like this:
But my code can only show one node, just like this:
So I feel confused, because there is no error in Developer Tools. As I layout the force, I infer to https://github.com/d3/d3-force/blob/master/README.md#links . So I solve the problem which is result from the different versions. But why it still doesn't work? Could you help me? I'm very appreciate it if you help me! Thank you!

Besides the points already explained by #Vinod:
.append("circle")
and
.force("center", d3.forceCenter(width/2, height/2));
You have a trailing comma. But the most important is this, to show the edges:
First, add the edges to the simulation:
force.force("link")
.links(edges);
And then, change the links id. Right now, there is no property called id. So, it should be:
.force("link", d3.forceLink()
.id(function(d,i) {
return i
})
//the rest of the function
Here is a demo:
var width = 400;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var nodes = [{
"id": "English"
}, {
"id": "Italy"
}, {
"id": "America"
}, {
"id": "Canada"
}, {
"id": "Australia"
}, {
"id": "Japan"
}, {
"id": "China"
}];
var edges = [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}, {
"source": 0,
"target": 3
}, {
"source": 1,
"target": 4
}, {
"source": 1,
"target": 5
}, {
"source": 1,
"target": 6
}];
var force = d3.forceSimulation()
.force("link", d3.forceLink()
.id(function(d,i) {
return i
})
.distance(function(d) {
return 150
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width/2, height/2));
force.restart(); //start
var svg_edges = svg.selectAll("line")
.data(edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
var color = d3.scaleOrdinal(d3.schemeCategory20);
//add nodes
var svg_nodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", 20)
.style("fill", function(d, i) {
return color(i);
})
.call(d3.drag()); //to drag the nodes
//add information
var svg_texts = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.style("fill", "black")
.attr("dx", 20)
.attr("dy", 8)
.text(function(d) {
return d.id;
});
force.nodes(nodes);
force.force("link")
.links(edges);
force.on("tick", function() { //update the position of lines
svg_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;
})
//update the position of nodes
svg_nodes.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
//update the position of information
svg_texts.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
});
<script src="https://d3js.org/d3.v4.min.js"></script>

There are several mistakes in your code
firstly you need to append circle like this
var svg_nodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r",20)
.style("fill",function(d,i){
return color(i);
})
.call(d3.drag()); //to drag the nodes
where as your code append r which is no SVG tag moreover the center of the graph should not be width and height it should be width/2 and height/2 to make it at center
See this fiddle http://jsfiddle.net/Qh9X5/9515/
similarly for lines you are using edges data which don't have the x,y values you need to pass the xy value for drawing lines
For a complete solution note in v3.3 See this https://jsfiddle.net/3uehrfj8/1/ with all nodes and edges

Related

D3v6 nested graph - nested join()?

I want to visualize the "children" insight each node. I guess the D3v6 .join() function can be nested. Unfortunately I can´t find any example. The snippet below contains an outerGraph with 3 nodes and children as attribute. So far those children aren´t used yet.
The innerGraph instead visualize the small nodes which will be obsolete as soon as the children approach is working. Another Idea would be to work with those two graphs and create a gravity / cluster, which will be the parent.
Goal: Either utilize the children attribute or combine both graphs with the help of an cluster /gravity or even nested join(). I am appreciating any hint / tip. The visuals result should be:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3v6 nested nodes</title>
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>
<style>
body {
background-color: whitesmoke;
}
</style>
<body>
<script>
var svg = d3.select("body").append("svg")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight);
var width = window.innerWidth
var height = window.innerHeight
var outerLinkContainer = svg.append("g").attr("class", "outerLinkContainer")
var outerNodeContainer = svg.append("g").attr("class", "outerNodeContainer")
var innerLinkContainer = svg.append("g").attr("class", "innerLinkContainer")
var innerNodeContainer = svg.append("g").attr("class", "innerNodeContainer")
//###############################################
//############# outer force Layouts #############
//###############################################
var outerGraph = {
"nodes": [
{
"id": "A",
"children": [
{
"id": "A1"
},
{
"id": "A2"
}
]
},
{
"id": "B",
"children": [
{
"id": "B1"
},
{
"id": "B2"
}
]
},
{
"id": "C",
"children": [
{
"id": "C1"
},
{
"id": "C2"
}
]
}
],
"links": [
{
"source": "A",
"target": "B"
},
{
"source": "B",
"target": "C"
},
{
"source": "C",
"target": "A"
},
]
}
var outerLayout = d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-500))
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
var outerLinks = outerLinkContainer.selectAll(".link")
.data(outerGraph.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
.style("opacity", 0.2)
var outerNodes = outerNodeContainer.selectAll("g.outer")
.data(outerGraph.nodes, function (d) { return d.id; })
.join("circle")
.attr("class", "outer")
.style("fill", "pink")
.style("stroke", "blue")
.attr("r", 40)
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
outerLayout
.nodes(outerGraph.nodes)
.on("tick", ticked)
outerLayout
.force("link")
.links(outerGraph.links)
//###############################################
//############## inner force Layout #############
//###############################################
var innerGraph = {
"nodes": [
{ "id": "A1" },
{ "id": "A2" }
],
"links": [
{
"source": "A1",
"target": "A2"
}
]
}
var innerlayout = d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-500))
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
var innerLinks = innerLinkContainer.selectAll(".link")
.data(innerGraph.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
var innerNodes = innerNodeContainer.selectAll("g.inner")
.data(innerGraph.nodes, function (d) { return d.id; })
.join("circle")
.style("fill", "orange")
.style("stroke", "blue")
.attr("r", 6)
.attr("class", "inner")
.attr("id", function (d) { return d.id; })
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
innerlayout
.nodes(innerGraph.nodes)
.on("tick", ticked)
innerlayout
.force("link")
.links(innerGraph.links)
//###############################################
//################## functons ###################
//###############################################
function ticked() {
outerLinks
.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;
});
innerLinks
.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;
});
outerNodes.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
innerNodes.attr("transform", function (d) {
return "translate(" + (d.x) + "," + (d.y) + ")";
});
}
function dragStarted(event, d) {
if (!event.active)
outerLayout.alphaTarget(0.3).restart();
innerlayout.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active)
outerLayout.alphaTarget(0)
innerlayout.alphaTarget(0)
d.fx = undefined;
d.fy = undefined;
}
</script>
</body>
</html>
I will update the post as soon as I found a solution.
Here's a slightly hack way to do it - I am a bit disappointed in the outcome because if you play with the outerNodes then the links between innerNodes cross over in an unattractive way.
The changes I made in your code:
update innerGraph so nodes have a parent property (plus add the links required to match your screenshot in the question)
add an additional class on outerNodes so that each outer node can be identified e.g. .outer_A, .outer_B etc
add an additional class on innerNodes so that each inner node can be identified e.g. .child_A1, .child_A2 etc
in ticked - for innerNodes return a point for the inner node so that it is sitting inside centre of it's parent at roughly 20px from the centre on the vector between the original force simulation selected point and the parent's centre.
in ticked - for innerLinks, force the source and target coordinates to update per the previous step
Those last two points are per here and here.
So it works - but only just. Vertical scrolling in the stack snippet seems to upset it a bit but it's maybe better if you try it out on your own dev environment. I still think you could you look at other tools - maybe this one from cytoscape.js and also the webcola example I mentioned in the comments?
var svg = d3.select("body").append("svg")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight);
var width = window.innerWidth
var height = window.innerHeight
var outerLinkContainer = svg.append("g").attr("class", "outerLinkContainer")
var outerNodeContainer = svg.append("g").attr("class", "outerNodeContainer")
var innerLinkContainer = svg.append("g").attr("class", "innerLinkContainer")
var innerNodeContainer = svg.append("g").attr("class", "innerNodeContainer")
//###############################################
//############# outer force Layouts #############
//###############################################
var outerGraph = {
"nodes": [
{
"id": "A",
"children": [
{
"id": "A1"
},
{
"id": "A2"
}
]
},
{
"id": "B",
"children": [
{
"id": "B1"
},
{
"id": "B2"
}
]
},
{
"id": "C",
"children": [
{
"id": "C1"
},
{
"id": "C2"
}
]
}
],
"links": [
{
"source": "A",
"target": "B"
},
{
"source": "B",
"target": "C"
},
{
"source": "C",
"target": "A"
},
]
}
var outerLayout = d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-500))
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
var outerLinks = outerLinkContainer.selectAll(".link")
.data(outerGraph.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
.style("opacity", 0.2)
var outerNodes = outerNodeContainer.selectAll("g.outer")
.data(outerGraph.nodes, function (d) { return d.id; })
.join("circle")
.attr("class", d => `outer outer_${d.id}`)
.style("fill", "pink")
.style("stroke", "blue")
.attr("r", 40)
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
outerLayout
.nodes(outerGraph.nodes)
.on("tick", ticked)
outerLayout
.force("link")
.links(outerGraph.links)
//###############################################
//############## inner force Layout #############
//###############################################
var innerGraph = {
"nodes": [
{ "id": "A1", "parent": "A" },
{ "id": "A2", "parent": "A" },
{ "id": "B1", "parent": "B" },
{ "id": "B2", "parent": "B" },
{ "id": "C1", "parent": "C" },
{ "id": "C2", "parent": "C" }
],
"links": [
{
"source": "A1",
"target": "A2"
},
{
"source": "A2",
"target": "B2"
},
{
"source": "A1",
"target": "C2"
},
{
"source": "B1",
"target": "B2"
},
{
"source": "B1",
"target": "C1"
},
{
"source": "C2",
"target": "C1"
}
]
}
var innerlayout = d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-500))
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
var innerLinks = innerLinkContainer.selectAll(".link")
.data(innerGraph.links)
.join("line")
.attr("class", "link linkChild")
.style("stroke", "black")
var innerNodes = innerNodeContainer.selectAll("g.inner")
.data(innerGraph.nodes, function (d) { return d.id; })
.join("circle")
.style("fill", "orange")
.style("stroke", "blue")
.attr("r", 6)
.attr("class", d => `inner child_${d.id}`)
.attr("id", function (d) { return d.id; })
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
innerlayout
.nodes(innerGraph.nodes)
.on("tick", ticked)
innerlayout
.force("link")
.links(innerGraph.links)
//###############################################
//################## functons ###################
//###############################################
function ticked() {
outerLinks
.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;
});
outerNodes.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
innerNodes.attr("transform", function (d) {
var parent = d3.select(`.outer_${d.parent}`);
var pr = parent.node().getBoundingClientRect();
var prx = pr.left + (pr.width / 2);
var pry = pr.top + (pr.height / 2);
var distance = Math.sqrt( ((d.x - prx) ** 2) + ((d.y - pry) ** 2 ));
var ratio = 20 / distance;
var childX = ((1 - ratio) * prx) + (ratio * d.x);
var childY = ((1 - ratio) * pry) + (ratio * d.y);
return "translate(" + (childX) + "," + (childY) + ")";
});
innerLinks.attr("x1", d => {
var m1 = d3.select(`.child_${d.source.id}`).node().transform.baseVal[0].matrix;
return m1.e;
}).attr("y1", d => {
var m1 = d3.select(`.child_${d.source.id}`).node().transform.baseVal[0].matrix;
return m1.f;
}).attr("x2", d => {
var m2 = d3.select(`.child_${d.target.id}`).node().transform.baseVal[0].matrix;
return m2.e;
}).attr("y2", d => {
var m2 = d3.select(`.child_${d.target.id}`).node().transform.baseVal[0].matrix;
return m2.f;
});
}
function dragStarted(event, d) {
if (!event.active)
outerLayout.alphaTarget(0.3).restart();
innerlayout.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active)
outerLayout.alphaTarget(0)
innerlayout.alphaTarget(0)
d.fx = undefined;
d.fy = undefined;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.5.0/d3.min.js"></script>

How to avoid curved links in a d3 radial tree diagram?

I am working with this code to get a radial tree diagram for my data. However, I'd like to modify it to avoid curved links. Instead I am interested in linear straight connections. The curved links make the illustration to be less sophisticated specially when we have lower number of children nodes. For instance, you may look at the parent node and its links with the nodes on the first layer (circle). How can I use straight lines for these connections?
This is the part of the code I would like to modify to satisfy my needs:
var link = g.selectAll(".link")
.data(root.links())
.enter().append("path")
.attr("class", "link")
.attr("d", d3.linkRadial()
.angle(function(d) { return d.x; })
.radius(function(d) { return d.y; }));
where function is currently defined as
function radialPoint(x, y) {
return [(y = +y) * Math.cos(x -= Math.PI / 2), y * Math.sin(x)];
}
Thanks.
To get linear straight connections, don't use a path generator - d3.linkRadial (or d3.linkHorizontal etc) - use a line:
var link = g.selectAll(".link")
.data(tree(root).links())
.enter().append("line")
.attr("class", "link")
.attr("stroke","#ccc")
.attr("x1", function(d) { return radialPoint(d.source.x,d.source.y)[0]; })
.attr("y1", function(d) { return radialPoint(d.source.x,d.source.y)[1]; })
.attr("x2", function(d) { return radialPoint(d.target.x,d.target.y)[0]; })
.attr("y2", function(d) { return radialPoint(d.target.x,d.target.y)[1]; }) ;
This will keep your links straight, the snippet below should demonstrate this.
var data = { "name": "Root", "children": [
{ "name": "A", "children": [ {"name": "A-1" }, {"name": "A-2" }, {"name":"A-3"}, {"name":"A-4"}, { "name":"A-5"} ] },
{ "name": "B", "children": [ {"name": "B-1" } ] },
{ "name": "C" },
{ "name": "D", "children": [ {"name": "D-1" }, {"name": "D-2" }, {"name": "D-3", "children": [ {"name": "D-3-i"}, {"name":"D-3-ii"} ] } ] },
{ "name": "E" },
{ "name": "F" }
] };
var width = 960;
var height = 500;
margin = {left: 100, top: 100, right: 50, bottom: 50}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g").attr('transform','translate('+ width/2 +','+ height/2 +')');
var root = d3.hierarchy(data);
var tree = d3.tree()
.size([2 * Math.PI, height/2]);
var link = g.selectAll(".link")
.data(tree(root).links())
.enter().append("line")
.attr("class", "link")
.attr("stroke","#ccc")
.attr("x1", function(d) { return radialPoint(d.source.x,d.source.y)[0]; })
.attr("y1", function(d) { return radialPoint(d.source.x,d.source.y)[1]; })
.attr("x2", function(d) { return radialPoint(d.target.x,d.target.y)[0]; })
.attr("y2", function(d) { return radialPoint(d.target.x,d.target.y)[1]; })
;
var node = g.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) { return "translate(" + radialPoint(d.x, d.y) + ")"; })
node.append("circle")
.attr("r", 2.5);
node.append("text")
.text(function(d) { return d.data.name; })
.attr('y',-10)
.attr('x',-10)
.attr('text-anchor','middle');
function radialPoint(x, y) {
return [(y = +y) * Math.cos(x -= Math.PI / 2), y * Math.sin(x)];
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>

arrays and nodes mapping in d3 force

lets say in d3 I have a couple data arrays like this:
Nodes:
var nodes = [
{"name": "abc", "type": "Db"},
{"name": "def", "type": "Db"},
{"name": "ghi", "type": "Db"},
{"name": "jkl", "type": "Db"}
]
Links:
var links = [
{source: nodes[0], target: nodes[1]},
{source: nodes[0], target: nodes[2]},
{source: nodes[1], target: nodes[3]}
]
is there a way that I can use names instead of the number? Like this:
var links = [
{source: nodes["abc"], target: nodes["def"]},
{source: nodes["abc"], target: nodes["ghi"]},
{source: nodes["def"], target: nodes["jkl"]}
]
but get this error:
Uncaught TypeError: Cannot read property 'x' of undefined
here:
link.attr("x1", function(d) { return d.source.x; })
here is the full code:
<!doctype html>
<html>
<head>
<title>D3 tutorial</title>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<script>
var w = 4000,
h = 4000;
var circleWidth = 5;
var fontFamily = 'Bree Serif',
fontSizeHighlight = '1.5em',
fontSizeNormal = '1em';
var palette = {
"lightgray": "#819090",
"gray": "#708284",
"mediumgray": "#536870",
"darkgray": "#475B62",
"darkblue": "#0A2933",
"darkerblue": "#042029",
"paleryellow": "#FCF4DC",
"paleyellow": "#EAE3CB",
"yellow": "#A57706",
"orange": "#BD3613",
"red": "#D11C24",
"pink": "#C61C6F",
"purple": "#595AB7",
"blue": "#2176C7",
"green": "#259286",
"yellowgreen": "#738A05"
}
var nodes = [
{"name": "abc", "type": "Db"},
{"name": "def", "type": "Db"},
{"name": "ghi", "type": "Db"},
{"name": "jkl", "type": "Db"}
]
var links = [
{source: nodes["abc"], target: nodes["def"]},
{source: nodes["abc"], target: nodes["ghi"]},
{source: nodes["def"], target: nodes["jkl"]}
]
var vis = d3.select("body")
.append("svg:svg")
.attr("class", "stage")
.attr("width", w)
.attr("height", h);
var force = d3.layout.force()
.nodes(nodes)
.links([])
.gravity(0.1)
.charge(-1000)
.size([w, h]);
var link = vis.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.attr("stroke", "#CCC")
.attr("fill", "none");
var node = vis.selectAll("circle.node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
//MOUSEOVER
.on("mouseover", function(d,i) {
if (i>0) {
//CIRCLE
d3.select(this).selectAll("circle")
.transition()
.duration(250)
.style("cursor", "none")
.attr("r", circleWidth+3)
.attr("fill",palette.black);
//TEXT
d3.select(this).select("text")
.transition()
.style("cursor", "none")
.duration(250)
.style("cursor", "none")
.attr("font-size","1.5em")
.attr("x", 15 )
.attr("y", 5 )
.text(function(d) { return d.name + "_" + d.type; })
} else {
//CIRCLE
d3.select(this).selectAll("circle")
.style("cursor", "none")
//TEXT
d3.select(this).select("text")
.style("cursor", "none")
}
})
//MOUSEOUT
.on("mouseout", function(d,i) {
if (i>0) {
//CIRCLE
d3.select(this).selectAll("circle")
.transition()
.duration(250)
.attr("r", circleWidth)
.attr("fill",palette.pink);
//TEXT
d3.select(this).select("text")
.transition()
.duration(250)
.attr("font-size","1em")
.attr("x", 8 )
.attr("y", 4 )
.text(function(d) { return d.name; })
}
})
.call(force.drag);
//CIRCLE
node.append("svg:circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", circleWidth)
.attr("fill", function(d, i) { if (i>0) { return palette.pink; } else { return palette.black } } )
//TEXT
node.append("text")
.text(function(d, i) { return d.name; })
.attr("x", function(d, i) { if (i>0) { return circleWidth + 5; } else { return -10 } })
.attr("y", function(d, i) { if (i>0) { return circleWidth + 0 } else { return 8 } })
.attr("font-family", "Bree Serif")
.attr("fill", function(d, i) { if (i>0) { return palette.black; } else { return palette.black } })
.attr("font-size", function(d, i) { if (i>0) { return "1em"; } else { return "1.8em" } })
.attr("text-anchor", function(d, i) { if (i>0) { return "beginning"; } else { return "end" } })
force.on("tick", function(e) {
node.attr("transform", function(d, i) {
return "translate(" + d.x + "," + d.y + ")";
});
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; })
});
force.start();
</script>
</body>
</html>
Make node an object, with each name as a property/key:
var nodes = {
abc: {"name": "abc", "type": "Db"},
def: {"name": "def", "type": "Db"},
ghi: {"name": "ghi", "type": "Db"},
jkl: {"name": "jkl", "type": "Db"}
}
Arrays can only be accessed by index (ie. array[3]), objects can be accessed by property (ie. object["propertyName"] or object.propertyName).
It's returning undefined because that number inside the square bracket is the index of the object (in this particular case) inside an array. When you use nodes[0] you are referencing the first object inside the array nodes (the index is zero-based, that is, 0 is the first object, 1 is the second and so on).
If you want to reference the object which value for the key name is "abc" you'll have to find it first. Please see this answer:
How to get index of object by its property in javascript

Grouping Circles and Path as a node in D3 Force Layout?

I'm trying to group the circles in the force directed graph layout, so that i could add the path of semi-circle that would make the circle into two semi-circle clickable sides. I managed to make two sides semi-circle clickable with d3 works just like this fiddle. I seems can't wrap d3 concept how to group, and connect the links.
How can i group the circles and the path and make it works with force layout? What are the concepts that i failed to understand to make this work? What did i do wrong?
Two sides clickable Semi-circle code
var vis = d3.select("body").append("svg")
var pi = Math.PI;
var arc = d3.svg.arc()
.innerRadius(0)
.outerRadius(70)
.startAngle(0) //converting from degs to radians
.endAngle(pi) //just radians
var canvas = vis.attr("width", "400").attr("height", "400").append("g");
// Added height and width so arc is visible
canvas.append("circle")
.attr("r", "70px")
.attr("fill","blue")
.attr("cx",400/2)
.attr("cy",400/2)
.on("click", function(d){
console.log("View");
})
.on("mouseover", function(){ d3.select(this).style("fill", "blue");
console.log(d3.select(this.parentNode).select(".view").style("visibility","visible"));
})
.on("mouseout", function(){
d3.select(this).style("fill", "blue");
console.log(d3.select(this.parentNode).select(".view").style("visibility","hidden"));
});
canvas.append("path")
.attr("d", arc)
.attr("fill", "blue")
.attr("transform", "translate(200,200)")
.on("click",function(d){
console.log("Expand");
})
.on("mouseover", function(){ d3.select(this).style("fill", "blue");
console.log(d3.select(this.parentNode).select(".expand").style("visibility","visible"));
})
.on("mouseout", function(){
d3.select(this).style("fill", "blue");
console.log(d3.select(this.parentNode).select(".expand").style("visibility","hidden"));
});
canvas.append("text")
.attr("dx", "190")
.attr("dy","200")
.attr("class","view")
.text("VIEW")
canvas.append("text")
.attr("dx", "190")
.attr("dy","200")
.attr("class","expand")
.text("Expand")
Force Layout that uses circle as node code, inspired by this stackoverflow question
This fiddle
var words = [{
"group": "n",
"word": "main node",
"children": [{
"group": "n",
"name": "sub node 1"
}, {
"group": "n",
"name": "sub node 2"
}, {
"group": "n",
"name": "sub node 3"
}, {
"group": "v",
"name": "sub node 4"
}, {
"group": "s",
"name": "sub node 5"
}, {
"group": "s",
"name": "sub node 6"
}, {
"group": "s",
"name": "sub node 7"
}, {
"group": "s",
"name": "sub node 8"
}, {
"group": "s",
"name": "sub node 9"
}, {
"group": "s",
"name": "sub node 10"
}, {
"group": "s",
"name": "sub node 11"
}, {
"group": "r",
"name": "sub node 12",
"children": [{
"group": "r",
"name": "sub sub node 1"
}, {
"group": "r",
"name": "sub sub node 2"
}, {
"group": "r",
"name": "sub sub node 3"
}]
}]
}]
var w = 600,
h = 600,
radius = 30,
node,
link,
root;
var pi = Math.PI;
var arc = d3.svg.arc()
.innerRadius(0)
.outerRadius(radius)
.startAngle(0) //converting from degs to radians
.endAngle(pi)
var force = d3.layout.force()
.on("tick", tick)
.charge(function(d) {
return -1000;
})
.linkDistance(function(d) {
return d.target._children ? 200 : 120;
})
.size([w, h - 160]);
var svg = d3.select("#viz").append("svg")
.attr("width", w)
.attr("height", h);
root = words[0]; //set root node
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();
function update() {
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.start();
// Update the links…
link = svg.selectAll(".link")
.data(links);
// Enter any new links.
link.enter().insert("svg:line", ".node")
.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;
});
// Exit any old links.
link.exit().remove();
// Update the nodes…
node = svg.selectAll("circle.node")
.data(nodes, function(d) {
return d.name;
})
.style("fill", color);
node.transition()
.attr("r", radius);
// Enter any new nodes.
node.enter().append("svg:circle")
.attr("class", "node")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", radius)
.style("fill", color)
//.on("click", click)
.on("click",function(){
console.log("Click Main Circle")
})
//.call(force.drag);
node.append("title")
.text(function(d) {
return d.name;
});
// Exit any old nodes.
node.exit().remove();
}
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;
});
node.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
}
// Color leaf nodes orange, and packages white or blue.
function color(d) {
if (d._children) {
return "#95a5a6";
} else {
switch (d.group) {
case 'r': //adverb
return "#e74c3c";
break;
case 'n': //noun
return "#3498db";
break;
case 'v': //verb
return "#2ecc71";
break;
case 's': //adjective
return "#e78229";
break;
default:
return "#9b59b6";
}
}
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();
}
// Returns a list of all nodes under the root.
function flatten(root) {
var nodes = [],
i = 0;
function recurse(node) {
if (node.children) node.size = node.children.reduce(function(p, v) {
return p + recurse(v);
}, 0);
if (!node.id) node.id = ++i;
nodes.push(node);
return node.size;
}
root.size = recurse(root);
return nodes;
}
I have tried to group the circles, by grouping the circles and adding the semi-circle path in the group, but the layout breaks.
var arc = d3.svg.arc()
.innerRadius(0)
.outerRadius(30)
.startAngle(0) //converting from degs to radians
.endAngle(pi)
var g = node.enter().append("g")
.attr("class", "node");
g.append("svg:circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", radius)
.style("fill", color)
//.on("click", click)
.on("click",function(){
console.log("Click Main Circle")
})
g.append("path")
.attr("d", arc)
.attr("fill", "blue")
.attr("transform", "translate(200,200)")
.on("click",function(d){
console.log("path");
})
Here is how you can fix your layout problem.
Instead of setting cx and cy to circle in the tick.
Do following:
Make a group
Add circle to above group (do not set cx/cy)
Add path to the above group. (do not set transform)
In tick do following to transform both circle and path which is contained in it.
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;
});
//transform for nodes.
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"
})
}
Working code here
Hope this helps!

Unable to get d3.js data.join and key function working when selecting line

This is what I'm currently using.
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Git commit history</title>
</head>
<body>
<button onclick="f(data0)">Original</button>
<button onclick="f(data1)">Final</button>
<div id="chart"></div>
<script src='http://d3js.org/d3.v3.min.js'></script>
<script>
var data1 = {"directed": true, "HEAD": "37e1d1e19f1ed57f8635ba4ba48d7a6a16ec52f6", "links": [{"source": 0, "target": 2}, {"source": 2, "target": 1}, {"source": 3, "target": 4}, {"source": 4, "target": 0}, {"source": 4, "target": 5}, {"source": 5, "target": 2}], "multigraph": false, "graph": [], "labels": ["master"], "master": "37e1d1e19f1ed57f8635ba4ba48d7a6a16ec52f6", "nodes": [{"message": "Add barn door", "id": "2b818acb7782772d0b43a0fbfd18320348c66d09", "pos": [182.04, 162.0]}, {"message": "Initial commit", "id": "5d49116ea5679a9eb21225f05dd4874b3a0b5e35", "pos": [371.04, 18.0]}, {"message": "Add animals", "id": "a21fd23c9a9742c93febff279d7f917d457c0f04", "pos": [371.04, 90.0]}, {"message": "Add chickens", "id": "37e1d1e19f1ed57f8635ba4ba48d7a6a16ec52f6", "pos": [371.04, 306.0]}, {"message": "Merge branch 'add-barn-doors'", "id": "f0f204010fe90e377f37c8e466110e49e420ac9e", "pos": [371.04, 234.0]}, {"message": "Remove cow", "id": "009d60bee58372a19e1188368ecfcc3c9ed5c2f1", "pos": [560.04, 162.0]}]}
var data0 = {"directed": true, "HEAD": "f0f204010fe90e377f37c8e466110e49e420ac9e", "links": [{"source": 0, "target": 4}, {"source": 1, "target": 0}, {"source": 1, "target": 2}, {"source": 2, "target": 4}, {"source": 4, "target": 3}], "multigraph": false, "graph": [], "labels": ["master"], "master": "f0f204010fe90e377f37c8e466110e49e420ac9e", "nodes": [{"message": "Add barn door", "id": "2b818acb7782772d0b43a0fbfd18320348c66d09", "pos": [182.04, 162.0]}, {"message": "Merge branch 'add-barn-doors'", "id": "f0f204010fe90e377f37c8e466110e49e420ac9e", "pos": [371.04, 234.0]}, {"message": "Remove cow", "id": "009d60bee58372a19e1188368ecfcc3c9ed5c2f1", "pos": [560.04, 162.0]}, {"message": "Initial commit", "id": "5d49116ea5679a9eb21225f05dd4874b3a0b5e35", "pos": [371.04, 18.0]}, {"message": "Add animals", "id": "a21fd23c9a9742c93febff279d7f917d457c0f04", "pos": [371.04, 90.0]}]}
var w = 1500,
H = 50,
fill = d3.scale.category20();
var h = H;
var vis = d3.select("#chart")
.append("svg:svg");
var f = function(data) {
h = H*1.45*data.nodes.length;
vis.attr("height", h)
.attr("width", w);
h = H*1.45*data.nodes.length;
vis.attr("height", h);
var transitiontime = 150;
var PosX = function(d, i, location) { return data.nodes[d[location]].pos[0]; };
var PosY = function(d, i, location) { return h-data.nodes[d[location]].pos[1]; };
var reverseMap = {};
for(var i=0; i<data.nodes.length; i++){
var p = data.nodes[i];
var hash = p.id;
reverseMap[hash] = p;
};
function poslink(d, i){ try{ return data.nodes[d.source].id + "" + data.nodes[d.target].id;} catch(err) {console.log(err); } }
var linkUpdateSelection = vis.selectAll(".link")
.data(data.links, poslink);
linkUpdateSelection.exit().remove();
linkUpdateSelection
.enter().append("line") // attach a line
.attr("class", "link")
.style("stroke", "black") // colour the line
.attr("x1", function(d, i) {return PosX(d, i, "source");}) // x position of the first end of the line
.attr("y1", function(d, i) {return PosY(d, i, "source");}) // y position of the first end of the line
.attr("x2", function(d, i) {return PosX(d, i, "target");}) // x position of the second end of the line
.attr("y2", function(d, i) {return PosY(d, i, "target");}) // y position of the second end of the line
.style('opacity', 0);
linkUpdateSelection
.attr("class", "link")
.style("stroke", "black") // colour the line
.transition()
.delay(function(d,i) {return i*transitiontime})
.attr("x1", function(d, i) {return PosX(d, i, "source");}) // x position of the first end of the line
.attr("y1", function(d, i) {return PosY(d, i, "source");}) // y position of the first end of the line
.attr("x2", function(d, i) {return PosX(d, i, "target");}) // x position of the second end of the line
.attr("y2", function(d, i) {return PosY(d, i, "target");}) // y position of the second end of the line
.style('opacity', 1);
var nodeUpdateSelection = vis.selectAll("circle.node")
.data(data.nodes, function(d) {return d.id});
nodeUpdateSelection.exit().remove();
nodeUpdateSelection.enter().append("svg:circle")
.attr("class", "node")
.attr("cx", function(d, i) { return d.pos[0]; })
.attr("cy", function(d, i) { return h-d.pos[1]; })
.attr("r", 0)
.attr('fill', function(d, i) {if (reverseMap[d.id]==reverseMap[data['HEAD']]){return '#99FF66';} else {return 'red';}} )
.on('mouseover', function(d,i) {
d3.select(this).transition()
.ease('cubic-out')
.duration('200')
.attr("r", 15)
})
.on('mouseout', function(d,i) {
d3.select(this).transition()
.ease('cubic-out')
.duration('200')
.attr("r", 10)
});
nodeUpdateSelection.transition()
.delay(function(d,i) {return i*transitiontime})
.attr("class", "node")
.attr("cx", function(d, i) { return d.pos[0]; })
.attr("cy", function(d, i) { return h-d.pos[1]; })
.attr('fill', function(d, i) {if (reverseMap[d.id]==reverseMap[data['HEAD']]){return '#99FF66';} else {return 'red';}} )
.attr("r", 10);
var textUpdateSelection = vis.selectAll("text.message")
.data(data.nodes, function(d) {return d.id});
textUpdateSelection.exit().remove();
textUpdateSelection
.enter().append("text")
.text(function(d) { return d.id.substring(0, 6) + " - " + d.message; })
.attr("x", function(d) { return 15+d.pos[0]; })
.attr("y", function(d) { return h-d.pos[1]+5; })
.attr("font-family", "sans-serif")
.attr("class", "message")
.attr("font-size", "15px")
.attr("fill", "blue")
.style('fill-opacity', 0);
textUpdateSelection
.text(function(d) { return d.id.substring(0, 6) + " - " + d.message; })
.transition()
.delay(function(d,i) {return i*transitiontime})
.style('fill-opacity', 1)
.attr("x", function(d) { return 15+d.pos[0]; })
.attr("y", function(d) { return h-d.pos[1]+5; })
.attr("class", "message")
.attr("font-family", "sans-serif")
.attr("font-size", "15px")
.attr("fill", "blue");
var labelUpdateSelection = vis.selectAll("text.labels")
.data(data.labels);
var labelPosX = function(d) { return reverseMap[d].pos[0]; };
var labelPosY = function(d) { return reverseMap[d].pos[1]; };
labelUpdateSelection.exit().remove();
labelUpdateSelection.enter().append("text")
.text(function(d) { return d })
.style('fill-opacity', 0)
.attr("x", function(d, i) { return data[d][0]- 75; })
.attr("y", function(d, i) { return h-data[d][1] + 5; })
.attr("class", "labels")
.attr("font-family", "sans-serif")
.attr("font-size", "15px");
labelUpdateSelection
.text(function(d) { return d })
.transition()
.delay(function(d,i) {return i*transitiontime})
.style('fill-opacity', 1)
.attr("x", function(d, i) { return labelPosX(data[d]) - 50 ; })
.attr("y", function(d, i) { return h-labelPosY(data[d]) - 25 + i*5; })
.attr("class", "labels")
.attr("font-family", "sans-serif")
.attr("font-size", "15px");
}
f(data0)
</script>
</body>
</html>
However, during an update, the lines do not move as expected. I'll try posting a screencast of what is happening to explain better. My current implementation works for nodes in a graph, but not for the lines for some reason. I feel like I'm missing something silly here.
Full code over here
Edit :
Here is a screencast of what currently occurs. I want the lines between two nodes to stay between those two nodes.
Edit II :
I've added a full working example above, with the data included as part of the code
The problem is that your key function is referencing data, which changes between calls. The source and target nodes of a link are referred to by index, and the same index points to different nodes with different data. That is, for the original data, index 0 may refer to "foo", but with the new data the same index refers to "bar".
The easiest way to fix this is to save the index with the links in a way that doesn't change when the data is changed:
data.links.forEach(function(d) {
d.id = data.nodes[d.source].id + "" + data.nodes[d.target].id;
});
Then your data matching becomes
var linkUpdateSelection = vis.selectAll("line.link")
.data(data.links, function(d) { return d.id; });
and the update works as expected. Complete demo here.

Categories