Search code examples
dc.jscrossfilter

dc.js disable brush resizing


I am trying to disable brush extent on a line chart and set a fixed width.

I am aware this subject has been discussed several times. I have tried without success to work on this answer: Disable brush resize (DC.js, D3.js) but I couldn't find a solution matching my situation.

If it makes any difference, I am expanding on the brushable ordinal chart discussed in this question: DC.js - Custom ordering of ordinal scale line chart

Here is the chart initialization code:

    line
        .width(950)
        .height(350)
        .margins({top: 10, right: 50, bottom: 35, left: 30})
        .dimension(graphDim)            
        .keyAccessor(function(kv) { return  graphGroup.ord2int(kv.key); })
        .group(graphGroup)
        .x(d3.scaleLinear().domain(linear_domain))
        .xAxisLabel("Chronologie")
        .yAxisLabel("Effectif")
        .brushOn(true)
        .renderArea(true)
        .renderHorizontalGridLines(true)
        .renderVerticalGridLines(true)
        .elasticY(true)
        .filter(dc.filters.RangedFilter(0,0.9));

    line.yAxis().ticks(4);

    line.xAxis()
        .tickValues(d3.range(data.length))
        .tickFormat(function(d) { return graphGroup.int2ord(d); });

    line.filterHandler(function(dimension, filters) {
        if(!filters || !filters.length) {
            dimension.filter(null);
            return filters;
        }
        console.assert(filters.length === 1);
        console.assert(filters[0].filterType === 'RangedFilter');
        var inside = graphGroup.all().filter(function(kv) {
            var i = graphGroup.ord2int(kv.key);
            return filters[0][0] <= i && i < filters[0][1];
        }).map(function(kv) { return kv.key; });
        dimension.filterFunction(function(d) {
            return inside.indexOf(d) >= 0;
        });
        return filters;
    })

And the fiddle: https://jsfiddle.net/bob_magnus_1/sr7hmnvf/9/`

Is there a simple way to override coordinateGridMixin.extendBrush function in such a chart?


Solution

  • The previous question was for DCv2. Fixed-width brushes are even easier in DCv3+ (because of improvements to d3-brush in D3v4).

    It's the same technique as before: look at how extendBrush is implemented, and then replace it. Here's how it looks in DCv3:

    _chart.extendBrush = function (brushSelection) {
        if (brushSelection && _chart.round()) {
            brushSelection[0] = _chart.round()(brushSelection[0]);
            brushSelection[1] = _chart.round()(brushSelection[1]);
        }
        return brushSelection;
    };
    

    (source)

    It takes a selection – an array of two elements – and returns a new selection.

    In your case, your data is ordinal but the scale is linear in order to enable brushing. The brush should match the scale.

    To keep it at 0.9 width:

    line.extendBrush = function(brushSelection) {
      brushSelection[1] = brushSelection[0] + 0.9;
      return brushSelection;
    };
    

    Hiding the brush handles (which have weird behavior) with CSS:

    .brush .custom-brush-handle {
      display: none;
    } 
    

    Fork of your fiddle.

    Ordinal brush with snapping

    I realized (looking at your 0.9 width brush) that you probably want proper ordinal brushing where it snaps to the region around the one selected point.

    You can do this by setting the begin and end of the selection:

    line.extendBrush = function(brushSelection) {
      brushSelection[0] = Math.round(brushSelection[0]) - 0.5;
      brushSelection[1] = brushSelection[0] + 1;
      return brushSelection;
    };
    

    We find the the currently hovered point by rounding, and then set the begin to 0.5 before it, and the end to 0.5 after it.

    Initialize the filter to surround the zero point:

    line
      .filter(dc.filters.RangedFilter(-0.5,0.5));
    

    ordinal snapping brush

    Revised fiddle.

    Floating point bug fix

    I noticed that if you select 20/30 above (linear value 3), brushSelection[0] will have changed from 2.5 to 2.49999999, causing the brush to jump back by one. Some numbers don't represent correctly in floating point.

    It's safer to use the average of begin and end:

        line.extendBrush = function(brushSelection) {
          const point = Math.round((brushSelection[0] + brushSelection[1])/2);
          return [
            point - 0.5,
            point + 0.5
          ];
        };
    

    Fiddle version with 20/30 selectable.