Data structure:
var data = [
{name: "male",
values: [
{ count: 12345,
date: Date 2015-xxx,
name: "male" },
{...}
]
},
{name: "female",
values: [
{ count: 6789,
date: Date 2015-xxx,
name: "female" },
{...}
]
}
]
The values that I want to access are data[a].values[b].count
The values are used to draw circles for my plot
code for circle plot:
focus.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", function(d,i) { return x(d.values[i].date); })
.attr("cy", function(d,i) { return y(d.values[i].count); })
.attr("r", 4)
.style("fill", function(d,i) { return color(d.values[i].name); })
The problem is that i = 1 because of its position in the object.
What I want to do is to loop through all the objects under values. How can I do that?
Edit: I wish to learn how to do it without altering the data, to improve on my skills.
Thanks.
There are several ways to do what you want using D3 only, without any other library and without altering the data. One of them is using groups to deal with "higher" levels of data (regarding the nested data). Let's see it in this code:
First, I mocked up a dataset just like yours:
var data = [
{name: "male",
values: [{ x: 123,y: 234},
{ x: 432,y: 221},
{ x: 199,y: 56}]
},
{name: "female",
values: [{ x: 223,y: 111},
{ x: 67,y: 288},
{ x: 19, y: 387}]
}
];
This is the data we're gonna use. I'm gonna make a scatter plot here (just as an example), so, let's set the domains for the scales accessing the second level of data (x and y inside values):
var xScale = d3.scaleLinear().range([20, 380])
.domain([0, d3.max(data, function(d){
return d3.max(d.values, function(d){
return d.x;
})
})]);
var yScale = d3.scaleLinear().range([20, 380])
.domain([0, d3.max(data, function(d){
return d3.max(d.values, function(d){
return d.y;
})
})]);
Now comes the most important part: we're gonna bind the data to "groups", not to the circle elements:
var circlesGroups = svg.selectAll(".circlesGroups")
.data(data)
.enter()
.append("g")
.attr("fill", function(d){ return (d.name == "male") ? "blue" : "red"});
Once in the first level of data we have 2 objects, D3 will create 2 groups for us.
I also used the groups to set the colours of the circles: if name is "male", the circle is blue, otherwise it's red:
.attr("fill", function(d){ return (d.name == "male") ? "blue" : "red"});
Now, with the groups created, we create circles according to the values in the data of each group, binding the data as follows:
var circles = circlesGroups.selectAll(".circles")
.data(function(d){ return d.values})
.enter()
.append("circle");
Here, function(d){ return d.values} will bind the data to the circles according to the objects inside values arrays.
And then you position your circles. This is the whole code, click "run code snippet" to see it:
var data = [
{name: "male",
values: [{ x: 123,y: 234},
{ x: 432,y: 221},
{ x: 199,y: 56}]
},
{name: "female",
values: [{ x: 223,y: 111},
{ x: 67,y: 288},
{ x: 19, y: 387}]
}
];
var xScale = d3.scaleLinear().range([20, 380])
.domain([0, d3.max(data, function(d){
return d3.max(d.values, function(d){
return d.x;
})
})]);
var yScale = d3.scaleLinear().range([20, 380])
.domain([0, d3.max(data, function(d){
return d3.max(d.values, function(d){
return d.y;
})
})]);
var xAxis = d3.axisBottom(xScale).tickSizeInner(-360);
var yAxis = d3.axisLeft(yScale).tickSizeInner(-360);
var svg = d3.select("body")
.append("svg")
.attr("width", 400)
.attr("height", 400);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0,380)")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(20,0)")
.call(yAxis);
var circlesGroups = svg.selectAll(".circlesGroups")
.data(data)
.enter()
.append("g")
.attr("fill", function(d){ return (d.name == "male") ? "blue" : "red"});
var circles = circlesGroups.selectAll(".circles")
.data(function(d){ return d.values})
.enter()
.append("circle");
circles.attr("r", 10)
.attr("cx", function(d){ return xScale(d.x)})
.attr("cy", function(d){ return yScale(d.y)});
.axis path, line{
stroke: gainsboro;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
The easiest way is to use a lib like underscore.js to edit your data array.
From underscore docs:
flatten _.flatten(array, [shallow])
Flattens a nested array (the nesting can be to any depth). If you pass shallow, >the array will only be flattened a single level.
_.flatten([1, [2], [3, [[4]]]]);
-> [1, 2, 3, 4];
_.flatten([1, [2], [3, [[4]]]], true);
-> [1, 2, 3, [[4]]];
map _.map(list, iteratee, [context]) Alias: collect
Produces a new array of values by mapping each value in list through a >transformation function (iteratee). The iteratee is passed three arguments: the >value, then the index (or key) of the iteration, and finally a reference to the >entire list.
_.map([1, 2, 3], function(num){ return num * 3; });
=> [3, 6, 9]
_.map({one: 1, two: 2, three: 3}, function(num, key){ return num * 3; });
=> [3, 6, 9]
_.map([[1, 2], [3, 4]], _.first);
=> [1, 3]
Underscore documentation
In your code you can do something like that:
var flatData = _.flatten(_.map(data, (d)=>d.values));
focus.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", function(d,i) { return x(d.date); })
.attr("cy", function(d,i) { return y(d.count); })
.attr("r", 4)
.style("fill", function(d,i) { return color(d.name); })
Related
Context : I have this Django server that manages devices, i want to show a communication graph between these devices, i've decide to use D3 force graph for this purpose, the Django server will send a json through Redis with a websocket, i want the client to read the json and print the graph.
So far i've been able to print static graph, but i can't manage to update it live.
Usefull link :
Core code from This example.
Tried to follow This, but i don't think it's the right direction.
Goal : Update a Force graph in real time using websocket.
My JS code :
var graph = {
"nodes": [
{"id": "Agent_1", "group": 1},
{"id": "Agent_2", "group": 2},
{"id": "Agent_3", "group": 1},
{"id": "Agent_4", "group": 3}
],
"links": []
};
const comSocket = new WebSocket(
'ws://'
+ window.location.host
+ '/ws/com/'
);
comSocket.onmessage = function (e) {
graph = JSON.parse(e.data).message;
console.log(graph);
simulation.nodes(graph.nodes);
simulation.force("link").links(graph.links);
simulation.alpha(1).restart();
};
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody().strength(-2500))
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g").attr("class", "links").selectAll("line")
.data(graph.links).enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(graph.nodes)
.enter().append("g")
var circles = node.append("circle")
.attr("r", 20)
.attr("fill", function(d) { return color(d.group); });
var lables = node.append("text").text(function(d) {return d.id;}).attr('x', 16).attr('y', 13);
node.append("title").text(function(d) { return d.id; });
simulation.nodes(graph.nodes).on("tick", ticked);
simulation.force("link").links(graph.links);
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
};
Using the above code it produce this error :
Uncaught TypeError: Cannot read properties of undefined (reading 'length')
at the line :
simulation.nodes(graph.nodes);
in onmessage()
The data value is a json with the same structure as var graph (line 1). So i don't know why it can initialize the graph correctly but canno't refresh with the same value.. :
{'nodes': [{'id': 'Agent_0', 'group': 1}, {'id': 'Agent_1', 'group': 2}, {'id': 'Agent_2', 'group': 1}, {'id': 'Agent_3', 'group': 3}], 'links': [{'source': 'Agent_0', 'target': 'Agent_2', 'value': 1}, {'source': 'Agent_0', 'target': 'Agent_1', 'value': 3}, {'source': 'Agent_0', 'target': 'Agent_3', 'value': 5}, {'source': 'Agent_1', 'target': 'Agent_3', 'value': 3}, {'source': 'Agent_2', 'target': 'Agent_3', 'value': 5}, {'source': 'Agent_1', 'target': 'Agent_2', 'value': 5}]}
It was a server side issue, wrong type was sent.
In the end i've also update the code to latest version (only color needed to be updated). Here's the final working version :
var graph = {
"nodes": [
{"id": "Agent_0", "group": 1},
{"id": "Agent_1", "group": 2},
{"id": "Agent_2", "group": 1},
{"id": "Agent_3", "group": 3}
],
"links": []
};
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.schemeCategory10;
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody().strength(-2500))
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g").attr("class", "links").selectAll("line").data(graph.links).enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.append("g").attr("class", "nodes").selectAll("g").data(graph.nodes).enter().append("g")
var circles = node.append("circle").attr("r", 20).attr("fill", function(d) { return color[d.group]; });
var lables = node.append("text").text(function(d) {return d.id;}).attr('x', 16).attr('y', 13);
node.append("title").text(function(d) { return d.id; });
simulation.nodes(graph.nodes).on("tick", ticked);
simulation.force("link").links(graph.links);
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
};
const comSocket = new WebSocket(
'ws://'
+ window.location.host
+ '/ws/com/'
);
comSocket.onmessage = function (e) {
graph = JSON.parse(e.data).message;
console.log(graph);
console.log(typeof graph);
svg.selectAll("*").remove();
link = svg.append("g").attr("class", "links").selectAll("line").data(graph.links).enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
node = svg.append("g").attr("class", "nodes").selectAll("g").data(graph.nodes).enter().append("g")
circles = node.append("circle").attr("r", 20).attr("fill", function(d) { return color[d.group]; });
lables = node.append("text").text(function(d) {return d.id;}).attr('x', 16).attr('y', 13);
node.append("title").text(function(d) { return d.id; });
simulation.nodes(graph.nodes).on("tick", ticked);
simulation.force("link").links(graph.links);
simulation.alpha(1).restart();
};
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
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.