I don't know if this is possible in dc.js
and crossfilter.js
, but I decided to ask anyways.
I combined a scatterplot and a barChart example from dc to make an interactive dashboard:
var chart1 = dc.scatterPlot("#test1");
var chart2 = dc.scatterPlot("#test2");
d3.csv("output.csv", function(error, data) {
data.forEach(function (x) {
x.x = +x.x;
x.y = +x.y;
x.z = +x.z;
});
var ndx = crossfilter(data),
dim1 = ndx.dimension(function (d) {
return [d.x, d.y];
}),
dim2 = ndx.dimension(function (d) {
return Math.floor(parseFloat(d.z) * 10) / 10;
}),
group1 = dim1.group(),
group2 = dim2.group(),
chart1.width(300)
.height(300)
.x(d3.scale.linear().domain([-2, 2]))
.y(d3.scale.linear().domain([-2, 2]))
.yAxisLabel("y")
.xAxisLabel("x")
.clipPadding(10)
.dimension(dim1)
//.excludedOpacity(0.5)
.excludedColor('#ddd')
.group(group1)
.symbolSize([2.5]);
chart2
.width(600)
.dimension(dim2)
.group(group2)
.x(d3.scale.linear().domain([0,3]))
.elasticY(true)
.controlsUseVisibility(false)
.barPadding([0.1])
.outerPadding([0.05]);
chart2.xAxis().tickFormat(function(d) {return d}); // convert back to base unit
chart2.yAxis().ticks(10);
dc.renderAll();
});
Result when brushing the bar chart:
I want to change the filtering so that when I brush the bar chart, brushed points in the scatterplot will have an opacity value, which is 1 in the middle of the brush, and decreases towards end of the range of brush.
The other points (outside the brush) should just be grey, instead of invisible as in the current script. Illustration:
Is this possible to do with the dc.js
and crossfilter.js
?
PS: The attached scatterplot isn't the desired outcome. It is not filtered based on opacity. I just attached it to show how the other points(grey) should look like after brushing the bar chart.
I couldn't get this working with animated transitions, because there is something I am missing about how to interrupt transitions, and the original dc.scatterPlot
is already applying opacity transitions.
So, to start off, let's turn transitions on the original scatter plot:
chart1
.transitionDuration(0)
We also need to add Z to the input data for the scatter plot. Although it would make more sense to add it to the value, it's easy to add it to the key (and the scatter plot will ignore extra elements in the key):
dim1 = ndx.dimension(function (d) {
return [d.x, d.y, d.z];
}),
Then we can add a handler to to the scatter plot to apply opacity to the dots, based on the range of the filter in the bar chart:
chart1.on('pretransition', function(chart) {
var range = chart2.filter(); // 1
console.assert(!range || range.filterType==='RangedFilter'); // 2
var mid, div; // 3
if(range) {
mid = (range[0] + range[1])/2;
div = (range[1] - range[0])/2;
}
chart1.selectAll('path.symbol') // 4
.attr('opacity', function(d) {
if(range) { // 5
if(d.key[2] < range[0] || range[1] < d.key[2])
op = 0; // 6
else
op = 1 - Math.abs(d.key[2] - mid)/div; // 7
//console.log(mid, div, d.key[2], op);
return op;
}
else return 1;
})
});
null
or it should be a RangedFilterYou could probably use d3.ease to map the distance [0,1] to opacity [0,1] using a curve instead of linearly. This might be nice so that it emphasizes the points closer to the midpoint
This demo is not all that cool because the data is purely random, but it shows the idea: https://jsfiddle.net/gordonwoodhull/qq31xcoj/64/
EDIT: alright, it's a total abuse of dc.js, but if you really want to use it without filtering, and displaying the excluded points in grey, you can do that too.
This will disable filtering on the bar chart:
chart2.filterHandler(function(_, filters) { return filters; });
Then apply opacity and color to the scatter plot like this instead:
chart1.selectAll('path.symbol')
.attr('opacity', function(d) {
if(range && range.isFiltered(d.key[2]))
return 1 - Math.abs(d.key[2] - mid)/div;
else return 1;
})
.attr('fill', function(d) {
if(!range || range.isFiltered(d.key[2]))
return chart1.getColor(d);
else return '#ccc';
})
With this data it's tricky to see the difference between the light blue dots and the grey dots. Maybe it will work better with non-random data, maybe not. Maybe another color will help.
Again, you might as well use straight D3, since this disables most of what dc.js and crossfilter do. But you'd have to start from scratch to ask that question.
EDIT 2: sort the dots by filteredness like this:
.sort(function(d) {
return range && range.isFiltered(d.key[2]) ? 1 : 0;
})