I want to show on the chart D3 e-mail communications between users using data from JSON file on chart FLARE or other.
Users can be represented on a graph as a node and e-mails between them as links.
If it is possible to present in the D3 and someone knows the solution to this problem please let me know.
The following sample array of data for a single email.
In the other tables changes to email details: user names, titles, emails, dates and times.
{
"metadataAsStrings": {
"doc-from": "User 1",
"doc-sender": "User 1",
"caat-derived-recipients": "User 2",
"doc-subject": "Title Email 1"
"doc-recipient": "User 2",
"caat-normalized-author": "User 1",
"caat-derived-email-action": "REPLY"
"caat-derived-end-email": "true",
"caat-derived-inclusive-email-reason": "MESSAGE"
"doc-date": "2014/09/25 10:20:00",
"doc-is", "User 2"
}
}
This is pretty easy with the force layout.
Here's the plnkr:
http://plnkr.co/edit/1Mub7rTUKQuuAB6TAoJb?p=preview
What I actually did was create a json according to the structure that d3 force layout needs.
Assuming I have something similar to your data, I do a little bit of parsing:
{
"edges": [
{
"source":"1",
"target": "2",
"color": "yellow",
"weight": "1.0",
"doc-subject": "Title Email 1"
},
{
"source":"2",
"target": "3",
"color": "blue",
"weight": "1.0",
"doc-subject": "Title Email 2"
}
],
"nodes": [
{
"label":"user 1",
"x":-1015.1223754882812,"y":679.421875,
"id":"1","attributes":{},"color":"rgb(175,156,171)",
"size":20
},
{
"label":"user 2",
"x":-915.1223754882812,"y":659.421875,
"id":"2","attributes":{},"color":"rgb(175,156,171)",
"size":15
},
{
"label":"user 3",
"x":-1015.1223754882812,"y":579.421875,
"id":"3","attributes":{},"color":"rgb(175,156,171)",
"size":15
}
]
}
Then, in d3, I have this code to parse it:
d3.json("graph.json", function(error, graphData) {
//setup the data
var graph = {};
graph.nodes = [];
graph.links = [];
var test = [];
// set the node data
for (var nodeIndex in graphData.nodes){
var curr_node = graphData.nodes[nodeIndex];
graph.nodes[curr_node.id] = {
x: curr_node.x,
y: curr_node.y,
color: curr_node.color,
size: curr_node.size,
label: curr_node.label,
id: curr_node.id
};
test.push(Number(curr_node.id));
}
// sort the IDs
function sortNumber(a,b) {
return a - b;
}
test.sort(sortNumber);
// now go over each ID and set it in the
var tmpNodes = [];
for (var index in test){
tmpNodes.push(graph.nodes[test[index]]);
}
graph.nodes = tmpNodes;
// now setup the edges/links
for (edge in graphData.edges){
var curr_link = graphData.edges[edge];
graph.links.push({source: test.indexOf(Number(curr_link.source)), target: test.indexOf(Number(curr_link.target)), weight: 1.0});
}
force
.nodes(graph.nodes)
.links(graph.links)
.start();
link = link.data(graph.links)
.enter().append("line")
.attr("class", "link");
node = node
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(drag)
.on("dblclick", dblclick)
.on("mouseover", function(d){
hover.html(d.id + ": " + d.label);
})
.on("mouseleave", function(d){
hover.html("");
})
;
node.append("circle")
.attr("r", function(d,i){
return d.size/2;
})
.attr("fill", function(d,i){
return d.color;
})
;
var textNode = node.append("g");
var text = textNode.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.attr("font-size", function(d){
return 12+(d.size-1)/7+"px";
})
.text(function(d) {
return d.label });
textNode.append("rect")
.attr("x",function(d,i){
var g = node[0][i].childNodes[1];
return -g.getBBox().width;
})
.attr("y",function(d,i){
var g = node[0][i].childNodes[1].childNodes[0];
return -g.getBBox().height+10;
})
.attr("fill","white")
.attr("fill-opacity",0.25)
.attr("width",function(d,i){
var g = node[0][i].childNodes[1];
return g.getBBox().width;
})
.attr("height",function(d,i){
var g = node[0][i].childNodes[1].childNodes[0];
return g.getBBox().height;
})
;
});
So what happens in the code, d3 gets the json, I do a little bit of parsing for d3 to get the data setup better (I made it like that so I could add more users, and sort them by their ID rather then the order in which they are set in the array) and then just give the links and nodes to d3 to plot.
Hope this helps.
Related
I have a code which plots lines and circles.
I know what does slice do below.
But when do we use slice vs non slice way of a variable in .data section?
var link = g.selectAll(".link")
.data(nodes.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.style("stroke", function(d) { return d.data.level;})
.attr("d", function(d)
{
return "M" + d.x + "," + d.y
+ "L" + d.parent.x + "," + d.parent.y;
});
var node = g.selectAll(".node")
.data(nodes.descendants())
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d)
{
return "translate(" + d.x + "," + d.y + ")";
});
var circle = node.append("circle").attr("r",30);
I take it you are using descendants() with d3.hierarchy - notice in the console output below that Eve has no parent because it is the 'root' node, and that all the other nodes in the descendants() array do have a parent.
const nodes = d3.hierarchy(data);
for (let d of nodes.descendants()) {
let name = d.data.name;
let depth = d.depth;
let parentName = d.parent ? d.parent.data.name : 'no parent - no link!';
let msg = `Name: ${name}; Depth: ${depth}; Parent: ${parentName}`;
console.log(msg);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script>
const data = {
"name": "Eve",
"children": [
{
"name": "Cain"
},
{
"name": "Seth",
"children": [
{
"name": "Enos"
},
{
"name": "Noam"
}
]
},
{
"name": "Abel"
},
{
"name": "Awan",
"children": [
{
"name": "Enoch"
}
]
},
{
"name": "Azura"
}
]
}
</script>
Then note d function:
.attr("d", function(d)
{
return "M" + d.x + "," + d.y
+ "L" + d.parent.x + "," + d.parent.y;
});
Which says Move the the centre of the 'child' circle and draw the Line to the middle of the 'parent' circle. Because your 'root' node (Eve) has no parent then this node should be eliminated from the link layout hence slice(1). But because all the other nodes should be connected to Eve then this root node is not sliced from here:
var node = g.selectAll(".node")
.data(nodes.descendants())
In order that the circle for the Eve node is drawn and therefore the links from the child nodes of Eve can connect to something.
I'm trying to create a grouped bar chart, with the years on the x-axis, and the median key on the y-axis.
The data structure looks like the snippet under, ie one object per year, containing an array of 2 objects containing themselves what I want to plot.
// Draw bars
var groups = g.selectAll(".groups")
.data(data)
.enter().append("g")
.attr("class", "groups")
groups.selectAll("bar")
.data(function(d) { console.log("d is", d); return d })
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.date); })
.attr("y", function(d) { return y(d.median); })
.attr("width", 35)
.attr("height", function(d) { return height - y(d.median); });
Can't get my head around it!
Thanks so much
data:
const data = [
{
"key": "2011",
"values": [
{
"date": "Sat Jan 01 2011 00:00:00 GMT+00 (GMT)",
"median": 7.5
}
]
},
{
"key": "2012",
"values": [
{
"date": "Sun Jan 01 2011 00:00:00 GMT+00 (GMT)",
"median": 7.2
}
]
}
]
Edit: Here's a JSFiddle
Your problem comes from this function :
data.forEach(function(d) {
d.date = parseTime(d[dateInput]);
d.median = +d[medianInput]
});
it should be
data.forEach(function(d) {
d.date = parseTime(d.values[0][dateInput]);
d.median = +d.values[0][medianInput]
});
to respect your data
I'm trying to make some lines with this data format:
var data3 = [
{ "task": "Estructura", "data": [{"x":new Date(Date.parse("2014-01-03")),"y":0}, {"x":new Date(Date.parse("2014-03-09")),"y":8}] },
{ "task": "Mamposteria", "data": [{"x":new Date(Date.parse("2014-02-01")),"y":0}, {"x":new Date(Date.parse("2014-03-01")),"y":8}] },
{ "task": "Friso", "data": [{"x":new Date(Date.parse("2014-03-01")),"y":0}, {"x":new Date(Date.parse("2014-03-30")),"y":8}] },
{ "task": "Mortero", "data": [{"x":new Date(Date.parse("2014-05-01")),"y":8}, {"x":new Date(Date.parse("2014-07-01")),"y":0}] },
{ "task": "Pisos", "data": [{"x":new Date(Date.parse("2014-07-01")),"y":8}, {"x":new Date(Date.parse("2014-09-01")),"y":0}] }
];
And this the code for generating the lines:
var colors = [
'steelblue',
'green',
'red',
'purple',
'black'
]
var xScale = d3.time.scale()
.domain([
new Date(Date.parse('2014-01-01')),
new Date(Date.parse('2014-12-31'))
])
.range([0, width]);
var yScale = d3.scale.linear()
.domain([0, 8])
.range([height, 0]);
var line = d3.svg.line() // line generator
.interpolate("linear")
.x(function(d) { return xScale(d.data.x); })
.y(function(d) { return yScale(d.data.y); });
var activities = svg.selectAll('.line')
.data(data3)
.enter()
.append("path");
var activitiesAttibutes = activities
.attr("class", "line")
.attr('stroke', function(d,i){
return colors[i%colors.length];
})
.attr("d", line)
.attr("transform", "translate(15,30)");
but I'm having trouble because it is not a two element array.
Do I have to arrange the data in another way or make changes in the line function generator? Could you provide me an example where I can solve this?
There are two things that need to change here - you need to iterate over the data array in each object for the line, and you need to change the accessors so that they apply to a single object in the data array:
var line = d3.svg.line() // line generator
.interpolate("linear")
// d is a single object in a data array
.x(function(d) { return xScale(d.x); })
.y(function(d) { return yScale(d.y); });
and then to apply the line, you're acting on a top-level object, so you need to pass in the data array:
var activitiesAttibutes = activities
.attr("d", function(d) {
return line(d.data);
});
See fiddle: http://jsfiddle.net/h07s5jc5/1/
I'm trying to use attrTween in d3 to animate a pie chart when the page is loaded but it's not working for me. I've used attrTween before to animate a change in data and it's worked fine but this time I want to 'grow' the pie chart when the page is loaded first but it's not behaving as expected and I'm not getting any information as to why this is.
If I remove the line .attrTween('d' arcTweenStart); then everything works fine except of course it does not animate. If the line is left in then nothing is displayed and the arcTweenStart function is never entered. Can anyone spot where I'm going wrong?
function drawCharts()
{
// Create the chart and bind the data to it and position it
var pieChart = d3.select("#groupRisk").selectAll("svg")
.data(dataSet) // Bind the data to the chart
.enter().append("svg")
.attr("id", "pie")
.attr("width", w) // Set th width
.attr("height", h) // Set the height
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")"); // Position the chart
// Create the pie chart layout
var pie = d3.layout.pie()
.value(function(d) { return d.count; })
.sort(null); // Sort is set to null to allow for better looking tweens
// Create "slices" for each data element
var arcs = pieChart.selectAll("g.slice")
.data(pie) // Bind the pie layout to the slices
.attr("id", "arcs")
.enter()
.append("g")
.attr("class", "slice");
// Create the graphics for each slice and colour them
arcs.append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.each(function(d) { this._current = d; })
.transition()
.duration(500)
.attrTween('d' arcTweenStart);
}
function arcTweenStart(b)
{
var start =
{
startAngle: b.startAngle,
endAngle: b.endAngle
};
var i = d3.interpolate(start, b);
return function(t)
{
return arc(i(t));
};
}
EDIT:
My data set looks like this:
var dataSet=
[
[
{ "label": "Green", "count": 40 },
{ "label": "Amber", "count": 50 },
{ "label": "Red", "count": 10 }
],
[
{ "label": "Green", "count": 20 },
{ "label": "Amber", "count": 30 },
{ "label": "Red", "count": 50 }
],
[
{ "label": "Green", "count": 50 },
{ "label": "Amber", "count": 20 },
{ "label": "Red", "count": 30 }
]
];
I have an array of data sets so I want to draw a chart for each one.
You don't show what your dataSet variable holds (that would have really helped answer the question!) but assuming your data looks like this:
var dataSet = [{
count: 4
}, {
count: 5
}, {
count: 6
}];
You don't need to do the first bind/enter:
d3.select("#groupRisk").selectAll("svg")
.data(dataSet) // Bind the data to the chart
.enter()
...
This would give you a pie chart for each entry in the data. Getting rid of that, your bind then becomes:
var arcs = pieChart.selectAll("g.slice")
.data(pie(dataSet)) //<-- call pie with the dataSet
.attr("id", "arcs")
.enter()
.append("g")
.attr("class", "slice");
But really to the heart of your question, your tween var start, has the same start/end angle as where you want to end. So, you animate the same thing over and over again. What I think you meant is:
function arcTweenStart(b) {
var start = {
startAngle: b.startAngle,
endAngle: b.startAngle //<-- set end to start and adjust on each call
};
var i = d3.interpolate(start, b);
return function(t) {
return arc(i(t));
};
}
Oh, and one typo in there too:
.attrTween('d' arcTweenStart); //<-- comma missing between 'd' and arcTweenStart
Example here.
I'm trying to create a circle pack graph using nest() and .rollup. I'm getting the following errors:
Error: Invalid value for <g> attribute transform="translate(undefined,undefined)"
Error: Invalid value for <circle> attribute r="NaN"
I want the circles to be sized according to the number of companies in each country. I'm attempting to adapt Mike Bostock's Flare circle-pack example.
If anyone could point me in the direction of any information, I'd be very grateful.
JS code:
var diameter = 960,
format = d3.format(",d");
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.value(function(d) { return d.size; });
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(2,2)");
//Get data
d3.json("data/countriesNested.php", function(error, data){
var submissionsByCountry = d3.nest()
.key(function(d) { return d.Country; })
.key(function(d) { return d.Organisation; })
.rollup(function(leaves) { return leaves.length; })
.entries(data);
var node = svg.datum(data).selectAll(".node")
.data(pack.nodes)
.enter().append("g")
.attr("class", function(d) { return d.children ? "node" : "leaf node"; })
.attr("transform", function(d) { return "translate(" + d.cx + "," + d.cy + ")"; });
node.append("title")
.text(function(d) { return d.name + (d.children ? "" : ": " + format(d.size)); });
node.append("circle")
.attr("r", function(d) { return d.r; });
});
d3.select(self.frameElement).style("height", diameter + "px");
</script>
Data file (from MySQL using PHP script):
[
{
"Country":"USA",
"ID":4,
"Organisation":"Company 1"
},
{
"Country":"USA",
"ID":5,
"Organisation":"Company 2"
},
{
"Country":"USA",
"ID":6,
"Organisation":"Company 3"
},
{
"Country":"FRANCE",
"ID":19,
"Organisation":"Company 4"
},
{
"Country":"FRANCE",
"ID":24,
"Organisation":"Company 5"
},
{
"Country":"GERMANY",
"ID":10,
"Organisation":"Company 6"
},
{
"Country":"ITALY",
"ID":7,
"Organisation":"Company 7"
},
.....
Thanks for reading.
There are a few errors in your code that need to be fixed:
You need to set the accessor functions for children and values on your pack layout:
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.children(function(d) {
return d.values; // accessor for children
})
.value(function(d) {
return d.values; // accessor for values
});
Your d3.nest() returns an array but d3.pack() requires you to supply a root object containing the hierarchy. You have to create a root object and put your nested array inside:
var countryRoot = {
key: "root",
values: submissionsByCountry
};
In your code you nest your data into submissionsByCountry but you are not using this variable anywhere else. So you obviously have to refer to it when binding data to your svg. This is accomplished by the above mentioned root object which is later on bound to the svg.
var node = svg.datum(countryRoot).selectAll(".node")
The attributes the pack layout is adding to your data nodes include values x and y, whereas you refered to them as cx and cy which are attributes to <svg:circle> but are not present in your data. Hence, you got your transform="translate(undefined,undefined)" error messages. You should use these attributes as such:
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
I put together a working plunk.
So, you want a parent circle (let's call it the world), with child circles representing each country sized with a count of entries in your JSON array?
d3.json("data/countriesNested.php", function(error, data) {
var submissionsByCountry = d3.nest()
.key(function(d) {
return d.Country;
})
.rollup(function(leaves) {
return leaves.length;
})
.entries(data);
var root = {
"key": "world",
"children": submissionsByCountry
};
...
This will give you something closely resembling the flare.json.
Next, you need to give d3 the right accessor for your circle size.
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.value(function(d) {
return d.values; //<-- this comes from your roll-up and is the count.
});
Finally it looks like you changed the example code to access non-exist cx and cy attributes in the resulting nodes data:
var node = svg.datum(root).selectAll(".node")
.data(pack.nodes)
.enter().append("g")
.attr("class", function(d) {
return d.children ? "node" : "leaf node";
})
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; //<-- this is .x, .y
});
Here's an example.