Search code examples
javascriptarrayscsvc3

Stacked bar graph not coming in a proper way


I was trying to create a stacked bar graph with timeseries ticks on my x-axis using C3js from a CSV content that I'm getting from web. Below is a part of the csv content that I want to parse.

Time Series,Category,Duration
8/4/12 12:00 AM,Post Processor,387
8/7/12 12:00 AM,Post Processor,407
8/9/12 12:00 AM,Post Processor,398
8/20/12 12:00 AM,Post Processor,327
8/24/12 12:00 AM,Post Processor,391
8/4/12 12:00 AM,Response Processing,517
8/7/12 12:00 AM,Response Processing,543
8/9/12 12:00 AM,Response Processing,532
8/20/12 12:00 AM,Response Processing,436
8/24/12 12:00 AM,Response Processing,522
8/28/12 12:00 AM,Response Processing,457
9/12/12 12:00 AM,Response Processing,471
9/14/12 12:00 AM,Response Processing,453
9/16/12 12:00 AM,Response Processing,510
8/4/12 12:00 AM,External Calls,1035
8/7/12 12:00 AM,External Calls,1087
8/9/12 12:00 AM,External Calls,1064
8/20/12 12:00 AM,External Calls,874
8/24/12 12:00 AM,External Calls,1044
8/28/12 12:00 AM,External Calls,915
9/12/12 12:00 AM,External Calls,944
8/4/12 12:00 AM,Internal Processing,1294
8/7/12 12:00 AM,Internal Processing,1359
8/9/12 12:00 AM,Internal Processing,1331
8/20/12 12:00 AM,Internal Processing,1093
8/24/12 12:00 AM,Internal Processing,1306

So, the category column had to be grouped and presented as values. Hence I had to do some manual processing of the content. I'm able to somehow generate the graph for this but the graph stacks are not proper and they overlap, I'm not sure why. I'm pasting my graph generation section of code.

var chart = c3.generate({
    bindto: '#chart',
    data: {
        x: 'x',
        xFormat: '%m/%d/%y %I:%M %p',
        columns: final_data,
        type: 'bar',
        groups: val
    },
    axis: {
        x: {
            type: 'timeseries',
            tick: {
                format: '%b %d',
                fit: true
            }
        }
    },
    zoom: {
        enabled: true
    }
});

where the variable final_data is an array of array with the data semantic as shown below. This processing was done to get things in a right form to make it understandable to C3.

[
   ['x', ..... comma separated date in csv ....],
   ['Post Processor', ..... values with post processor .....],
   ['Response Processing', ..... values with request processing .....],
   ['External Calls', .... values with external calls .....],
   ['Internal Processing', ..... values with internal processing....]
]

The array final_data is generated by a very vague code. Pasting it below.

var master_object = {
   count: 0,
   values: [],
   data: []
};

var isKeyPresent = function(key) {
   if(master_object.values.indexOf(key) == -1)
       master_object.values.push(key);

var trump = master_object.data;
var check, returnable;
trump.forEach(function loop(anElementInMaster, i) {
    if(anElementInMaster[0] == key) {
        check = 1;
        returnable =  i;
        loop.stop = true;
    }
});
if(check != 1)
       return -1;
   else
       return returnable;
};

var kyaToBhiFunction = function(csvEntryArray) {
   var index;
   var key = csvEntryArray[0];
   if((index = isKeyPresent(key)) >= 0) {
       master_object.data[index].push(csvEntryArray[1]);
   }
   else {
       master_object.data.push(csvEntryArray);
   }
};

var resetMasterObject = function() {
    master_object = {
        count: 0,
        values: [],
        data: []
    };
};

playerForTimeSeries_c3 = function(csvReport) {
    var csvReportArray = d3.csv.parseRows(csvReport).slice(1);
    var timeseries_array = [], val = [], values;
    var final_data;
    csvReportArray.forEach(function(entry) {
        timeseries_array.push(entry.shift());
        kyaToBhiFunction(entry);
    });

    timeseries_array.unshift('x');
    final_data = master_object.data;
    values = master_object.values;

    // graph generation code continues here
}

and here's the kind of graph I get (Unstacked bars, bad x-axis) Please help me out with this.

http://screencast.com/t/poax9WuIgv


Solution

  • You were constructing the columns object incorrectly. Each date should have an entry for each of the groups.

    You need something like

    var csvReportArray = d3.csv.parseRows(csvReport).slice(1);
    
    var final_data = [['x']];
    var values = [];
    
    csvReportArray.forEach(function (entry) {
        // date index
        var xIndex = final_data[0].indexOf(entry[0]);
        if (xIndex === -1) {
            final_data[0].push(entry[0]);
            xIndex = final_data[0].length - 1;
            // push in 0 for all the groups
            final_data.forEach(function (e, i) {
                // skip the x array
                if (i)
                    e.push(0);
            })
        }
    
        var valueIndex = values.indexOf(entry[1])
        if (valueIndex === -1) {
            values.push(entry[1])
            valueIndex = values.length - 1;
            // insert a new value (group)
            final_data.push([entry[1]]);
            // fill it with 0s
            final_data[0].forEach(function (e, i) {
                // skip the label
                if (i)
                    final_data[valueIndex + 1].push(0);
            })
        }
    
        final_data[valueIndex + 1][xIndex] = entry[2];
    });
    
    var val = [values];
    

    Because you've selected a time series, your x array will be interpreted as dates and they will be spaced accordingly i.e. Jan 1 will be 5 as far from Jan 5 as Jan 5 is from Jan 6 even if these 3 are consecutive elements in the x array.

    That said, you can avoid the bar overlap by reducing the bar width. However the exact value depend on the width of your graph and how close 2 bars are (which in turn depend on how close the x values are)

    You can set the bar width by passing it in via the options

    bar: {
        width: 20
    },
    ...
    

    Fiddle - http://jsfiddle.net/7k2js964/


    enter image description here