Dc.js applying range Chart to multiple graphs

泪湿孤枕 提交于 2019-11-30 05:19:54

问题


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!


回答1:


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




回答2:


The range chart works on the .x()-scale of the focus chart.

What you can do is to define one scale globally and use it for every chart that should be controlled by the range chart. Only one of the focus charts has to set the .rangeChart() property.

That is undefined behaviour I think which means this could change in the future.

Pseudo code:

var xScale = d3.scale.linear();
var xScaleRange = d3.scale.linear();

var rangeChart = dc.lineChart('#R').x(xScaleRange).[...];

var focusChartA = dc.barChart('#A').x(xScale).rangeChart(rangeChart).[...];
var focusChartB = dc.barChart('#B').x(xScale).[...];


来源:https://stackoverflow.com/questions/27445259/dc-js-applying-range-chart-to-multiple-graphs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!