Search code examples
chartszoomingrenderdc.js

Mouse Zooming not Working for dc.js Line Chart


I'm creating an Angular wrapper for the dc.js library. I've been closely following the annotated source to get some examples working, and I've had some success with pie and row charts. I was able to get the line chart working and I can select regions with the brush. I am not, however, able to zoom. I have disabled the brush and enabled mouse zooming.

When I hover the chart and move my scroll wheel the other charts on the page react, so filters are taking effect. When I select a slice of a pie it does filter down the chart:

enter image description here

enter image description here

In the documentation I noticed there's a zoomed event. I do not see any usage of .on('zoomed', ...) in the annotated source, yet the resultant line chart very clearly (and cleanly) supports zooming. I'm unsure if I need to implement a listener to redefine the domain zoomable chart's domain on zoom. If so, I'm not exactly sure how to get the lowest and greatest values for that new domain.

EDIT: Gordon Woodhull has stated (in the comments) that the zoom functionality is derived from d3-zoom, so there is no need to listen to zoomed events and recalculate the domain.

Also a view into how the charts are being created in my wrapper. Charts components contain a <div> that it gives to dc.js to place a chart in. A Chart component takes as input a ChartSettings model to configure the chart before rendering it. A component that shows a chart would thus do the following:

<dc-chart [settings]="priceChart"></dc-chart>

The Chart component itself uses the settings (which specifies the type) to determine the proper dc.js chart object to instance. The component then uses a service implementing crossfilter to retrieve the dimension and group for the desired domain and range (specified in the given settings). Using the settings and the service's response, the component configures the chart:

let applicableMixins = {
  baseMixin: true, // The base mixin applies to all dc.js charts
  colorMixin: true, // The color mixin applies to all dc.js charts
  coordinateGridMixin: ["bar", "line", "bubble"].indexOf(cS.type) >= 0,
  marginMixin: ["row", "bar", "line", "bubble"].indexOf(cS.type) >= 0,
  // TODO: bubbleMixin
  // TODO: capMixin
  // TODO: stackMixin
}

let c = this.chart;
// Note: All chart settings are assigned default values in chart-settings.model, so there's no
// need to set default values here. All properties in a ChartSettings object are defined.
// Base Mixin
c.height( cS.height ); // fills parent when null
c.width( cS.width );
c.dimension(this.latestReceipt.dimension);
c.group(this.latestReceipt.group);

// Color Mixin
c.colors(d3.scaleOrdinal(d3.schemeCategory10));

// Coordinate Grid Mixin
if(applicableMixins.coordinateGridMixin) {
  c.brushOn(false);
  c.elasticX(cS.elasticX);
  c.x(cS.xScale); // d3.scaleTime().domain([new Date(1985, 1, 1), new Date(1986, 4, 1)])
  c.round(d3.timeMonth.round); // from example
  c.xUnits(d3.timeMonths); // from example
  c.renderArea(cS.renderArea); // true
  c.mouseZoomable(true);

  // Override for example.
  c.width(990);
  c.height(200);

  c.elasticY(cS.elasticY); // true
  c.renderHorizontalGridLines(true);
  c.renderVerticalGridLines(false);
  c.zoomScale([0, 100]);
  c.zoomOutRestrict(false);
  c.margins({
    top: 30, right: 50, bottom: 25, left: 40
  });
}

In an attempt to get zooming working I mimicked the annotated source as much as possible. I'm using generated data that simulates the value of a stock that's a little more verbose than the data used in the dc.js mainpage example.

[
    {
        "open": 110.47,
        "high": 115.16,
        "low": 104.79,
        "close": 117.0982,
        "date": "01/01/1985",
        "quarter": 1,
        "isGain": true,
        "dayOfWeek": "Tue"
    },
    {
        "open": "117.10",
        "high": 120.58,
        "low": 117.03,
        "close": 124.126,
        "date": "01/02/1985",
        "quarter": 1,
        "isGain": true,
        "dayOfWeek": "Wed"
    },
    {
        "open": "124.13",
        "high": 128.31,
        "low": 116.28,
        "close": 119.16479999999999,
        "date": "01/03/1985",
        "quarter": 1,
        "isGain": false,
        "dayOfWeek": "Thu"
    },
    ...
    {
        "date": "04/01/1986",
        ...
    }
]


Solution

  • After some back and forth, we figured out that the problem is simply that elasticX is not compatible with mouseZoomable, since it permanently locks the x scale domain to the full range of values in the data.

    In order to allow mouseZoomable(true) you need to make sure elasticX is false.

    Of course, this also means you need to calculate the X scale domain yourself, which is mildly annoying. Maybe dc.js should only "elast" the X domain once (on render) if both options are set. That would be convenient and probably what is expected.