javascriptd3.jsdata-visualizationdc.js

Scale of chart generated by using dc.js is returning NaN


I am very new to DC/D3 libraries. I am trying to incorporate DC with ReactJS by having a separate pure JS file that is a reusable D3 component. I am following this example here. Here is the dummy data I am using: json snippet.

This is my App.js:

state = {
        data: null,
    };

    componentDidMount() {
        console.log("componentDidMount");
        d3.json("dummy_data.json").then(data => {
            this.setState({data: data});
            console.log("Data is updated!");
        })
        this.createVisualization();
    }

    componentDidUpdate() {
        console.log("componentDidUpdate");
        this.updateVisualization();
    }

    createVisualization = () => {
        console.log("createVisualization");
        this.visualization = new Dashboard({
            parentElement: d3.select(this.node)
        });
    }

    updateVisualization = () => {
        this.visualization.updateVis(this.state.data);
        console.log("updateVisualization finished");
    }

    render() {
        return (
            <div style={{width: '100vw'}} id="app"
                 ref={node => this.node = node}>
                <h1>Hello World!!!</h1>
            </div>
        )
    }

And this is my JS file (dashboard.js) used to generate/render chart:

export default class Dashboard {
    constructor(_config) {
        // setting up based on input config
        this.parentElement = _config.parentElement;
        this.initVis();
    }

    initVis() {
        let vis = this;

        vis.chartContainer = vis
            .parentElement
            .append("div")
            .attr("id", "chart-container")
            .style("width", "100%");

        vis.chart = new dc.ScatterPlot("#chart-container");
        vis
            .chart
            .width(768)
            .height(480)
            .margins({top: 0, right: 10, bottom: 20, left: 50})
            .x(d3.scaleLinear())
            .elasticX(true)
            .elasticY(true)
            .brushOn(false) // turn off brush-based range filter
            .symbolSize(8) // set dot radius
            .clipPadding(10);
        const f = vis.chart.x();
        console.log(f.domain());
        console.log("Finish initVis")
    }

    updateVis(newData) {
        let vis = this;
        vis.cf = crossfilter(newData);
        console.log(newData);
        vis.chart
            .on('pretransition', () => {
                // console.log(vis)
                // const xext = d3.extent(vis.chart.group().all(), d => d.key);
                // console.log(newData.map(d => [+d.age, +d.readmission_risk]));
                const r = regression.linear(newData.map(d => [d.age, d.readmission_risk]));
                const m = r.equation[0],
                      b = r.equation[1];
                const [x1, x2] = vis.chart.x().domain();
                const points = [
                    [
                        x1, m * x1 + b
                    ],
                    [
                        x2, m * x2 + b
                    ]
                ];
                const xScale = vis.chart.x();
                const yScale = vis.chart.y();
                const margins = vis.chart.margins();
                console.log(xScale(20));
                var line = vis.chart.g().selectAll('line.regression').data([points]);
                // console.log(vis.chart.x().domain());
                function do_points(line) {
                line
                    .attr('x1', d => xScale(d[0][0]) + margins.left)
                    .attr('y1', d => yScale(d[0][1]) + margins.top)
                    .attr('x2', d => xScale(d[1][0]) + margins.left)
                    .attr('y2', d => yScale(d[1][1]) + margins.top);
                }

                line = line.enter().append('line')
                        .attr('class', 'regression')
                        .call(do_points)
                        .merge(line);
                line.transition().duration(vis.chart.transitionDuration()).call(do_points);

            })
        vis.renderVis();
    }

    renderVis() {
        let vis = this;
        const ageDimension = vis.cf.dimension(d => d.age);
        const ageGroup = ageDimension.group();
        vis.chart.dimension(ageDimension).group(ageGroup);
        vis.chart.render();
        console.log("rendering");
    }

I have identified the problem to be the xScale in the call-back function in the updateVis method. It is returning NaN. But I tried to call the xScale in the initVis the scale function seems working fine and return [0,1]. I have been struggling because of this for a long time. Any help would be appreciated!

Update: Here is a snapshot of the error message I got:enter image description here


Solution

  • Thanks for including a reproducible example. It's really hard to debug D3 and dc.js code by just staring at it without running it.

    The problem here is that the scatter plot expects the group keys to be a two-element array of numbers, not just a single number. You can see that the key_function(), in the regression example which you started from, returns a function returning such keys.

    Changing your dimension accordingly:

        const ageDimension = vis.cf.dimension(d => [d.age, d.readmission_risk] );
    

    caused the chart to appear.

    I also had to add the CSS from the example in order to get the regression line to show; I created dashboard.css:

      line.regression {
        stroke: red;
        stroke-width: 5;
        opacity: 0.5;
      }
    

    And added it to dashboard.js:

    import './dashboard.css';
    

    Result:

    scatterplot with regression line

    Incidentally, the way you have calculated the regression line, it won't update when the data is filtered. If you want it to do so, this should work:

    const r = regression.linear(vis.chart.group().all().filter(kv => kv.value).map(kv => [kv.key[0], kv.key[1]]));
    

    This uses the keys from the chart's group, but only the ones which are filtered "in" (value > 0).