dc.js: include all bars when count is zero - javascript

I need to include all bars in the graph. Example: I have five bars (1,2,3,4,5) with reduceCount from score. When all bars have values, the graph is fine.
But, when bars don't have values (in this case, one):
I tried adding .y(d3.scale.ordinal().domain([1, 2, 3, 4, 5])) but this method doesn't exist in a rowchart graph.

You can use a "fake group" to ensure that bins exist even when there aren't any values to fill them.
From the FAQ:
function ensure_group_bins(source_group) { // (source_group, bins...}
var bins = Array.prototype.slice.call(arguments, 1);
return {
all:function () {
var result = source_group.all().slice(0), // copy original results (we mustn't modify them)
found = {};
result.forEach(function(d) {
found[d.key] = true;
});
bins.forEach(function(d) {
if(!found[d])
result.push({key: d, value: 0});
});
return result;
}
};
};
Use it like this:
var mod_group = ensure_group_bins(your_group, 1, 2, 3, 4, 5);
chart.group(mod_group)
I wouldn't use .data() here because it can interfere with capped charts, of which the row chart is one. (Although it's entirely reasonable to expect it to work.)

Related

How to sort c3.js bar chart

I was looking forward to sorting the bars in the bar chart (not stacked bar) using c3.js. But could not find any suitable way, there is an option mentioned below but that's not applicable for the bar chart.
data: {
order: 'asc'
}
In my case, all data are coming dynamically and rendering through c3.js to make a bar chart.I was looking for a sort like https://bl.ocks.org/mbostock/raw/3885705/
You were on the right track with your jsfiddle but as the data passed to c3.generate() is an array of datasets then you cannot just call data.sort().
EDIT
For this specific case, where your data is in the form you described in your comments, this would be a suitable method.
I primarily used array functions like slice, splice, map and sort. These are key functions to gain familiarity with if you are manipulating and plotting data. The mozzila docs are a great point to start.
You should also note which functions modify the array they are called on and which return a new array; Mutating your data when you did not intend to can often cause hard-to-spot bugs.
var data = [
["a", "b", "c"],
['data1', "30", " 200", " 100"]
]
// declare a function to control variable scope
var sortData = function(unsortedData) {
// deep copy array to avoid modification of input array
var sorted = unsortedData.map(function(row) {
// use slice to copy this array
return row.slice()
})
// remove the dataname
var name = sorted[1].splice(0, 1);
// produce an array of data points [[x1,y1],[x2,y2]...]
var datapoints = sorted[1].map(function(d, i) {
// use index in map function to pull out name
// return array for datapoint [x,y]
return [sorted[0][i], d];
});
//sort datapoints
var sortedData = datapoints.sort(function(a, b) {
return a[1] - b[1];
});
// map back into separate x and y data
sorted[1] = sortedData.map(function(point, i) {
// assign x value to data[0] element
sorted[0][i] = point[0];
// return the y data point
return point[1];
});
// add the dataname back into the y data
sorted[1] = name.concat(sorted[1]);
// add the 'x' label name to x-values
sorted[0].splice(0, 0, 'x')
// return the sorted array
return sorted
}
var chart = c3.generate({
data: {
x: 'x',
columns: sortData(data),
type: 'bar',
},
axis: {
x: {
type: 'category' // this needed to load string x value
}
}
})
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.18/c3.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.18/c3.js"></script>
<div id="chart"></div>

Losing data when rendering rowchart using dc.js

