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.
Related
I have three Row Charts and my code calculates and updates the percentages for each chart whenever a user first lands on the page or clicks a rectangle bar of a chart. This is how it calculates the percentages
posChart:
% Position= unique StoreNumber counts per Position / unique StoreNumber counts for all POSITIONs
deptChart:
% Departments= POSITION counts per DEPARTMENT/POSITION counts for all DEPARTMENTs
stateChart:
% States= unique StoreNumber counts per STATE / unique StoreNumber counts for all STATEs
What I want is when a user clicks a rectangle bar of a rowChart such as “COUNTS BY STATE”, it should NOT update/recalculate the percentages for that chart (it should not affect its own percentages), however, percentages should be recalculated for the other two charts i.e. “COUNTS BY DEPARTMENT” and “COUNTS BY POSITION”. The Same scenario holds for the other charts as well. This is what I want
If a user clicks a
“COUNTS BY DEPARTMENT” chart --> recalculate percentages for “COUNTS BY POSITION” and “COUNTS BY STATE” charts
“COUNTS BY POSITION” chart --> recalculate percentages for “COUNTS BY DISTRIBUTOR” and “COUNTS BY STATE” charts
Please Help!!
link:http://jsfiddle.net/mfi_login/z860sz69/
Thanks for the reply.
There is a problem with the solution you provided. I am looking for the global total for all filters but I don’t want those totals to be changed when user clicks on a current chart's rectangular bar.
e.g.
if there are two different POSITIONS (Supervisor, Account Manager) with the same StoreNumber (3), then I want StoreNumber to be counted as 1 not 2
If we take an example of Account Manager % calculation (COUNTS BY POSITION chart)
total unique StoreNumbers=3
Total Account Manager POSITIONs=2
% = 2/3=66%
Is there a way to redraw the other two charts without touching the current one?
It seems to me that what you really want is to use the total of the chart's groups, not the overall total. If you use the overall total then all filters will be observed, but if you use the total for the current group, it will not observe any filters on the current chart.
This will have the effect you want - it's not about preventing any calculations, but about making sure each chart is affected only by the filters on the other charts.
So, instead of bin_counter, let's define sum_group and sum_group_xep:
function sum_group(group, acc) {
acc = acc || function(kv) { return kv.value; };
return d3.sum(group.all().filter(function(kv) {
return acc(kv) > 0;
}), acc);
}
function sum_group_xep(group) {
return sum_group(group, function(kv) {
return kv.value.exceptionCount;
});
}
And we'll use these for each chart, so e.g.:
posChart
.valueAccessor(function (d) {
//gets the total unique store numbers for selected filters
var value=sum_group_xep(posGrp)
var percent=value>0?(d.value.exceptionCount/value)*100:0
//this returns the x-axis percentages
return percent
})
deptChart
.valueAccessor(function (d) {
total=sum_group(deptGrp)
var percent=d.value?(d.value/total)*100:0
return percent
})
stateChart
.valueAccessor(function (d) {
var value=sum_group_xep(stateGrp);
return value>0?(d.value.exceptionCount/value)*100:0
})
... along with the other 6 places these are used. There's probably a better way to organize this without so much duplication of code, but I didn't really think about it!
Fork of your fiddle: http://jsfiddle.net/gordonwoodhull/yggohcpv/8/
EDIT: Reductio might have better shortcuts for this, but I think the principle of dividing by the total of the values in the current chart's group, rather than using a groupAll which observes all filters, is the right start.
I've used a fake group to fix my issue with plotting only top(n) values on a graph. Now I found an other issue that the bar chart shows a few empty positions for those data fields that doesn't come under 'ton(n)', I want to remove them and sort the bars in descending order of their values.
Here is the JSFiddle.
orgHigestBandwidthConsumed.dimension(dimByOrgName)
.group(fakeGroup)
.ordering(function (d) { return -d.value; })
I've tried chart.ordering() to order the bars which didn't work. Can somebody help me fixing this?
Since you are specifying the ordinal domain, dc.js will use that instead of calculating it using the existing values and the ordering you specify.
Simply remove the domain and specify the ordering:
.x(d3.scale.ordinal())
.ordering(function(kv) { return -kv.value; })
Fork of your fiddle: http://jsfiddle.net/0p78m8vr/6/
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'm stuck on a seamingly easy problem with dc.js and crossfilter.
I have a datatable on a dimension and it shows my data correctly. The problem is that I want to show the 'worst' data items but the datatable picks the 'best' items in the filters.
The following is a scetch of the current table code.
var alertnessDim = ndx.dimension(function(d) {return d.alertness1;});
dc.dataTable(".dc-data-table")
.dimension(alertness1)
.group(function (d) {
return d.DATE.getYear();
})
.columns([
function (d) {
return d.DATE;
},
function (d) {
return d['FLEET'];
},
function (d) {
return d.alertness1;
}
])
.sortBy(function (d) {
return d.alertness1;
})
.order(d3.ascending);
This connects to the crossfilter properly and it sorts the items in the correct order, but the 25 items it is showing are the ones with the highest alertness values, not the lowest.
Anyone have any ideas on how to solve this, preferbly without creating another dimension?
You are right to be confused here. You would think this would be a supported use case but it is not, as far as I can tell. The data table uses dimension.top so it is always going to take the highest values.
So I don't think there is a way around using a special dimension with opposite ordering/keys. For the other charts you could use group.order in order (heh) to get it to return the lowest values. But the data table doesn't use a group because it's not reducing its values.
It's confusing that the data table also has an order parameter which doesn't help here.
Hope that is acceptable. Otherwise I think you'd have to poke around in the code. Pull Requests always welcome! (preferably with tests)
One quick way to achieve descending sort on a specific column, is to sort by its negative value:
.sortBy(function(d){ return -d.ALARM_OCCURRENCE; }); // show highest alarm count first
I'm trying to make an chart using the default line plus bar chart, but I want to use two or more streams in the bars, is it possible?
Currently, when I try to do this, I got some trouble with the effects of the chart, and so I can't show properly the hover balloon of the bars, he always display the data of just one of the streams. But the main problem is the dates of x axis, displaying 1970's dates, when I remove the second stream of bars, the dates display well:
Anyone already tried to do this kind of chart successfully?
EDIT
Adding Fiddles:
Fiddle with two columns stream and messy dates
Fiddle with just one column stream and ok dates
I'm calling this kind of graph:
linePlusBarChart()
The problem with the dates is that your data contains timestamps (i.e. in seconds), but Javascript expects milliseconds. This is easily fixed by multiplying the values by 1000:
series.values = series.values.map(function (d) {
return {
x: d[0]*1000,
y: d[1]
}
});
The tooltip problem is actually a bug in NVD3 -- it's not meant to be used this way. The problem boils down to the mouseover handler assuming that the first item of the data is representative of what you want. You can fix this for your case by selecting the item by data point number modulo 2 (because there're two bars):
.on('mouseover', function(d,i) {
d3.select(this).classed('hover', true);
dispatch.elementMouseover({
point: d,
series: data[i%2],
pos: [x(getX(d,i)), y(getY(d,i))],
pointIndex: i,
seriesIndex: i%2,
e: d3.event
});
})
This will only work for exactly two bar series though. Updated jsfiddle with the modified NVD3 code here.