How to draw line charts with complex data structures in d3 - d3.js

I have the following complex data structure:
[
Object {id: 15, targets: Array[2]},
Object {id: 26, targets: Array[2]},
Object {id: 39, targets: Array[2]}
]
'targets' is an array of objects. Each of them has this shape:
Object {idTarget: "target1", events: Array[315]}
Object {idTarget: "target2", events: Array[310]}
'events' is an array with the real values to plot.
So, each element has this shape:
Object {timestamp: 1373241642, value: 1801.335}
Now, with this structured dataset, I would like to create an svg group 'g' for each external object (I am referring to 15, 26 and 39) and inside each group I want to create two lines, one for each target, using the values in 'events'.
Having a flat dataset it's easy to proceed in the drawing following the pattern: select + data + enter + append, but I am having trouble with this complex dataset.
For example I don't even know how to assign a key function to start.
svg.selectAll('.element')
.data(data, function(d){ return d.id + ?})
I would like to have this kind of keys '15target1', '15target2', '26target1', '26target2' and so on.
Do you recommend to simplify the dataset giving up the possibility of having neat groups or there is a workaround here that lets me easily draw what I want?
Thanks.

You want nested selections for this. Your code would look something like this.
var gs = d3.selectAll("g").data(data, function(d) { return d.id; });
gs.enter().append("g");
var line = d3.svg.line().x(function(d) { return xscale(d.timestamp); })
.y(function(d) { return yscale(d.value); });
gs.selectAll("path")
.data(function(d) { return d.targets; }, function(d) { return d.idTarget; })
.enter().append("path")
.attr("d", function(d) { return line(d.events); });

Related

Selecting rows in dc.datatables.js

I am trying to allow selection of a row in a datatable:
nasdaqTable /* dc.dataTable('.dc-data-table', 'chartGroup') */
.dimension(dateDimension)
// Data table does not use crossfilter group but rather a closure
// as a grouping function
.group(function (d) {
var format = d3.format('02d');
return d.dd.getFullYear() + '/' + format((d.dd.getMonth() + 1));
})
// (_optional_) max number of records to be shown, `default = 25`
.size(10)
// There are several ways to specify the columns; see the data-table documentation.
// This code demonstrates generating the column header automatically based on the columns.
.columns([
// Use the `d.date` field; capitalized automatically; specify sorting order
{
label: 'date',
type: 'date',
format: function(d) {
return d.date;
}
},
// Use `d.open`, `d.close`; default sorting order is numeric
'open',
'close',
{
// Specify a custom format for column 'Change' by using a label with a function.
label: 'Change',
format: function (d) {
return numberFormat(d.close - d.open);
}
},
// Use `d.volume`
'volume'
])
// (_optional_) sort using the given field, `default = function(d){return d;}`
.sortBy(function (d) {
return d.dd;
})
// (_optional_) sort order, `default = d3.ascending`
.order(d3.ascending)
// (_optional_) custom renderlet to post-process chart using [D3](http://d3js.org)
.on('renderlet', function (table) {
table.selectAll('.dc-table-group').classed('info', true);
});
This is the standard example drawn from https://github.com/dc-js/dc.datatables.js which integrates dc.js with datatables.js
However, I looked around for examples of implementation of rows being clickable and couldn't find any.
The goal I am trying to achieve is allow users to click the rows they would be interested in after playing around with the crossfilter dimensions and submitting them to a backend server.

How to create a one level nest in D3

