Related
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>
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>
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
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
As far as I see update(),enter(),exit() are used to update the view from the data. Is there also a way in the other direction, updating the data from the GUI?
e.g. click on the B-circle, click remove -> will be removed from GUI, but is still present in the data.. by clicking refresh, it appears again:
http://jsfiddle.net/stefanf/6Qm2z/33/
'var nodesToRemove = pack.nodes(data);
var sel = vis.selectAll("circle").data(nodesToRemove, function(d){
return d.id;
});
sel.remove();'
p.s. I now I could do it by first updating data with javascript, however my question is if there is a appropriate d3-way..
I've done this working fiddle http://jsfiddle.net/6Qm2z/36/
var dataGlobal = {
"name": "Area",
"id": 1,
"size": 1,
"children": [
{
"name": "F",
"id": 2,
"size": 1,
"children": [
{ "name": "C",
"id": "3",
"size": 1
},
{
"name": "D",
"id": 4,
"size": 1,
"children": [
{ "name": "A",
"id": 5,
"size": 0.5
},
{ "name": "B",
"id": 6,
"size": 1
}
]
},
{
"name": "H",
"id": 7,
"size": 1
}
]
}
]
};
var w = 680,
h = 600,
r = 420,
x = d3.scale.linear().range([0, r]),
y = d3.scale.linear().range([0, r]),
node,
root;
var pack = d3.layout.pack()
.size([r, r])
.value(function(d) { return d.size; })
var vis = d3.select("body").insert("svg:svg", "h2")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(" + (w - r) / 2 + "," + (h - r) / 2 + ")");
node = root = dataGlobal;
var refreshGraph = function(newArray) {
var nodes;
if(newArray != null){
nodes=newArray;
}
else{
nodes = pack.nodes(root);
}
var sel = vis.selectAll("circle")
.data(nodes, function(d) { return d.id; });
sel
.enter().append("svg:circle");
sel
.attr("class", function(d) { return d.children ? "parent" : "child"; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return d.r; })
.on("dblclick", function(d) { return zoom(node == d ? root : d); })
.on("click", function () {
d3.select(".selected").classed("selected", false);
d3.select(this).classed("selected", true);
});
sel.exit().remove();
sel = vis.selectAll("text")
.data(nodes, function(d) { return d.name; });
sel
.enter().append("svg:text");
sel
.attr("class", function(d) { return d.children ? "parent" : "child"; })
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("opacity", function(d) { return d.r > 20 ? 1 : 0; })
.text(function(d) { return d.name; });
sel.exit().remove();
d3.select(window).on("click", function() { zoom(root); });
}
function zoom(d, i) {
var k = r / d.r / 2;
x.domain([d.x - d.r, d.x + d.r]);
y.domain([d.y - d.r, d.y + d.r]);
var t = vis.transition()
.duration(d3.event.altKey ? 7500 : 750);
t.selectAll("circle")
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.attr("r", function(d) { return k * d.r; });
t.selectAll("text")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y); })
.style("opacity", function(d) { return k * d.r > 20 ? 1 : 0; });
node = d;
d3.event.stopPropagation();
}
d3.select('#refreshBtn').on("click", function(d) {
updatedRefresh(dataGlobal);
});
d3.select('#addBtn').on("click", function(d) {
obj = {
"name": document.getElementById('textBtn').value,
"id": document.getElementById('idBtn').value,
"size": 1
}
d3.select(".selected").datum().children.push(obj)
refreshGraph()
});
d3.select("#deleteBtn").on("click", function() {
d3.select(".selected").each(function(data){
var nodesToRemove = pack.nodes(data);
var sel = vis.selectAll("circle").data(nodesToRemove, function(d){
return d.id;
});
sel.remove();
vis.selectAll("text").data(nodesToRemove, function(d){
return d.name;
})
.remove();
dataGlobal.children = prune(dataGlobal.children, data.id);
});
});
refreshGraph();
function prune(array, label) {
for (var i = 0; i < array.length; ++i) {
var obj = array[i];
if (obj.id === label) {
// splice out 1 element starting at position i
array.splice(i, 1);
return array;
}
if (obj.children) {
if (prune(obj.children, label)) {
if (obj.children.length === 0) {
// delete children property when empty
delete obj.children;
// or, to delete this parent altogether
// as a result of it having no more children
// do this instead
array.splice(i, 1);
}
return array;
}
}
}
}
function updatedRefresh(newArray) {
debugger;
var nodes;
if(newArray != null){
nodes=pack.nodes(newArray);
}
else{
nodes = pack.nodes(root);
}
var sel = vis.selectAll("circle")
.data(nodes, function(d) { return d.id; });
sel
.enter().append("svg:circle");
sel
.attr("class", function(d) { return d.children ? "parent" : "child"; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return d.r; })
.on("dblclick", function(d) { return zoom(node == d ? root : d); })
.on("click", function () {
d3.select(".selected").classed("selected", false);
d3.select(this).classed("selected", true);
});
sel.exit().remove();
sel = vis.selectAll("text")
.data(nodes, function(d) { return d.name; });
sel
.enter().append("svg:text");
sel
.attr("class", function(d) { return d.children ? "parent" : "child"; })
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("opacity", function(d) { return d.r > 20 ? 1 : 0; })
.text(function(d) { return d.name; });
sel.exit().remove();
d3.select(window).on("click", function() { zoom(root); });
}
every time that you delete one item I update dataGlobal array with the prune function.
Look at the updatedRefresh function to update with an array input