D3.js graph displaying only one dataset - d3.js

I having trouble getting the data on the graph. I only get one data set bar in.
You can see it here : http://infinite-fjord-1599.herokuapp.com/page2.html
But when I console.log the foreach for it. It displays all the objects:
data.days.forEach(function(d) {
d.ages = ageNames.map(function(name) { return {name: name, value: +d.values[name]}; });
console.log(d.ages);
});
The code on jsFiddle. http://jsfiddle.net/arnir/DPM7y/
I'm very new to d3.js and working with json data so I'm kinda lost here. I took the example of the d3.js example site and modified it.

See the updated fiddle here: http://jsfiddle.net/nrabinowitz/NbuFJ/4/
You had a couple of issues here:
Your x0 scale was set to a domain that displayed a formatted date, but when you were calling it later you were passing in d.State (which didn't exist, so I assume it was a copy/paste error). So the later days were being rendered on top of the first day.
There was a mismatch between the way you were selecting the group g element and the way you were appending it - not actually a root cause here, but likely to cause problems later on.
To fix, move the date formatting to a different function:
function formatDate(d) {
var str = d.modified;
d.date = parseDate( str.substring(0, str.length - 3) );
var curr_month = d.date.getMonth() + 1;
var curr_date = d.date.getDate();
var nicedate = curr_date + "/" + curr_month;
return nicedate;
}
and then use the same function for the scale setup:
x0.domain(data.days.map(formatDate));
and the transform (note the fix in the selector and class here as well):
var state = svg.selectAll("g.day")
.data(data.days)
.enter().append("g")
.attr("class", "day")
.attr("transform", function(d) {
return "translate(" + x0(formatDate(d)) + ",0)";
});

There are a couple of small things that threw you off. First, the domain of the x0 scale should be an array of datetime objects, not an array of strings:
x0.domain(data.days.map(function(d) {
var str = d.modified;
d.date = parseDate( str.substring(0, str.length - 3) );
return d.date;
}));
will return datetimes, not strings like it was before (minor nitpick: really not a fan of this use of map, I would add the date property separately in a forEach function as the data is loaded).
Second, x0 needs to be passed a property that actually exists:
var state = svg.selectAll(".state")
.data(data.days)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.date) + ",0)"; });
Before, you were using x0(d.state) which is a vestige from the grouped bar example (several others still exist; I've changed the minimum to get your project working). Since the value didn't exist, all of the rectangles were getting drawn over each other.
Additionally, we need to format the axis labels so we aren't printing out the entire datetime object all over the labels:
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom")
.tickFormat(d3.time.format("%m-%d"));
Finally, I noticed that the newest dates were being printed on the left instead of the right. You could sort the results of data.days.map( ... ) to fix that, I just reversed the range of x0:
var x0 = d3.scale.ordinal()
.rangeRoundBands([width, 0], .1);
fixed files

Related

D3 - Stacked bar chart dual axis with line chart

I have a stackblitz here - https://stackblitz.com/edit/d3-stacked-trend-bar-positioned-months-4sqvwd?embed=1&file=src/app/bar-chart.ts&hideNavigation=1
I'm using D3 to create a stacked bar chart in Angular
I now also need to have a line graph on the same chart.
I think the best way to do this is with a dual axis.
I have the second axis working but can't get the line to work.
Can anyone point me the direction to get this working
The line function (valueline in your case) doesn't seem like its defined correctly as it's missing the accessor functions. Here are the docs for the same.
I couldn't fork your code but here's a snippet (containing the drawLine method) you can try:
private drawLine(linedata:any){
var that = this;
var valueline = d3.line().x(function(d, i) {
return that.x1(d.date);
// return that.x0(d.date) + 0.5 * that.x0.bandwidth();
}).y(function(d) {
return that.y1(d.value);
});
this.x1.domain(this.data.map((d:any)=>{
return d.date
}));
this.y1.domain(d3.extent(linedata, function(d) {
return d.value
}));
this.lineArea.append("path")
.data([linedata])
.attr("class", "line").style('stroke-width', 2)
.attr("d", valueline);
}
It works and I also have included a commented line for the x attribute which matches the way you're offsetting the bars. And another suggestion would be to use the same x0 scale as the newly defined x1 has the same domain as x0. Hope this helps.

Heatmap and sparklines

I created an heatmap using this example and this data:
NAME,YEAR,M1,M2
A,2000,20,5
B,2000,30,1
C,2000,,10
D,2000,,88
E,2000,,21
F,2000,84,3
G,2000,,64
A,2001,44,48
B,2001,15,51
C,2001,20,5
D,2001,95,2
E,2001,82,9
F,2001,,77
G,2001,3,80
A,2002,8,99
B,2002,92,52
C,2002,62,
D,2002,41,
E,2002,66,
F,2002,21,21
G,2002,62,4
A,2003,2,5
B,2003,89,78
C,2003,9,
D,2003,7,9
E,2003,2,45
F,2003,92,58
G,2003,2,14
A,2004,2,55
B,2004,89,58
C,2004,9,55
D,2004,7,59
E,2004,2,70
F,2004,92,
G,2004,2,
Now I would like to add to the right of the heatmap a sparkline for each row, so there must be a sparkline associated with A, to B, etc.
And I wish they were positioned right next to each other.
To make the sparklines I saw this example.
This is the result: PLUNKER.
As you can see, I can't get the data correctly from the data.csv file to create the sparklines. Also I don't know how to place them in the correct position.
I tried this way but without success.
var sparkSvg = d3.select("#container-sparkline")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.data(dataNest)
.enter()
.append("path")
.attr("class", "sparkline-path")
.attr("d", function(d) {
console.log("i");
console.log(d);
});
Also I'm not sure using two div is the correct way to put a chart near another chart.
Anyone would know how to help me?
Approach:
I've created a sparkline for every name in data set with values on x axis as a year and y as a m2 value from data set. For the demo purposes I've hardcoded number of years to 5 so x axis have only 5 values, but that can be computed with some additional script based on input data.
I've also added tome padding for sparkline container so they're aligned with the heatmap
Code:
As you can see in the plunker I've introduced a function to group data by name, so for each name we have an array with objects:
var groupBy = function(array, key) {
return array.reduce(function(a, v) {
(a[v[key]] = a[v[key]] || []).push(v);
return a;
}, {});
};
// data grouped by name
var groupedData = groupBy(data, 'name');
Since we assumed for demo purposes that X axis has fixed number of values we need to find max value for Y axis to properly scale charts. To do that I reduce array of values to get only m2 values and find a max number whthin that array:
var maxYvalue = Math.max(...data.map(function(d){return Number(d.m2)}));
Now we can create scales for the sparklines
var x = d3.scaleLinear().domain([0, 4]).range([0, 60]);
var y = d3.scaleLinear().domain([0, maxYvalue]).range([2, itemSize-2 ]);
I'm assuming that chart have width of 60px and height of itemSize, I also introduce 2px of vertical padding so its easier to read those sparklines being on next to each-other.
Now we can define d3.line(as you already did in your plunker) which we'll use fro rendering sparklines .
var line = d3.line()
.x(function(d, i) { return x(i); })
.y(function(d) { return y(d); })
And last step is to render sparklines inside "#container-sparkline" container. To do that we can iterate over every array in groupedData and render sparkline for each name:
// for each name render sparkline
Object.keys(groupedData).forEach(function(key){
const sparkData = groupedData[key].map(function(datum){
return Number(datum['m2']);
})
var sparkSvg = d3.select("#container-sparkline")
.append("svg")
.attr("width", "100%")
.attr("height", itemSize-1)
.append("path")
.attr("class", "sparkline-path")
.attr("d", line(sparkData));
})
I've also slightly changed styles for #container-sparkline and added borders for sparkline svg's. I hope this is what you've asked for.
Here you can find your plunker with my changes
http://plnkr.co/edit/9vUFI76Ghieq4yZID5B7?p=preview

Reload nested data in D3.js

I do not manage to update a bar-chart with nested data in D3.js with new data.
I have nested data of the form:
data = [[1,2,3,4,5,6],[6,5,4,3,2,1]];
I managed to visualize the data by first appending a group for every subarray.
In the groups I then add the arrays as data (simplified):
function createGraph(l, svg){
var g = svg.selectAll("g")
.data(l)
.enter().append("g");
var rect = g.selectAll("rect)
.data(function(d){return d;})
.enter().append("rect")
. ...
}
However, when call the function again with different data, nothing happens.
It seems like in the second row, the rects do not get updated.
I have created a full example over at jsBin: http://jsbin.com/UfeCaGe/1/edit?js,output
A little more explanation of Lars' bug-catch, since I'd already started playing around...
The key was in this section of the code:
var group = svg.selectAll("g")
.data(l)
.enter().append("g");
The variable group is assigned the enter selection, not the raw selection. Then in the next line:
var bar = group.selectAll("rect")
.data(function(d){
return d;
});
You end up defining bar as only the rectangles that are children of just-entered groups. So even though you were handling update correctly for the rectangles, that whole section of code wasn't even running. You need to save the group selection before branching the chain to deal with entering groups:
var group = chart.selectAll("g")
.data(dt);
group.enter().append("g");
var bar = group.selectAll("rect")
.data(function(d){
return d;
});
Also, you're missing a j in your function declaration in your update. And you can reduce code duplication by putting your rectangle update code after your rectangle enter code, and then any attributes that get set in the update don't have to be specified for enter. (Some older examples don't use this pattern, because the original versions of d3 didn't automatically transfer newly-entered elements to the main selection.)
// enter
bar.enter().append("rect")
.attr("fill", function(d,i,j){
return colors(j);})
.attr("height", 0);
// update
bar.attr("transform", function(d, i, j) {
x = "translate("+(i*2.2*w+j*w)+",0)";
return x; })
.transition()
.duration(750)
.attr("width", w)
.attr("height", function(d){return d*10;});

D3.js: calculate x-axis time scale for bar graph?

I have the following dataset:
var data = [
{
"air_used": 0.660985,
"datestr": "2012-12-01 00:00:00",
"energy_used": 0.106402
},
{
"air_used": 0.824746,
"datestr": "2013-01-01 00:00:00",
"energy_used": 0.250462
} ...
]
And I want to draw a bar graph (for air_used) and line graph (for energy_used) that look like this:
My problem is that at the moment, with the x-scale I'm using, the graph looks like this - basically the bars are in the wrong position, and the last bar is falling off the chart:
Here is a JSFiddle with full code and working graph: http://jsfiddle.net/aWJtJ/4/
To achieve what I want, I think I need to amend the x-scale so that there is extra width before the first data point and after the last data point, and so that the bars are all shifted to the left by half the width of each bar.
Can anyone help me figure out what I need to do with the x-scale?
I've tried adding an extra month to the domain - that stops the last bar falling off the end of the graph, but it also adds an extra tick that I don't want, and it doesn't fix the position of the line graph and ticks.
If possible I want to continue to a time scale for the x-axis, rather than an ordinal scale, because I want to use D3's clever time-based tick formatters and date parsers, e.g. xAxis.ticks(d3.time.weeks, 2).
Expand your domain to be +1 and -1 month from the actual extent of your data. That will pad the graph with the extra months on either side and then update the bar width to add 2 to the count of data elements.
var barRawWidth = width / (data.length + 2);
See this fiddle: http://jsfiddle.net/reblace/aWJtJ/6/
If you want to hide the lower and upper boundary months, you can hack it like this: http://jsfiddle.net/reblace/aWJtJ/7/ by just adding and subtracting 20 days instead of a whole month, but there are probably more elegant ways to do it.
var xExtent = d3.extent(data, function(d) { return d.date; });
var nxExtent = [d3.time.day.offset(xExtent[0], -20), d3.time.day.offset(xExtent[1], 20)];
x.domain(nxExtent);
As pointed out in the comments, I think the best approach is to use d3.scale.ordinal. Note that using it doesn't prevent you from using d3.time parsers, but you need to take into account the bar width to align the line with the bars.
An example solution is here:
http://jsfiddle.net/jcollado/N8tuR/
Relevant code from the solution above is as follows:
// Map data set to dates to provide the whole domain information
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.date;
}))
.rangeRoundBands([0, width], 0.1);
...
// Use x.rangeBand() to align line with bars
var line = d3.svg.line()
.x(function(d) { return x(d.date) + x.rangeBand() / 2; })
.y(function(d) { return y(d.energy_used); });
...
// Use x.rangeBand() to set bar width
bars.enter().append("rect")
.attr("class", "air_used")
.attr("width", x.rangeBand())
...
Note that date parsing code has been moved up to have d.date available when creating the x scale. Aside from that, d3.time statements have not been modified at all.

