culturalanomoly
culturalanomoly

Reputation: 1301

d3JS Chart Not Rendering Properly

Given the code below. I'm able to create a bar chart, populated by random data and get it to display appropriately. Unfortunately, for me, I can't get the update of the graph to render appropriately.

The axes appear to be updating accordingly but the graph's bars are not displaying in the proper location, relative to its axes. There's a JSFiddle below that illustrates the issue. You'll notice the bars being rendered along the x-axis in various locations.

var Chart = (function (){
    function Chart(options){
        this.chart = d3.select(options.el);
        this.margin = options.dimensions.margin;
        this.height = options.dimensions.height;
        this.width = options.dimensions.width;
        this.data = options.data;
    }
    return Chart;
}());
var BarChart = (function(){
    function BarChart(options){
        Chart.call(this, options);

        this.xscale;
        this.vguidescale;
        this.vaxis;
        this.haxis;

        this.height = this.height - this.margin.top - this.margin.bottom;
        this.width = this.width - this.margin.left - this.margin.right;
        this.barcolors = createBarColors(options.barcolors);
        this.verticalTickCount = options.verticalTickCount;
        this.horizontalTickRangeCount = options.horizontalTickRangeCount || 10;
        this.chart = this.chart
            .append('svg')
                .attr('class', 'metric-chart')
                .attr('width', this.width + this.margin.left + this.margin.right)
                .attr('height', this.height + this.margin.top + this.margin.bottom)
                .append('g')
                    .attr('class', 'metric-chart__chart-bar')
                    .attr('transform', 'translate('+ this.margin.left +', '+ this.margin.top +')');
        this.vguide = d3.select('svg').append('g');
        this.hguide = d3.select('svg').append('g');
    }

    BarChart.prototype = Chart.prototype;
    BarChart.prototype.update = function(data) {
        var that = this;

        this.xscale = _getAxisScaleX(data, this.width);
        this.yscale = _getAxisScaleY(data, this.height);

        this.bars = this.chart.selectAll('rect')
            .data(d3.values(data))
            .order();

        this.bars.remove()
        this.bars.enter()
            .append('rect')
                .style('fill', function(d,i) { return that.barcolors[i % that.barcolors.length]; })
                .attr('width', that.xscale.rangeBand())
                .attr('x', function(d,i) {
                    return that.xscale(i);
                })
                .attr('height', 0)
                .attr('y', this.height)
                .transition()
                .attr('height', function(d) { return that.yscale(d); })
                .attr('y', function(d) {
                    return that.height - that.yscale(d);
                })
                .duration(1500)
                .ease('elastic');

        this.bars.exit().transition().attr('height', 0).remove();

        this.vguidescale = _getVerticalGuideScale(data, this.height);
        this.haxis = d3.svg.axis()
            .scale(this.xscale)
            .orient('bottom')
            .tickValues(this.xscale.domain().filter(function(d, i) {
                var remainder = i % that.horizontalTickRangeCount;
                return remainder === 0;
            }));
        this.vaxis = d3.svg.axis()
            .scale(this.vguidescale)
            .orient('left')
            .ticks(this.verticalTickCount);

        this.vguide
            .attr('transform', 'translate(' + this.margin.left + ', ' + this.margin.top + ')')
            .attr('class', 'metric-chart__vert-guide');
        this.hguide
            .attr('transform', 'translate(' + this.margin.left + ', ' + (this.height + this.margin.top) + ')')
            .attr('class', 'metric-chart__horz-guide');

        this.vaxis(this.vguide);
        this.haxis(this.hguide);
    };

    function _getAxisScaleX (data, width) {
        return d3.scale.ordinal()
            .domain(d3.range(0, data.length))
            .rangeBands([0, width], 0.2);
    }

    function _getAxisScaleY (data, height) {
        return d3.scale.linear()
            .domain([0, d3.max(data)])
            .range([0, height]);
    }

    function _getVerticalGuideScale (data, height) {
        return d3.scale.linear()
            .domain([0, d3.max(data)])
            .range([height, 0]);
    }

    function createBarColors(colors){
        return colors || ['#363636','#565656', '#868686'];
    }

    return BarChart;
}());

Usage:

var chartDimensions = {
    margin : { top: 10, right: 10, bottom: 25, left: 25 },
    height : 200,
    width : 500
}, chartoptions;

chartoptions = {
    el : '.graph',
    data: genRandomArray(0, 50),
    dimensions : chartDimensions,
    verticalTickCount : 10,
    horizontalTickRangeCount : 5
};

/* generate bar chart and update on an interval ... */
var chart = new BarChart(chartoptions);
(function updateChart(){
    chart.update(genRandomArray(0, genrandom(10, 90)));
    setTimeout(updateChart, 4000);
}());

Issue Demo:

http://jsfiddle.net/h8ztvmq4/

Upvotes: 1

Views: 725

Answers (1)

burnpanck
burnpanck

Reputation: 2196

I think you are slightly misusing the D3 selections concept: When you do this.bars = ... .data() ... you end up with the update selection, i.e. all the elements whose value changed since before. On that selection, you called .remove(), implying the changed elements should be removed. This is clearly not what you want. After that, you properly deal with the enter() and exit() selections, adding new elements and removing old elements that are not needed anymore. If you remove just that first call to this.bars.remove() before the this.bars.enter() call, It looks more like what you probably want: The tail (changing of the number of bars) works as expected. Only, the head is still not updated properly. For this, you will need to use the update selection that you have just before the enter() call, to modify the appropriate properties of the bars (e.g. x,y, height), maybe using a transition.

Here's an updated fiddle. It still doesn't look perfect, since you change the x-scale whenever the number of bars changes. Thus, the changing bars need to move horizontally, but the exiting ones doe not move in accord, so these two selections overlap briefly. You should probably consider updating the axis in a separate transition.

Upvotes: 2

Related Questions