How to filter views with an opacity range in d3/dc.js?
Asked Answered
A

1

1

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:

brush

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:

opacity scatterplot

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.

Ade answered 16/4, 2018 at 18:4 Comment(6)
Ooooo coool. It's not supported by crossfilter directly, but there is probably a way to hack it.Tungstate
Hi @Gordon, did you find any way?Ade
Didn't have a chance to look at it yesterday. I'll try to find some time today.Tungstate
Hi @Snow, if you intend to award the bounty, you'll have to do it manually. Since you started the bounty after I first wrote my answer, it won't be awarded automatically. (It will just be lost.) Thanks!Tungstate
@Tungstate oh sorry, I thought it was already rewarded.Ade
Sweet, thank you. Hate to see hard-earned rep go to waste, as has happened in the past.Tungstate
T
2

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;

        })
    });
  1. Get the current brush/filter from the bar chart
  2. It should either be null or it should be a RangedFilter
  3. Find the midpoint and the distance from the midpoint to the edges of the brush
  4. Now apply opacity to all symbols in the scatter plot
  5. If there is an active brush, apply opacity (otherwise 1)
  6. If the symbol is outside the brush, opacity is 0
  7. Otherwise the opacity is linear based on the distance from the midpoint

You 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/

scatter with opacity based on range

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.

no filtering just brushing 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.

Updated fiddle.

EDIT 2: sort the dots by filteredness like this:

        .sort(function(d) {
          return range && range.isFiltered(d.key[2]) ? 1 : 0;
        })

with sorting

Fiddle 3

Tungstate answered 18/4, 2018 at 19:9 Comment(13)
oh wow this is great. Thank a lot Gordon! Although, I do not understand the need for the if condition at point 6. If we apply that, not all the brushed points are included (so I removed it).Ade
Also, is there a way to make the filtered out points of the barchart(grey bars) visible in the scatterplot, like in my question?Ade
Oh so you don't want to filter at all, just change opacity and color. This is really not a crossfilter application at all. Because as its name implies, crossfilter is about filtering and you really just want brushing. Maybe you're better off just using d3 rather than forcing dc.js / crossfilter for something it's not intended for. Sorry I didn't understand your question.Tungstate
But I actually do want to filter, maybe you didn't understand me well. I just want to filter the brushed points(blue bars), and display the grey bars as grey in the scatterplotAde
The concept I wanted to implement is Smooth brushing and linking.Ade
Yeah, that's not what crossfilter does, but nonetheless I've shown how to disable the filtering and do just brushing above. If you keep going with this, you'll probably just want to use straight d3; Mike's scatter plot matrix brushing example might be a good start.Tungstate
Thanks for the update @Tungstate The problem with the update though, is that the grey points sometimes stand on top of brushed points.Ade
You're really going to have to bite the bullet and learn some d3. Not sure why I keep going with this, but the line of sorting that you need is above now. I will not help you any further.Tungstate
You're very right, I will implement the above fiddle in d3 only(however at the moment I don't see how it can be made without crossfilter, for the linking part). Thanks a lot though, you don't know how much you've helped me :)Ade
Crossfilter really only does filtering and it's disabled here. All we've got here is a bar chart with a d3.brush, and a scatter plot with the color & opacity connected to the brush. Aside from aggregating the data for the bars, crossfilter is out of the picture.Tungstate
Thanks for fixing the fiddle. Oh dear, I didn't think about how all my fiddles point to master branch of dc.js. I guess they are all broken now, and no way to fix them without creating new versions.Tungstate
creating new versions would be too time consuming, not mention you have around 450 answers :pAde
Haha yeah no reasonable way to fix except one at a time. From now on I'll have to pin my fiddles to a particular version. Thanks again.Tungstate

© 2022 - 2024 — McMap. All rights reserved.