Accessing elements associated with data using D3 after they have been generated

I have a dataset, each item has been linked to svg rects using D3.
var bars = svg_content.selectAll("rect")
.data(dataset);
.enter()
.append("rect")
Assume the generation is complete (i.e. the .enter() process is complete and the rects have been generated).
How would I access the rect associated with a specific index of that dataset (for instance, the rect linked to the third piece of data)?
You can use selection.filter or the function form of the commonly used selection.select depending on your needs:
var third = selection.filter(function(d, i) { return i == 2; });
// Equivalently
var third = selection.select(function(d, i) { return i == 2; });
There are a few ways to do this. Generally, in d3, you tend to access the data from within a selection. So you would see something like:
var bars = svg_content.selectAll("rect")
.data(dataset);
.enter()
.append("rect")
.attr('class', function(d) { return d.myName; });
Here d is the data item from dataset that is associated with a particular rect. That code would class each rect with the "myName" property of each data item.
Let's say some you want to place one of these rects specially. One with myName='aName'. We will select that rectangle and set the 'tranform' attribute based on the associated data.
svg.content.selectAll('rect.aName')
.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + 20 ')'; })
Note that in both cases you can also access the item's index and if it's relevant also the parent index (use function(d,i,j) {...})
Finally, though I don't encourage it in general, I have for unit tests directly accessed the data associated with an element with __data__. For example with jQuery:
$.find("svg rect.aName")[0].__data__;
You can play with a quick fiddle here

Resources