D3 - get selected Date range on brushend - d3.js

I am new to d3 and using brushing on grouped bar chart
http://fiddle.jshell.net/CjaD3/21/
I am trying to get the range selected. I am listing to the "brushend" event and calling a function brushend(). Its getting called but returning me the x-axis coordinates in numbers ([42, 318]). I want in Date dormat like 'Sat 25' to 'Mon 27'
Thanks for your help.

This is where you would usually use a the invert method of your x-scale. Unfortunately, that method doesn't exist for ordinal scales. Luckily, Jason Davies, patch is still around. I don't like messing with the source, so I adapted it for your function:
function brushend() {
console.log("brushend");
var b = brush.empty() ? contextXScale.domain() : brush.extent();
console.log(b);
var d = mini_x0.domain(),
r = mini_x0.range(),
startDate = d[d3.bisect(r, b[0]) - 1],
finDate = d[d3.bisect(r, b[1]) - 1];
console.log([startDate, finDate]);
}
Updated fiddle.

Related

dc.js access data points in multiple charts when click datapoint in first chart

Using different dimensions of the same dataset, there are three dc.js Line Charts on screen.
When user clicks a datapoint on any lineChart, I wish to locate and return the data values for that corresponding point from all other charts, including the one clicked on.
I am also attempting (on mouseover) to change the circle fill color to red for the datapoint being hovered, as well as for the corresponding datapoint (same "x" value) for all other charts.
I am using the .filter() method but haven't been successful getting the desired data. The error message is: "Uncaught TypeError: myCSV[i].filter is not a function"
Full jsFiddle demo/example
lc1.on('renderlet', function(lc1) {
var allDots1 = lc1.selectAll('circle.dot');
var allDots2 = lc2.selectAll('circle.dot');
var allDots3 = lc3.selectAll('circle.dot');
allDots1.on('click', function(d) {
var d2find = d.x;
var d2find2 = d3.select(this).datum();
console.log(myCSV);
alert('Captured:'+"\nX-axis (Date): "+d2find2.x +"\nY-axis (Value): "+ d2find2.y +"\nDesired: display corresponding values from all three charts for this (date/time) datapoint");
allDots2.filter(d=>d.x == d2find2).attr('fill','red');
findAllPoints(d2find2);
});//END allDots1.on(click);
function findAllPoints(datum) {
var objOut = {};
var arrLines=['car','bike','moto'];
for (var i = 0; i < 3; i++) {
thisSrx = arrLines[i];
console.log('thisSrx: '+thisSrx);
console.log(myCSV[i].date)
console.log(datum.x);
//loop thru myCSV obj, compare myCSV[i].date to clicked "x" val
//build objOut with data from each graph at same "X" (d/t) as clicked
objOut[i] = myCSV[i].filter(e => e.date === datum.x)[0][thisSrx];
}
$('#msg').html( JSON.stringify(objOut) );
console.log( JSON.stringify(objOut) );
}//END fn findAllPoints()
});//END lc1.on(renderlet)
myCSV contains all three data points, so I don't see the need to loop through the three charts independently - findAllPoints is going to find the same array entry for all three data series anyway.
The main problem you have here is that date objects don't compare equal if they have the same value. This is because == (and ===) evaluate object identity if the operands are objects:
> var d1 = new Date(), d2 = new Date(d1)
undefined
> d1
Mon Feb 13 2017 09:03:53 GMT-0500 (EST)
> d2
Mon Feb 13 2017 09:03:53 GMT-0500 (EST)
> d1==d2
false
> d1.getTime()===d2.getTime()
true
There are two ways to deal with this.
Approach 1: use second event argument
If the items in all the charts match up item by item, you can just use the index.
All d3 callbacks pass both the datum and the index. So you can modify your callback like this:
allDots1.on('click', function(d,i) {
// ...
allDots2.filter((d,j)=> j===i).attr('fill','red').style('fill-opacity', 1);
alert(JSON.stringify(myCSV[i]));
});
http://jsfiddle.net/gordonwoodhull/drbtmL77/7/
Approach 2: compare by date
If the different charts might have different data indices, you probably want to compare by date, but use Date.getTime() to get an integer you can compare with ===:
allDots1.on('click', function(d) {
var d2find = d.x;
// ...
allDots2.filter(d=> d.x.getTime()===d2find.getTime()).attr('fill','red').style('fill-opacity', 1);
var row = myCSV.filter(r=>r.date.getTime()===d2find.getTime())
alert(JSON.stringify(row));
});
http://jsfiddle.net/gordonwoodhull/drbtmL77/10/
Note that in either case, you're going to need to also change the opacity of the dot in the other charts, because otherwise they don't show until they are hovered.
Not sure when you want to reset this - I guess it might make more sense to show the corresponding dots on mouseover and hide them on mouseout. Hopefully this is enough to get you started!

dc.js stacked line chart with more than 1 dimension

