I'd like to know if dc.js can render a bar chart without dimension, for example, a bar chart displaying the reduce result of ALL data.
When we want to render a bar chart we always do like this:
chart.width(400)
.height(200)
.dimension(...some Dimension here...)
.group(...some Group here...)
.valueAccessor(function (p) {
return p.value.someValue;
})
.x(d3.time.scale().domain(["Global"]))
.y(d3.scale.linear().domain([0, 100]))
.yAxis().tickFormat(function (v) {
return v + "%";
});
chart.render();
My question is what to put in the dimension and group in order to achieve my purpose? The documentation says that dimension() is mandatory.
Thanks very much!
The easiest way to do this is probably to add an additional attribute to your data that has the same value for everything and use that as the dimension.
Related
I am trying to create a stack bar chart in dcjs. The dcjs stack bar examples are quite clear the huge difference from the barchart with that of stack is that the stack function. The stack function takes the same group as input and it can take third parameter as function which decides by which value it has to split. I rather want a dimension to be split the entire bar chart.
Lets say the following data point is something like this
data = [
{activity:"A1",time_taken:10,activity_group:"Master A"},
{activity:"A2",time_taken:20,activity_group:"Master B"},
{activity:"A1",time_taken:30,activity_group:"Master C"},
{activity:"A2",time_taken:15,activity_group:"Master D"}
]
I want to have activity group in x-axis split by its activity representing time taken on y-axis, like this:
How do I achieve this ?
Your fiddle is on dc.js version 1.7, which is more than five years old and not something I can wrap my head around. :-/
I converted it to dc.js version 2, which also uses D3 v3.
dc.js is not great at showing the raw data, it's more about showing aggregated data. But in this case it could make sense to create a stack for every activity_group; that way it will automatically be assigned its own color.
Using ES6 we can get a list of all activity_group like this:
const stacks = [...new Set(data.map(row => row.activity_group))];
Now let's aggregate the data by stack:
var groupActivity = dimByActivity.group().reduce(
function reduceAdd(p, v) {
p[v.activity_group] += v.time_taken;
return p;
},
function reduceRemove(p, v) {
p[v.activity_group] -= v.time_taken;
return p;
},
function reduceInitial() {
return Object.fromEntries(stacks.map(stack => [stack,0]));
});
This is substantially the same as the stacked bar example except that we have a stack per activity_group.
Note that we are creating all the stacks in every bin, just leaving them zero where they don't exist. This is because dc.js expects the same stacks for every X value - it won't work otherwise.
As in the stacked bar example, we'll programmatically add the stacks to the chart, keeping in mind that we need to use .group() for the first stack:
function sel_stack(valueKey) {
return function(d) {
return d.value[valueKey];
};
}
// ...
stacks.forEach(function(stack, i) {
if(i===0)
chanUtil.group(groupActivity, stack, sel_stack(stack));
else
chanUtil.stack(groupActivity, stack, sel_stack(stack));
})
Here's the output. I messed a little with the margins and height in order to get the legend not to overlap and there are probably smarter ways to deal with this:
Fork of your fiddle.
As I said, this is making dc.js do something it doesn't want to do, so YMMV!
I want to display percentages on the y-axis of a dc.js barchart that can dynamically change when filtering the chart itself or some other charts.
Here is my case:
var ndx = crossfilter(dataCsvInitial);
var all = ndx.groupAll();
var accCredLimDim = ndx.dimension(function(d) { return d.acct_curr_crlimit;});
Then, I group by bins:
var value_range_credlim = maxCredLim - minCredLim; // defined earlier...
var nb_of_bins_credlim = 50,
bin_width_credlim = value_range_credlim/nb_of_bins_credlim;
var accCredLimGrp = accCredLimDim.group(function(d) {return Math.floor(d/bin_width_credlim)*bin_width_credlim;});
And draw my bar chart:
var creditBar = dc.barChart("#creditDistrib");
creditBar
.width(600)
.height(250)
.margins({top: 10, right: 50, bottom: 30, left: 50})
.dimension(accCredLimDim)
.group(accCredLimGrp)
.transitionDuration(500)
.x(d3.scaleLinear().domain([minCredLim, maxCredLim]))
.xUnits(function(){return nb_of_bins_credlim;})
.elasticY(true)
.brushOn(true)
.xAxisLabel("Credit Limit")
I succeeded to do what I want initially just by customizing the yAxis().tickFormat() attribute of the bar chart, by dividing the tick value by the total number of rows being filtered at the moment:
creditBarChart.yAxis().tickFormat(function (d) {
return 100*d/all.value() + '%';
});
And I recompute these ticks every time a transition is being made, because my y-axis is elastic:
creditBar
.on("pretransition", function(){
creditBar.yAxis().tickFormat(function (d) {
if (!creditBar.hasFilter()){
return Math.trunc(100*d/all.value()) + '%';
}
});
});
As you can see, I don't update the ticks when the bar chart is being filtered. Indeed, when it is filtered I want the ticks to remain unchanged, as the y-axis should not change. However, because I am dividing the tick value by all.value() this rule cannot work when filtering the bar chart itself. The displayed percentages are obviously wrong.
This question is quite close to solving my problem: link but it is applicable only for categorical bar chart...
How can I display percentages on the y-axis ticks, that can change of values when filtering other charts and also when filtering the chart itself?
Is there a sort of all.value() that would be computed excluding the effect of filtering a specified chart?
Thanks!
Since you want the groupAll not to observe the filter on this chart, you should use the chart dimension's groupAll not the one on the crossfilter object. From the docs:
Note: a grouping intersects the crossfilter's current filters, except
for the associated dimension's filter. Thus, group methods consider
only records that satisfy every filter except this dimension's filter.
So, if the crossfilter of payments is filtered by type and total, then
groupAll by total only observes the filter by type.
That's kind of a mouthful, but I hope the intention is clear.
var accCredLimDim = ndx.dimension(function(d) { return d.acct_curr_crlimit;});
var all = accCredLimDim.groupAll();
Once you do that, you don't have to put an if statement in your tickFormat definition:
creditBar
.on("pretransition", function(){
creditBar.yAxis().tickFormat(function (d) {
return Math.trunc(100*d/all.value()) + '%';
});
});
The if statement was incorrect for a couple of reasons. First, there could be a filter on this chart and also filters on the other charts. Second, any accessor you call, like tickFormat, needs to return a value every time it is called. But this would return undefined if there was any filter on this chart, because that is the default return value in JS.
I have a composite chart of 2 line charts however I need to add a third chart to it.
This third chart will have these unique properties:
The data will come in via an ajax call and be available as a two dimensional array [[timestamp,value],[timestamp,value]...]
Every new ajax call needs to replace the values of the previous one
It does not need to respect any of the filters and will not be used on any other charts
It will however need to use a differently scaled Y axis.. (and labeled so on the right)
This is how the chart currently looks with only two of the charts
This is my code with the start of a third line graph... Assuming I have the array of new data available i'm at a little loss of the best/simplest way to handle this.
timeChart
.width(width).height(width*.333)
.dimension(dim)
.renderHorizontalGridLines(true)
.x(d3.time.scale().domain([minDate,maxDate]))
.xUnits(d3.time.months)
.elasticY(true)
.brushOn(true)
.legend(dc.legend().x(60).y(10).itemHeight(13).gap(5))
.yAxisLabel(displayName)
.compose([
dc.lineChart(timeChart)
.colors(['blue'])
.group(metric, "actual" + displayName)
.valueAccessor (d) -> d.value.avg
.interpolate('basis-open')
.dimension(dim),
dc.lineChart(timeChart)
.colors(['red'])
.group(metric, "Normal " + displayName)
.valueAccessor (d) -> d.value.avg_avg
.interpolate('basis-open'),
dc.lineChart(timeChart)
.colors(['#666'])
.y()#This needs to be scaled and labeled on the right side of the chart
.group() #I just want to feed a simple array of values into here
])
Also side note: I've noticed what I might be a small bug with the legend rendering. As you can see in the legend both have the same label but i've used different strings in the second .group() argument.
I believe you are asking a few questions here. I will try to answer the main question: how do you add data to a dc chart.
I created an example here: http://jsfiddle.net/djmartin_umich/qBr7y/
In this example I simply add random data to the crossfilter, though this could easily be adapted to pull data from the server:
function AddData(){
var q = Math.floor(Math.random() * 6) + 1;
currDate = currDate.add('month', 1);
cf.add( [{date: currDate.clone().toDate(), quantity: q}]);
$("#log").append(q + ", ");
}
I call this method once a second. Once it completes, I reset the x domain and redraw the chart.
window.setInterval(function(){
AddData();
lineChart.x(d3.time.scale().domain([startDate, currDate]));
dc.redrawAll();
}, 1000);
I recommend trying to get this working in isolation before trying to add the complexity of multiple y-axis scales.
Currently your best bet is to create a fake group. Really the .data method on the charts is supposed to do this, but it doesn't work for charts that derive from the stack mixin.
https://github.com/dc-js/dc.js/wiki/FAQ#filter-the-data-before-its-charted
I have a lineChart with a somewhat complicated .valueAccessor function. I also have a barChart on the same dimension and group.
There are several other charts on the webpage but on other dimensions and groups.
When I filter using one of the other charts, the barChart updates correctly but not the lineChart. Could there be something explaining that behaviour ?
I would like to get feedback before setting up a jsFiddle as my data is quite heavy.
EDIT : here is the fiddle. It pulls 7MB of data in an ajax call so it takes a while to run. The answer provided by #DJMartin below should be the fix for me but it doesn't work.
From the crossfilter API reference: https://github.com/square/crossfilter/wiki/API-Reference
"Note: a grouping intersects the crossfilter's current filters, except for the associated dimension's filter. Thus, group methods consider only records that satisfy every filter except this dimension's filter. So, if the crossfilter of payments is filtered by type and total, then groupAll by total only observes the filter by type."
If you create a second dimension using the same property, the filtering will reflect across charts.
Here is an example of that: http://jsfiddle.net/djmartin_umich/nw8EV/.
teamMemberChart
.width(270)
.height(220)
.dimension(teamMemberDimension)
.group(teamMemberGroup)
.valueAccessor(function (d) {
return d.value.projectCount;
})
.elasticX(true);
teamMemberChart2
.width(270)
.height(220)
.dimension(teamMemberDimension)
.group(teamMemberGroup)
.valueAccessor(function (d) {
return d.value.projectCount;
})
.elasticX(true);
teamMemberChart3
.width(270)
.height(220)
.dimension(teamMemberDimension2)
.group(teamMemberGroup2)
.valueAccessor(function (d) {
return d.value.projectCount;
})
.elasticX(true);
The first two charts use the same dimension - picking one option does not reflect the other. The third chart uses a different dimension on the same property - choosing an option on this chart updates the other two charts.
I am working on dc.js, i have draw pieChart
pieChart.width(300)
.height(200)
.transitionDuration(1500)
.dimension(startValue)
.group(startValueGroup, "StartValue")
.radius(100)
.minAngleForLabel(0.5)
.legend(dc.legend().x(230).y(0))
.title(function(d) {
return d.key + ": " + d.value;
})
.renderTitle(true)
.on("filtered", function (chart) {
dc.events.trigger(function () {
//console.log(total_payment);
});
});
Now, I want to set specific x axis value. Currently, pieChart taking center position of define width and height. That mean it's taking position of (150,100). I want to change this position to (100, 100).
How can i change position of x axis as per above code?
You can't set this directly in the dc options, but you can modify these values after the chart has been rendered. Assuming that "pie" is the ID of the DOM element that you rendered the pie chart into, you can do
d3.select("#pie > svg > g").attr("transform", "translate(100,100)");
I know it is an old post and that the answer works perfectly but when the chart is part of a bigger system (several charts) I found easier to add the above command directly to the chart by doing this:
pieChart.on('renderlet', function (chart) {
chart.select("svg > g").attr("transform", "translate("100,100)");
});
Edit:
Actually I just found out you can do:
.cx(100)
.cy(100)
which will set the centre of the pie chart to 100, 100 before render.