I lose data when creating a dc.js rowchart.
var ndx = crossfilter(data);
var emailDimemsion = ndx.dimension(function(d) {
return d.email;
});
var emailGroup = emailDimemsion.group().reduce(
function(p, d) {
++p.count;
p.totalWordCount += +d.word_count;
p.studentName = d.student_name;
return p;
},
function(p, d) {
--p.count;
p.totalWordCount -= +d.word_count;
p.studentName = d.student_name;
return p;
},
function() {
return {
count: 0,
totalWordCount: 0,
studentName: ""
};
});
leaderRowChart
.width(600)
.height(300)
.margins({
top: 0,
right: 10,
bottom: 20,
left: 5
})
.dimension(emailDimemsion)
.group(emailGroup)
.elasticX(true)
.valueAccessor(function(d) {
return +d.value.totalWordCount;
})
.rowsCap(15)
.othersGrouper(false)
.label(function(d) {
return (d.value.studentName + ": " + d.value.totalWordCount);
})
.ordering(function(d) {
return -d.value.totalWordCount
})
.xAxis()
.ticks(5);
dc.renderAll();
The fiddle is here, https://jsfiddle.net/santoshsewlal/6vt8t8rn/
My graph comes out like this:
but I'm expecting my results to be
Have I messed up the reduce functions somehow to omit data?
Thanks
Unfortunately there are two levels of ordering for dc.js charts using crossfilter.
First, dc.js pulls the top N using group.top(N), where N is the rowsCap value. Crossfilter sorts these items according to the group.order function.
Then it sorts the items again using the chart.ordering function.
In cases like these, the second sort can mask the fact that the first sort didn't work right. Crossfilter does not know how to sort objects unless you tell it what field to look at, so group.top(N) returns some random items instead.
You can fix your chart by making the crossfilter group's order match the chart's ordering:
emailGroup.order(function(p) {
return p.totalWordCount;
})
Fork of your fiddle: https://jsfiddle.net/gordonwoodhull/0kvatrb1/1/
It looks there is one student with a much longer word count, but otherwise this is consistent with your spreadsheet:
We plan to stop using group.top in the future, because the current behavior is highly inconsistent.
https://github.com/dc-js/dc.js/issues/934
Update: If you're willing to use the unstable latest version, dc.js 2.1.4 and up do not use group.top - the capping is determined by chart.ordering, capMixin.cap, and capMixin.takeFront only.

Row Chart grouping on two text dimensions [duplicate]

I need to create a rowchart in dc.js with inputs from multiple columns in a csv. So i need to map a column to each row and each columns total number to the row value.
There may be an obvious solution to this but i cant seem to find any examples.
many thanks
S
update:
Here's a quick sketch. Apologies for the standard
Row chart;
column1 ----------------- 64 (total of column 1)
column2 ------- 35 (total of column 2)
column3 ------------ 45 (total of column 3)
Interesting problem! It sounds somewhat similar to a pivot, requested for crossfilter here. A solution comes to mind using "fake groups" and "fake dimensions", however there are a couple of caveats:
it will reflect filters on other dimensions
but, you will not be able to click on the rows in the chart in order to filter anything else (because what records would it select?)
The fake group constructor looks like this:
function regroup(dim, cols) {
var _groupAll = dim.groupAll().reduce(
function(p, v) { // add
cols.forEach(function(c) {
p[c] += v[c];
});
return p;
},
function(p, v) { // remove
cols.forEach(function(c) {
p[c] -= v[c];
});
return p;
},
function() { // init
var p = {};
cols.forEach(function(c) {
p[c] = 0;
});
return p;
});
return {
all: function() {
// or _.pairs, anything to turn the object into an array
return d3.map(_groupAll.value()).entries();
}
};
}
What it is doing is reducing all the requested rows to an object, and then turning the object into the array format dc.js expects group.all to return.
You can pass any arbitrary dimension to this constructor - it doesn't matter what it's indexed on because you can't filter on these rows... but you probably want it to have its own dimension so it's affected by all other dimension filters. Also give this constructor an array of columns you want turned into groups, and use the result as your "group".
E.g.
var dim = ndx.dimension(function(r) { return r.a; });
var sidewaysGroup = regroup(dim, ['a', 'b', 'c', 'd']);
Full example here: https://jsfiddle.net/gordonwoodhull/j4nLt5xf/5/
(Notice how clicking on the rows in the chart results in badness, because, what is it supposed to filter?)
Are you looking for stacked row charts? For example, this chart has each row represent a category and each color represents a sub-category:
Unfortunately, this feature is not yet supported at DC.js. The feature request is at https://github.com/dc-js/dc.js/issues/397. If you are willing to wade into some non-library code, you could check out the examples referenced in that issue log.
Alternatively, you could use a stackable bar chart. This link seems to have a good description of how this works: http://www.solinea.com/blog/coloring-dcjs-stacked-bar-charts

Calculate Max of Sum Product of D3 array