I'm learning about nesting and have been looking at phoebe bright's explanations, where she writes:
var nested_data = d3.nest()
.key(function(d) { return d.status; })
.entries(csv_data);
gets this:
[
{
"key": "Complete",
"values": [
{
"id": "T-024",
"name": "Organisation list in directory",
"priority": "MUST",
},
{
When I try to do the same, in my console, if I can recreate it, looks like this:
Object
key: "1847"
values: Array [2]
0: Object
production: "A Mirror for Witches"
1: Object
production: "Sadlers Wells"
When I try to display the "Values" as text, all I get is [Object, object] in my html, where what I want is the production names.
How do I do this? I have tried nesting production as a key also, but this doesn't seem to work, and have also tried returning the index when returning the values, but can't get that to work either.
Any help I will really appreciate, thanks.
Here is my code
data.csv
year,production,company
1847,A Mirror for Witches
1847,Sadlers Wells
d3.csv("data.csv", function(csv_data){
var nested_data = d3.nest()
.key(function(d) { return d.year; })
.entries(csv_data)
console.log(nested_data);
var selection =
d3.select("body").selectAll("div")
.data(nested_data)
.enter()
selection.append("div")
.classed('classed', true)
.text(function(d){
return d.key;
});
d3.selectAll(".classed").append("div")
.text(function(d){
return d.values;
});
});
Here's a working plunk: http://plnkr.co/edit/0QuH8P9ujMdl0vWuQkrQ?p=preview
I added a few more lines of data to show it working properly.
The thing to do here is to add a second selection (I've called it production_selection) and bind data based off the first selection (year_selection): You use nested selections to show nested data.
First selection (show a div for each year, or key, in your nested data):
var year_selection = d3.select("#chart").selectAll("div")
.data(nested_data)
.enter().append("div")
...
Second selection (show all productions, or values, under that key):
var production_selection = year_selection.selectAll(".classed")
.data(function(d) { return d.values; })
.enter().append("div")
...
For the second selection, you just define the accessor function (d.values)

Flatten nested array created with d3.nest() to create stacked bar

I have data returned from a REST API in the following form.
[{
"created": "2014-06-01T11:21:47Z",
"is_good": false,
"amount": 10
},{
"created": "2014-06-01T12:01:00Z",
"is_good": false,
"amount": 12
},{
"created": "2014-06-02T10:00:00Z",
"is_good": true,
"amount": 8
},{
"created": "2014-06-02T08:00:00Z",
"is_good": false,
"amount": 3
},
...
]
In order to make a stacked bar chart, I thought the solution would be to use d3.nest() to rollup the amounts, first by date, then by is_good (the stacking category).
nestedData = d3.nest()
.key(function(d) { return d3.time.day(new Date(d.created)); })
.key(function(d) { return d.is_good; })
.rollup(function(leaves) { return {amount: d3.sum(leaves, function(d) { return d.amount; })}; })
.entries(jsonData);
That would probably be fine when drawing the chart following Mike Bostock's example here, but wouldn't work in a d3.layout.stack() call, because it requires the .values() to be the group iterable from which x and y is then calculated. That lead me to try the keys the other way around, but then drawing the chart itself becomes tricky.
So after all of that, I'm now wondering if there's a neat d3 way of flattening the nested values into something that resembles the datasets in almost all stacked bar chart examples.
Alternatively, perhaps I'm just not seeing how best to use the double nested data to create a stacked bar chart based on the examples.
Any help would be much appreciated.
I eventually decided to tackle this without using d3.layout.stack at all, and attempted to convert the double nested array into something resembling the example given. This is the code that will take that complex array and squash it down into something more manageable when drawing the chart.
data.forEach(function(d) {
var y0 = 0;
d.amounts = color.domain().map(function(is_good) {
return {is_good: is_good, y0: y0, y1: y0 += +d.values.filter(function(d) {
return d.key == is_good;
})[0].values.amount};
});
d.total = d.amounts[d.amounts.length - 1].y1;
});
Here's a working example.
I'm sure this isn't perfect, so if anyone has a better way of achieving the same result, I'd be interested to see it!

d3js: Dynamically updating node objects

I'm creating a force graph with nodes representing political candidates as circles. Each circle's radius corresponds to the candidate's approval rating (a percentage). I have data over three years. It's coming from a .CSV file. I'm working in d3.js, and I'm still very new.
What I'm trying to do is dynamically update each node object such that the radius reflects the currently-selected year. Loading the graph works fine: the first year (2013) works. But, when I switch to 2012, the DOM elements don't change: they retain their 2013 values. Here are the relevant snips of code:
After opening the CSV call:
data.forEach(function(d) {
var node = {
id: d.id,
name: d.candidate,
value: (parseInt(d["y"+year])),
radius: (parseInt(d["y"+year], 10))*3,
party: d.cand_party,
sex: d.gender
};
nodes.push(node);
});
And here's the update I'd like to do:
d3.select(window).on("keydown", function() {
switch (d3.event.keyCode) {
case 37: year = Math.max(year0, year - 1); break;
case 39: year = Math.min(year1, year + 1); break;
}
update ();
});
function update() {
title.text(year);
circles.transition(1500)
.attr("r", function(d) { return d.radius; });
};
The update() function is working, in that my title is switching to 2012, as desired. However, when I check each node's DOM info, I see that radius and value retain their 2013 values (the circles aren't changing size).
Any advice appreciated, thanks.
Posted too soon! In line with what Lars commented, I found this worked:
data.forEach(function(d) {
var node = {
id: d.id,
name: d.candidate,
value: (parseInt(d["y"+year])),
radius2014: (parseInt(d.y2014, 10))*3,
radius2013: (parseInt(d.y2013, 10))*3,
radius2012: (parseInt(d.y2012, 10))*3,
party: d.cand_party,
sex: d.gender
};
nodes.push(node);
});
And then calling things like so:
function update() {
title.text(year);
circles.transition(1500)
.attr("r", function(d) { return d["radius"+year]; });
};

Binding data appears to be working, but nothing is being appended

This is a follow-up to this question which I asked last week (since then I've changed a lot of things though so don't pay toooo much attention to the code I posted).
My data is organised like this:
dataset = {
'name': 'total',
'groups': [
{
'name': 'foo',
'children': [ {c1},{c2}...], // key: value pairings
'totals': { key1: val1, ....} // where val1 = sum of key1 from all children
},
// and so on
]
}
What I'm doing in D3 is this:
Make a table for each group
Append a row for the group totals
Append th's for those keys not included in 'totals'
Append more th's for each item in 'totals'
Then there is a function which adds the child rows/cells when the parent table is clicked
Item 4 is where I'm having trouble. Below are two scenarios which I have tried:
// (1) This works
parentTotals.selectAll('th.totals')
.data(tbs) // tbs is a list of the key names in 'totals'
.enter()
.append('th')
.attr('class', 'totals')
.text(function(d) { return d; });
// (2) This does not work
parentTotals.selectAll('th.totals')
.data(function(d) { return d.totals; }) // inherits a child from dataset.groups
.enter()
.append('th')
.attr('class', 'totals')
.text(function(d, i) { return d[tbs[i]]; });
The reason why I believe that the data is being bound correctly in scenario 2 is that if I put a console.log(d.totals); before return d.totals; I get a lovely Object { key1: val1, .... } for each member in the group. So if the data is being bound, why aren't any cells being appended? Thank you.
== EDIT ==
With the information provided by Scott I have made it work. If anyone's interested, this is what I did:
parentTotals.selectAll('th.totals')
.data(function(d) {
return Object.keys(d.totals).map(function(k, i) {
return d.totals[tbs[i]];
})
})
.enter()
.append('th')
.attr('class', function(d, i) { return tbs[i]; })
.text(function(d, i) {
return (d/1000).toFixed(1);
});
totals is an Object, not an Array. D3's data bind is based on arrays. You can get an array of key names from an object using: Object.keys(d.totals);.

Resources