-->

How to filter views with an opacity range in d3/dc

2019-04-12 10:33发布

问题:

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.

回答1:

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/

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.

Updated fiddle.

EDIT 2: sort the dots by filteredness like this:

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

Fiddle 3