I'm trying to plot only top(n) or least(n) values of a particular Crossfilter group in dc.js, but I'm not sure how to achieve this. I've checked it in console.log() that my group is returning values as needed, but how can I plot those top values in the chart?
groupForBandwidthConsumed = dimByOrgName.group().reduceSum(function (d) {
return d.bandwidthConsumed;
});
groupForBandwidthConsumed.top(Infinity).forEach(function (d) {
console.log(d.key, d.value);
});
I read that .data() function helps in doing this but it doesn't work with bar chart. Can somebody explain me how to do this?
Here is the JSFiddle for the bar chart I'm working on.
Well, I was able to sort it out myself using 'fake grouping' idea.
function getTops(source_group) {
return {
all: function () {
return source_group.top(2);
}
};
}
var fakeGroup = getTops(groupForBandwidthConsumed);
Then modified the group attribute as: .group(fakeGroup)... JSFiddle
Related
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.
I have 1 pie chart and 1 series line chart with brush. Currently when I brushing the pie chart updates, but shows the same result regardless of the selected range. How to make brush work correctly?
const pieChart = dc.pieChart('#piechart');
const chart = dc.seriesChart('#chart');
const groupParameter = 'markdown';
const data = d3.csvParse(d3.select('pre#data').text());
data.forEach(d => {
d.week = +d.week;
d.markdown = +d.markdown;
});
const ndx = crossfilter(data);
const pieDimension = ndx.dimension(d => d.itemCategory);
const dimension = ndx.dimension(d => [d.itemCategory, d.week]);
const pieGroup = pieDimension.group().reduceSum(d => d[groupParameter]);
const group = dimension.group().reduceSum(d => d[groupParameter]);
pieChart
.width(768)
.height(480)
.dimension(pieDimension)
.group(pieGroup)
.legend(dc.legend());
chart
.width(768)
.height(480)
.chart(c => dc.lineChart(c).curve(d3.curveCardinal))
.x(d3.scaleLinear().domain([27, 38]))
.brushOn(true)
.yAxisLabel(groupParameter)
.yAxisPadding('5%')
.xAxisLabel('Week')
.elasticY(true)
.dimension(dimension)
.group(group)
.mouseZoomable(true)
.seriesAccessor(d => d.key[0])
.keyAccessor(d => d.key[1])
.valueAccessor(d => d.value)
.legend(
dc
.legend()
.itemHeight(13)
.gap(5)
.horizontal(1)
.legendWidth(140)
.itemWidth(70)
);
chart.margins().left += 100;
dc.renderAll();
https://jsfiddle.net/qwertypomy/L37d01e5/#&togetherjs=HF15j0M5pH
It doesn't look like brushing was ever properly implemented for series charts.
The issue is that the dimension key for series chart is a 2-dimensional array, but a normal RangedFilter is applied, which doesn't understand these keys.
You can manually apply a filter handler which looks at the right part of the key:
chart.filterHandler(function(dimensions, filters) {
if (filters.length === 0) {
dimension.filter(null);
} else {
var filter = dc.filters.RangedFilter(filters[0][0], filters[0][1]);
dimension.filterFunction(function(k) {
return filter.isFiltered(k[1]);
});
}
return filters;
});
Actually, I'm not sure if there is an elegant way to fix this. I've started an issue to track it: https://github.com/dc-js/dc.js/issues/1471
EDIT: This doesn't work unless it's applied to both the series chart and all of its children. Horribly inefficient, but like so:
function filterHandler(dimensions, filters) {
if (filters.length === 0) {
dimension.filter(null);
} else {
var filter = dc.filters.RangedFilter(filters[0][0], filters[0][1]);
dimension.filterFunction(function(k) {
return filter.isFiltered(k[1]);
});
console.log('all',all.value());
}
return filters;
}
chart
.chart(c => dc.lineChart(c).curve(d3.curveCardinal).filterHandler(filterHandler))
.filterHandler(filterHandler);
New version of fiddle, with dataCount to show it's working now: https://jsfiddle.net/gordonwoodhull/qga6z1yu/39/
We really need a generalized version of https://github.com/dc-js/dc.js/pull/682 to apply filters only once, in a cooperative way between multiple charts which share the same dimension.
Right now, this will apply the filter four times every time the brush changes!
I am new to dc.js and facing issues in deciding dimensions and groups. I have data like this
this.data = [
{Type:'Type1', Day:1, Count: 20},
{Type:'Type2', Day:1, Count: 10},
{Type:'Type1', Day:2, Count: 30},
{Type:'Type2', Day:2, Count: 10}
]
I have to show a composite chart of two linecharts one for type Type1 and other for Type2. My x-axis will be Day. So one of my dimensions will be Day
var ndx = crossfilter(this.data);
var dayDim = ndx.dimension(function(d) { return d.Day; })
How the grouping will be done? If I do it on Count, the total count of a particular Day shows up which I don't want.
Your question isn't entirely clear, but it sounds like you want to group by both Type and Day
One way to do it is to use composite keys:
var typeDayDimension = ndx.dimension(function(d) {return [d.Type, d.Day]; }),
typeDayGroup = typeDayDimension.group().reduceSum(function(d) { return d.Count; });
Then you could use the series chart to generate two line charts inside a composite chart.
var chart = dc.seriesChart("#test");
chart
.width(768)
.height(480)
.chart(function(c) { return dc.lineChart(c); })
// ...
.dimension(typeDayDimension)
.group(typeDayGroup)
.seriesAccessor(function(d) {return d.key[0];})
.keyAccessor(function(d) {return +d.key[1];}) // convert to number
// ...
See the series chart example for more details.
Although what Gordon suggested is working perfectly fine, if you want to achieve the same result using composite chart then you can use group.reduce(add, remove, initial) method.
function reduceAdd(p, v) {
if (v.Type === "Type1") {
p.docCount += v.Count;
}
return p;
}
function reduceRemove(p, v) {
if (v.Type === "Type1") {
p.docCount -= v.Count;
}
return p;
}
function reduceInitial() {
return { docCount: 0 };
}
Here's an example: http://jsfiddle.net/curtisp/7frw79q6
Quoting Gordon:
Series chart is just a composite chart with the automatic splitting of the data and generation of the child charts.
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)
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!