Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dc.js applying range Chart to multiple graphs

I have been working with dc.js for the last couple of weeks but there is one problem I just don't seem to be able to figure out.

I want to be able to change the scales of four different charts based on a single chart with a brush filter. Something along the lines of:

priorityTotChart
  .width(2*w/3).height(h/3)
  .margins({top: 10, right: 50, bottom: 20, left: 40})
  .dimension(dateDim)
  .group(priorityTotal)
  .centerBar(true)
  .gap(1)
  .x(d3.time.scale().domain([minDate,maxDate]))
  .elasticY(true)
  .brushOn(true);

var translate = 3;  

priority1Chart.width(w/3)
  .height(h/6)
  .transitionDuration(300)
  .margins({top: 10, right: 50, bottom: 25, left: 40})
  .dimension(dateDim)
  .group(priority1)
  .mouseZoomable(false)
  .x(d3.time.scale().domain([minDate,maxDate]))
  .xUnits(d3.time.months)
  .elasticY(true)
  .brushOn(false)
  .ordinalColors(["red"])
  .rangeChart(priorityTotChart)
  .yAxisLabel("Hits per day")
  .renderArea(true)
  .renderlet(function (chart) {
    chart.selectAll("g._1").attr("transform", "translate(" + translate + ", 0)");
});

priority2Chart.width(w/3)
  .height(h/6)
  .transitionDuration(300)
  .margins({top: 10, right: 50, bottom: 25, left: 40})
  .dimension(dateDim)
  .group(priority2)
  .mouseZoomable(false)
  .x(d3.time.scale().domain([minDate,maxDate]))
  .xUnits(d3.time.months)
  .elasticY(true)
  .brushOn(false)
  .ordinalColors(["orange"])
  .rangeChart(priorityTotChart)
  .yAxisLabel("Hits per day")
  .renderArea(true)
  .renderlet(function (chart) {
    chart.selectAll("g._1").attr("transform", "translate(" + translate + ", 0)");
});

priority3Chart.width(w/3)
  .height(h/6)
  .transitionDuration(300)
  .margins({top: 10, right: 50, bottom: 25, left: 40})
  .dimension(dateDim)
  .group(priority3)
  .mouseZoomable(false)
  .x(d3.time.scale().domain([minDate,maxDate]))
  .xUnits(d3.time.months)
  .elasticY(true)
  .brushOn(false)
  .ordinalColors(["blue"])
  .rangeChart(priorityTotChart)
  .yAxisLabel("Hits per day")
  .renderArea(true)
  .renderlet(function (chart) {
    chart.selectAll("g._1").attr("transform", "translate(" + translate + ", 0)");
});

priority4Chart.width(w/3)
  .height(h/6)
  .transitionDuration(300)
  .margins({top: 10, right: 50, bottom: 25, left: 40})
  .dimension(dateDim)
  .group(priority4)
  .mouseZoomable(false)
  .x(d3.time.scale().domain([minDate,maxDate]))
  .xUnits(d3.time.months)
  .elasticY(true)
  .brushOn(false)
  .ordinalColors(["green"])
  .rangeChart(priorityTotChart)
  .yAxisLabel("Hits per day")
  .renderArea(true)
  .renderlet(function (chart) {
    chart.selectAll("g._1").attr("transform", "translate(" + translate + ", 0)");
});

However this method doesn't seem to work as only the last chart that uses .rangeChart(priorityTotChart) will be updated.

I can get around this problem by making each graph depend on the range of the previous graph i.e. priority1Chart has .rangeChart(priorityTotChart), and priority2Chart has .rangeChart(priority1Chart) etc., but this method is very slow. So I was hoping there was a better way to achieve the same effect?

Thanks in advance!

like image 515
Suzie Avatar asked Dec 12 '14 13:12

Suzie


1 Answers

Another way of asking your question is, "how can I attach multiple focus charts to a single range chart?"

(At least, if you only care about brushing the range chart zooming the focus charts, not the other way around.)

Given this new question, take a look at the implementation of coordinateGridMixin.focusChart:

_chart.focusChart = function (c) {
    if (!arguments.length) {
        return _focusChart;
    }
    _focusChart = c;
    _chart.on('filtered', function (chart) {
        if (!chart.filter()) {
            dc.events.trigger(function () {
                _focusChart.x().domain(_focusChart.xOriginalDomain());
            });
        } else if (!rangesEqual(chart.filter(), _focusChart.filter())) {
            dc.events.trigger(function () {
                _focusChart.focus(chart.filter());
            });
        }
    });
    return _chart;
};

All we need to do here is make a new version of the function that takes multiple focus charts. Since this function doesn't access any internal data, we can "monkey patch" any coordinate grid chart to add the functionality:

chart1.focusCharts = function (chartlist) {
    if (!arguments.length) {
        return this._focusCharts;
    }
    this._focusCharts = chartlist; // only needed to support the getter above
    this.on('filtered', function (range_chart) {
        if (!range_chart.filter()) {
            dc.events.trigger(function () {
                chartlist.forEach(function(focus_chart) {
                    focus_chart.x().domain(focus_chart.xOriginalDomain());
                });
            });
        } else chartlist.forEach(function(focus_chart) {
            if (!rangesEqual(range_chart.filter(), focus_chart.filter())) {
                dc.events.trigger(function () {
                    focus_chart.focus(range_chart.filter());
                });
            }
        });
    });
    return this;
};

We also need to copy the rangesEqual helper function out of coordinateGridMixin in order to get this working.

I created an example and added it to the dc.js examples:

http://dc-js.github.io/dc.js/examples/multi-focus.html

You can see the source here:

https://github.com/dc-js/dc.js/blob/master/web/examples/multi-focus.html

This would be nice to support natively, including the opposite case zoom -> filter, which does require changing dc.js internals. I added an issue to track the idea: https://github.com/dc-js/dc.js/issues/820

like image 171
Gordon Avatar answered Dec 04 '22 02:12

Gordon