aponysus
aponysus

Reputation: 102

Sorting D3.js bar chart multiple ways (ascending and descending)

Here is a fiddle of my code.

I'm trying to sort a bar chart in either ascending or descending order, after randomly refreshing the data feeding the chart. When I click my 'Update Data' button and then click either of the sort buttons, it works fine. But then when I click the other sort button (or even the same one again!), I get seemingly random results.

I think its because the data isn't being sorted itself, so maybe when each subsequent 'sort' click event happens, that event is agnostic to the sorting that happened before???

Yet I thought binding the data to the DOM elements took care of that issue?

Here is my sort function:

var sortBars = function (type) { 

        y.domain([d3.min(dataset, function(d) { return d.value; }),
             d3.max(dataset, function(d) { return d.value; })])
         .range([Math.floor(height*0.2), height*0.8]);

        sortItems = function(a, b) {
            if (type == 'asc') {
                return a.value - b.value;
            }
            else if (type == 'desc') {
                return b.value - a.value;
            }
        }

        var delay = function(d, i) {
                return i * 50;
            }

        svg.selectAll('.bar')
            .sort(sortItems)
            .transition()
            .delay(delay)
            .duration(1000)
            .attr('x', function(d, i) {
                return x(i);
            });

        svg.selectAll('.label')
            .sort(sortItems)
            .transition()
            .delay(delay)
            .duration(1000)
            .text(function(d) {
                return d.value;
            })
            .attr('text-anchor', 'middle')
            .attr('x', function(d, i) {
                return x(i) + x.rangeBand() / 2;
            })
            .attr('y', function(d) {
                return height - y(d.value) + margin.top;
            });

        x.domain(dataset.sort(sortItems)
            .map(function(d) { return d.key }));

        svg.select('.axis')
                .transition()
                .delay(delay)
                .duration(1000)
                .call(xAxis);

};

d3.select('.sort-asc').on('click', function() {
        sortBars('asc');
});
d3.select('.sort-desc').on('click', function() {
        sortBars('desc');
});

Upvotes: 1

Views: 1988

Answers (1)

meetamit
meetamit

Reputation: 25157

First, instead of calling x(i), you want to call x(d.key), because you're creating the ordinal domain out of the keys.

Next, setting x.domain(...) to the re-sorted array is good, which is why your xAxis is updating correctly. However, you're doing it too "late", near the end of the function. Since you want the domain re-sorting to also affect the result returned by x(d.key), you need to move that line so it happens before you update the bars and labels.

I'm pretty sure you can remove the calls to sort(sortItems) on the bars and labels, since the updates you're doing don't depend on i within the selection.

Finally, for style points, you can use d3 to define the sort functions like so (taking advantage of d3.ascending(a.value, b.value) and d3.descending():

    var sortItems = function(a, b) {
        return d3[type == 'asc' ? 'ascending' : 'descending'](a.value, b.value)
    }

Or, if you pass "ascending" or "descending" as the type param:

    var sortItems = function(a, b) { return d3[type](a.value, b.value) }

Upvotes: 1

Related Questions