I'm reading a csv file, and need to compute two figures from this data using D3.js or normal JavaScript:
This might be able to be done in one step, but I've broken it down for the purposes of explanation:
Once my data is read in, I need to iterate through each of the columns, labelled "one" to "ten"
(the length of this data is an unknown length, so it might go up to twelve or twenty),
...each time multiplying each column which comes after "multiplier" by variable called "multiplier"
(in the data, I gave it arbitrary values of 1.5, 1, 0,5 to make reading visually clearer).
This gives a new grid of figures from which a maximum score and minimum score of each of these new figures must be calculated for each ID from 1 to n. So each ID will have a max and minimum. I need to know the maximum and minimum of these new scores across the entire data returned as variables.
The data is read in:
d3.csv("data.csv", function(csv) {
var mydata = bars
.selectAll("rect")
.data(csv)
.enter()};
The example data appears as:
ID,total,mutiplier,one,two,three,four,five,six,seven,eight,nine,ten
1,16500,1.5,0.362,0.37,0.1,0.101,0.035,0.362,0.37,0.1,0.101,0.035
2,61000,1,0.426,0.382,0.115,0.084,0.053,0.426,0.382,0.115,0.084,0.053
3,48700,1.5,0.156,0.531,0.195,0.399,0.14,0.156,0.149,0.106,0.399,0.14
4,33000,0.5,0.462,0.409,0.149,0.106,0.149,0.106,0.085,0.1,0.106,0.051
5,8000,0.5,0.327,0.316,0.085,0.1,0.085,0.1,0.057,0.245,0.1,0.057
6,12760,1,0.149,0.195,0.057,0.245,0.057,0.245,0.119,0.114,0.245,0.08
This original data cannot be replaced as I reference it later.
So from this data, after iterating through all columns, and taking the max and min from each over the whole data --- the minimum is 0.003535 and the maximum is 3.8875575
...and I need the function to return a var min and var max for next calculation.
Hope someone out there can help!
One way you can load your data as a text, and latter use d3.csv.parseRows to parse your CSV as an array of arrays. So just take the slice, ignoring the first 3 columns.
d3.text('data.csv', function(text)
{
var rows = d3.csv.parseRows(text, function(row, index)
{
// skip header, coerce to Number values
if(index > 0)
{
return row.map(Number);
}
});
var extent = rows.reduce(function(result, row)
{
return d3.extent(result.concat(row.slice(3).map(function(value)
{
return value * row[2];
})));
}, [NaN, NaN]);
var min = extent[0];
var max = extent[1];
});
Other way if array of objects is more convenient structure for later plotting, you can do the following.
var nonMeasureColumns = {'ID': 0, 'total': 0, 'multiplier': 0};
d3.csv('data.csv')
.row(function(row)
{
for(var key in row)
{
row[key] = Number(row[key]);
}
return row;
})
.get(function(error, rows)
{
var extent = rows.reduce(function(result, row)
{
return d3.extent(result.concat(d3.map(row).entries()
.filter(function(entry)
{
return !(entry.key in nonMeasureColumns);
})
.map(function(entry)
{
return entry.value * row['multiplier'];
})
));
}, [NaN, NaN]);
var min = extent[0];
var max = extent[1];
});

jQuery each going outside of function

I have a jqPlot chart that I want to add links on and I believe I figured out a way to do it using an array such as [[[1,2,"http://google.com"]],[[2,3,"http://yahoo.com]]] however, when I try to load this via XML, jQuery, and Ajax it doesn't quite work.
I believe that the problem lies within the .each clauses found in this code:
function getBars(xml)
{
var categoryid = 1;
var bars = [];
$(xml).find("category").each(
function()
{
bars.push(loadBars(categoryid,$(this)));
categoryid++;
});
return bars;
}
function loadBars(categoryid,xml)
{
var bar = [];
var bars = [];
$(xml).find("bar").each(function()
{
bar.push(parseInt(categoryid));
bar.push(parseInt($(this).attr("size")));
bar.push($(this).attr("link"));
bars.push(bar);
});
$("#debug").append("\nBAR:")
debug2dArray(bars);
return bars;
}
The XML looks like:
<?xml version="1.0"?>
<chart>
<category>
<bar size="20" link="http://google.com"/>
</category>
<category>
<bar size="70" link="http://yahoo.com" />
</category>
</chart>
Here is a jsFiddle
Update
After updating the variables to be non-global, the chart now displays right, but two of the same values are still being added to the array. Code has been updated to reflect changes.
I haven't digested your whole code yet, but one really fatal pitfall you're doing is using variables in your functions that haven't been declared with var (I'm particularly looking at how you've used your bar variable on both functions).
When you use a variable without declaring it with var like you're doing here, you're bringing the variable to a global visibility. That means that that variable is the same variable used (most) everywhere in your code. The same bar in the first function is the same bar in the second.
When your two functions start, the first thing it does is clear the bar variable (i.e. bar = [];). Since they're sharing bar references, calling one function effectively nullifies what the other did.
Is this your intention? If not (or even so), you should declare your variable with var:
var categoryId = 1,
bar = [];
In addition to the lack of var, you are returning variables at the end of the each iterators, instead of the end of the function. Here's a working fiddle: http://jsfiddle.net/fwRSH/1/
function loadBars(categoryid, xml) {
var bar = [];
var bars = [];
$(xml).find("bar").each(function() {
bar.push(parseInt(categoryid, 10));
bar.push(parseInt($(this).attr("size"), 10));
bar.push($(this).attr("link"));
bars.push(bar);
//$("#debug").append("\nBAR:"); //not defined in fiddle, commented out
//debug2dArray(bars); //not defined in fiddle, commented out
});
return bars; //moved from end of "each" iterator to here.
}
function getBars(xml) {
var categoryid = 1;
var bars = [];
$(xml).find("category").each(function() {
bars.push(loadBars(categoryid, $(this)));
categoryid++;
});
return bars;
}
$(document).ready(function() {
var bars = [];
$("div#barchart").css("background-color", "#F00");
$("div#barchart").css("height", "200px");
$("div#barhcart").css("width", "400px");
//moved for debugging
bars = getBars($('div#xmlDI'));
/* returns:
* [
* [
* [1, 20, "http://google.com"]
* ],
* [
* [2, 70, "http://yahoo.com"]
* ]
* ]
*/
$.jqplot("barchart", bars, {
seriesDefaults: {
renderer: $.jqplot.BarRenderer,
rendererOptions: {
fillToZero: true
}
},
axes: {
// Use a category axis on the x axis and use our custom ticks.
xaxis: {
renderer: $.jqplot.CategoryAxisRenderer,
ticks: ['one', 'two'],
autoscale: true
},
yaxis: {
autoscale: true
}
}
});
});​
None of your variables are declared using var, particularly the bars array. This causes them to be implicitly global, and you overwrite the variable every time you call loadBars.
I am not sure how you want your graph to look like. Because the data you provide to the graph, in general terms, is 'correctly' displayed. If you would write it in the following way:
[
[[1, 30, "http://google.com"], [2,0,""]],
[[1,0,""],[2, 40, "http://yahoo.com"]]
]
...it would give exactly the same results, the library just assumes that the data which is not provided for a particular series is 0 and this is how it is treated as visible here.
Since you do not like it this way my guess is that you made a formatting error in your data variable, as we can see here the 'gap' is gone.
Therefore, I think that the below is the format you are after:
[[
[1, 30, "http://google.com"],
[2, 40, "http://yahoo.com"]
]]
Additionally, as it goes to clicking on a bar of a bar chart you could find useful the answer to the problem. There you could see how to capture the click and how to open a URL. You would just need to slightly adopt it to your need as I used a global array of URLs.
Code to parse the XML:
var bars = [], cat = 0;
$.ajax({
type: 'GET',
url: 'plotlinks.xml',
dataType: "xml",
cache: true,
success: function(data, textStatus, jqXHR) {
$(data).find("category").each( function() {
var barSet = [cat];
$(this).find("bar").each(function() {
var $elt = $(this);
barSet.push([$elt.attr('size'),$elt.attr('link')]);
});
cat++;
bars.push(barSet);
});
// bars is an array; each element is an array.
// The first element in the inner array is the
// category "index" (0,1,2,...). All other
// elements represent a link for that category.
// Those elements are arrays of [size,url].
alert($.stringifyJSON(bars));
}
});
Resulting json:
[[0,
["20","http://google.com"]
],
[1,
["70","http://yahoo.com"]
]
]

Categories

Resources