My dataset is an array of json of the like :
var data = [ { company: "A", date_round_1: "21/05/2002", round_1: 5, date_round_2: "21/05/2004", round_2: 20 },
...
{ company: "Z", date_round_1: "16/01/2004", round_1: 10, date_round_2: "20/12/2006", round_2: 45 }]
and I wish to display both 'round_1' and 'round_2' time series as stacked line charts.
The base line would look like this :
var fundsChart = dc.lineChart("#fundsChart");
var ndx = crossfilter(data);
var all = ndx.groupAll();
var date_1 = ndx.dimension(function(d){
return d3.time.year(d.date_round_1);
})
fundsChart
.renderArea(true)
.renderHorizontalGridLines(true)
.width(400)
.height(360)
.dimension(date_1)
.group(date_1.group().reduceSum(function(d) { return +d.round_1 }))
.x(d3.time.scale().domain([new Date(2000, 0, 1), new Date(2015, 0, 1)]))
I have tried using the stack method to add the series but the problem resides in the fact that only a single dimension can be passed as argument of the lineChart.
Can you think of a turnaround to display both series while still using a dc chart?
Are you going to be filtering on this chart? If not, just create a different group on a date_2 dimension and use that in the stack. Should work.
If you are going to be filtering, I think you'll have to change your data model a bit. You'll want to switch to have 1 record per round, so in this case you'll have 2 records for every 1 record you have now. There should be 1 date property (the date for that round), an amount property (the contents of round_x in the current structure), and a 'round' property (which would be '1', or '2', for example).
Then you need to create a date dimension and multiple groups on that dimension. The group will have a reduceSum function that looks something like:
var round1Group = dateDim.group().reduceSum(function(d) {
return d.round === '1' ? d.amount : 0;
});
So, what happens here is that we have a group that will only aggregate values from round 1. You'll create similar groups for round 2, etc. Then stack these groups in the dc.js chart.
Hopefully that helps!

How to get quantize values

is there a way to get the start and end values of the quantizes of an quantize scale.
The range is defined by 5 colors ans the domain by d3.min and d3.max function on my data from an json file.
I need them for my legend of an choropleth map.
Thank you for helping.
Carsten
Thats my code
var quantizecolors = ["#d7191c","#fdae61","#ffffbf", "#a6d96a","#1a9641"];
var colorEnerg = d3.scale.quantize().range(quantizecolors);
colorEnerg.domain([
d3.min(collection.features, function(d){return d.properties.EB/d.properties.BEVZ;}),
d3.max(collection.features, function(d){return d.properties.EB/d.properties.BEVZ;})
]);
I assume that you're asking about the minimum and maximum domain values. Apart from saving them when you're setting them, you can also call colorEnerg.domain() without any values, which will return the array [min, max].
You can get the position of the breaks by computing the number and position of intervals:
var dom = colorEnerg.domain(),
l = (dom[1] - dom[0])/colorEnerg.range().length,
breaks = d3.range(0, colorEnerg.range().length).map(function(i) { return i * l; });

Trouble with filters and triggers in dc.js - re-drawing is out of sync

I have two plots: a line plot and a bubble plot. When I click on the bubble plot, I want the line plot to be updated so that it is drawn with only the data related to that 'bubble'. This is different from the standard implementation whereby clicking would add or remove the data from the existing filter.
If you look at the image you can see that although 'model 0' is selected the plotted hazard (y-scale in plot 1) does not correspond.
And now when I click on 'model 5', I get the opposite.
My current implementation is posted as a jsfiddle here. I can see from the attached data table that I am achieving what I want, but the line plot does not re-draw correctly. In fact, it seems to re-draw with the last filter, not the new one.
This implementation is hacked from here: in particular, the renderlet and on("filtered", function (chart) { lines. However, to make this work, I have had to comment out the plot1.filter(chart.filter()); line for the second plot.
I don't really understand why a renderlet and the on("filtered" ... or on("postRedraw" ... listeners are needed together.
I have been round the houses on this one, so any suggestions would be very gratefully received.
I tried to simplify the jsfiddle to isolate the problem. Here is the adapted jsfiddle: http://jsfiddle.net/djmartin_umich/mKz7A/
Your plot2 keyAccessor accessed the df value from the p.value.df rather than using a dimension on df. My guess is that this is what was causing problems. Here is the adapted code:
dfDim = ndx.dimension(function (d) {return d.df;});
...
plot2.width(300)
.height(250)
.dimension(dfDim)
I also noticed that your plot2 valueAccessor and radiusAccessor were not using a computed average. Your code would overwrite est and estse for each record added or removed from the group. Here is the adapted code that computes the average:
dfGroup = dfDim.group().reduce(
//add
function (p, v) {
++p.count;
p.est += v.est;
p.avg_est = p.est / p.count;
p.estse += v.estse;
p.avg_estse = p.estse / p.count;
return p;
},
//remove
function (p, v) {
--p.count;
p.est -= v.est;
p.avg_est = p.est / p.count;
p.estse -= v.estse;
p.avg_estse = p.estse / p.count;
return p;
},
//init
function (p, v) {
return {
count: 0,
est: 0,
estse: 0,
avg_est: 0,
avg_estse: 0
};
});
After these changes, I believe the code behaves as you wanted.

d3 retrieve and add to current selection's attribute value

I'm trying to get the values for an element's translation.
For example, if I select the x axis:
d3.select('.x.axis').attr("transform")
then I get
"translate(0,112)"
How do I get the 0 and the 112 without parsing a regexp?
I'm trying to do it so that I can add to the value. In pseudocode:
d3.selectAll('.x.axis').attr('transform', 'translate('
.attr('transform').match(/(\d+)(\.\d+)?/g)[0] // <-- clearly won't work
+ additional_value
+ ', 0)');
D3 provides the transform() function for exactly this purpose:
var t = d3.transform(d3.select('.x.axis').attr("transform")),
x = t.translate[0],
y = t.translate[1];
if you would like to use selectAll you could try something like this:
// move ticks to the center of the x-axis
var transform;
d3.selectAll('.tick').attr('transform', function(){
transform = d3.transform(d3.select(this).attr("transform"));
return "translate("+transform.translate[0]+", -3)";
});

Resources