dc.js Adding count to the xAxis label - dc.js

This is related to the dc.js boxPlot example. Is there an easy way to add the number of data values to each xAxis label?
exp-1 [10]
I know I can configure the xAxis label by:
chart.xAxis().tickFormat(function(k) {return k + ' []';});
But I only have access to the key and not the value count.

I would do this by keeping a map from keys to counts, refreshing it before the chart is rendered or redrawn:
var counts = {};
function get_counts() {
speedArrayGroup.all().forEach(function(kv) {
counts[kv.key] = kv.value.length;
});
}
chart.on('preRender', get_counts)
.on('preRedraw', get_counts);
Now that we're sure counts is initialized whenever the axis is drawn, we can use it in the tickFormat:
chart.xAxis().tickFormat(function(k) {return k + ' [' + counts[k] + ']';});
Not so interesting with the standard example, but it works:

Related

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

No data in area chart

I have an area chart I am using from http://bl.ocks.org/mbostock/3883195 with a simple modification of using json data.
The json is returned without issue, and no other errors present themselves, however, no chart is displayed.
The x and y axes are displayed, but no chart.
jsfiddle.net/oqc19yff/
Any pointers welcome.
Kev
This is incorrect:
x.domain(d3.extent(data, function(d) { return d.event_time; }));
Reason:
Because x is an ordinal scale.
Instead it should have been:
//declare a variable array
var k = [];
data.forEach(function(d) {
d.event_time = d.event_time;
d.total = +d.total;
k.push(d.event_time);//in that array store all the event_time
});
Now set that array as domain of x.
x.domain(k);
working code here

How to add labels to c3.js scatter plot graph?

Is it possible to add labels to scatter plot points in c3.js like in this google charts example?
https://google-developers.appspot.com/chart/interactive/docs/gallery/bubblechart#javascript
c3 doesn't support this currently - https://github.com/masayuki0812/c3/issues/481. But you can easily add the functionality - just loop through the chart series and points and add the labels as necessary.
var labels = [
['AA', 'BB', 'CC', 'DD', 'EE', 'FF', 'GG', 'HH'],
['ZA', 'ZB', 'ZC', 'ZD', 'ZE', 'ZF', 'ZG', 'ZH']
];
// series
var series = chart.internal.main
.selectAll('.' + c3.chart.internal.fn.CLASS.circles)[0];
// text layers
var texts = chart.internal.main
.selectAll('.' + c3.chart.internal.fn.CLASS.chartTexts)
.selectAll('.' + c3.chart.internal.fn.CLASS.chartText)[0]
series.forEach(function (series, i) {
var points = d3.select(series).selectAll('.' + c3.chart.internal.fn.CLASS.circle)[0]
points.forEach(function (point, j) {
d3.select(texts[i])
.append('text')
.attr('text-anchor', 'middle')
.attr('dy', '0.3em')
.attr('x', d3.select(point).attr('cx'))
.attr('y', d3.select(point).attr('cy'))
.text(labels[i][j])
})
});
Fiddle - http://jsfiddle.net/6phuuans/
Currently C3.js doesnt provide us with the option to add labels to a scatter plot chart. But the following method can be used to add responsive data labels:
After the chart is rendered (in the "onrendered" property of the chart), identify the data points ( tags) and add tags with the x and y coodinates picked from the relevant circle, as the tags sibling.
Code Snippet:
onrendered: function(){
// get the parent of the the <circles> to add <text as siblings>
var g = d3.selectAll('.c3-circles');
//Get all circle tags
var circles = d3.selectAll('circle')[0];
//go to each circle and add a text label for it
for(var i = 0; i < circles.length; i++){
//fetch x-coordinate
var x = $(circles[i])[0].cx;
//fetch y-coordinate
var y = $(circles[i])[0].cy;
//create and append the text tag
g.append('text')
.attr('y', y.baseVal.value - 15) // (-15) places the tag above the circle, adjust it according to your need
.attr('x', x.baseVal.value)
.attr('text-anchor', 'middle')
.attr('class', 'c3-text c3-text-'+i)
.text(data[i].<data point key>) // the text that needs to be added can be hard coded or fetched for the original data.
//Since I am using a JSON to plot the data, I am referencing it and using the key of the value to be shown.
}
}
This will add the label, but on resize , multiple data labels will be plotted. To handle that, on the charts resize, we must remove the previous data labels (in the "onresize" property of the chart).
Code Snippet:
onresize: function () {
$('.c3-shapes.c3-circles text').remove();
}

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.

D3.js graph displaying only one dataset

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

Resources