Showing posts with label Google charts. Show all posts
Showing posts with label Google charts. Show all posts

Thursday, January 31, 2013

Pareto charts with Google charts

As a follow-up to my recent post about histograms in Google charts I thought it would be nice to have a Pareto chart too.

I often use Pareto charts only in the very simple sense of showing bars in descending order but if one wants to include the ascending percentage line there are two challenges here:

  • for a given data set one needs to calculate the accumulated percentages
  • the chart needs to show two graphs (columns and line) and two axes (left for bars, right for percentages)

    The first challenge needs to be coded, the second can be done with so called Combo Charts.

    Assume we have the following data represented as a 2-dimensional array in Javascript, not necessarily ordered since they will be reordered anyway. The column titles are kept in a separate array with an additional title for the percentages.

    var dataSet1 = [
        [ 'A', 25 ],
        [ 'B', 125 ],
        [ 'C', 35 ],
        [ 'D', 10 ],
        [ 'E', 12 ],
        [ 'F', 70 ],
        [ 'G', 60 ]
        ];
    
    var dataTitle = [ 'Category', 'Size', 'Pctg' ];
    

    The Pareto chart looks like this:

    How did I get there?

    There is a function which

  • first sorts the array according to column 2 in descending order (that is done by supplying the JavaScript built-in 'sort' with a sorting function of our own)
  • then calculates the total of column, calculates the percentages and puts the percentages into a new column in each row
  • prepends the data array with its title row

    function paretorize() {
      // Sort the dataSet array usung column 2
      dataSet1.sort( function(a,b) {
        return b[1] - a[1];
      });
    
      // Calculate the total of column 2
      var sum = 0;
      for(row=0; row<dataSet1.length; row++) {
        sum += dataSet1[row][1];
      }
    
      // Calculate the accumulating percentages
      // and add them into a new column in each row
      var accum = 0;
      for(row=0; row<dataSet1.length; row++) {
        dataSet1[row].push( accum+100*dataSet1[row][1]/sum );
        accum = dataSet1[row][2];
      }
    
      // Add the title row at the beginning of dataSet
      // ('unshift' is not supported in IE8 and earlier)
      dataSet1.unshift( dataTitle );
    }
    
    paretorize();
    

    Now that the dataSet array has been constructed it can be fed to the Google charts and a few options need to be set to take care of the second graph and axis. This is achieved by making vAxes an array of two axes with their own titles (and other attributes if needed) and by setting seriesType and series to specify standard behaviour and the special setting for the line chart.

    function drawChart() {
      var data = google.visualization.arrayToDataTable( dataSet1 );
    
      var options = {
        title:  'Pareto chart',
        legend: { position: 'none' },         // no legend
        // Create two vertical axes taking its titles from the first row
        vAxes:[
           { title: dataSet1[0][1], minValue: 0 }, 
           { title: dataSet1[0][2], minValue: 0, maxValue: 100 }
         ],
        hAxis:  { title: dataSet1[0][0] },
        backgroundColor: {strokeWidth: 2 },   // to get a nice box
        seriesType: "bars",                   // the standard chart type
        // the second data column should be of type 'line' and should be associated with the second vertical axis
        series: {1: {type: "line", targetAxisIndex: 1 }},  
        };
    
      // Note: this calls a ComboChart !!!
      var chart = new google.visualization.ComboChart(document.getElementById('chart_div'));
      chart.draw(data, options);
    }
    

    The same comments apply about chart width, number of data rows, size of columns as in the histogram blog.

    Putting all together

    The code snippets above are part of a Javascript script and need to be put into an HTML page as follows in order to display the chart.
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script type="text/javascript">
    google.load("visualization", "1", {packages:["corechart"]});
    google.setOnLoadCallback(drawChart);
    
    ... put here all three code snippets from above ...
    
    </script>
    <div id="chart_div" style="width: 400px; height: 300px;"></div>
    

    One thing to note here: if you create several charts on one HTML page each of them needs to have its own id, function and needs to be set separately like

    google.setOnLoadCallback(drawChartA);
    google.setOnLoadCallback(drawChartB);
    google.setOnLoadCallback(drawChartC);
    ...
    
    function drawChartA() {
    ...
      // ComboChart or any other chart type of course
      var chart = new google.visualization.ComboChart(document.getElementById('chart_divA'));
    ...
    }
    
    function drawChartB() {
    ...
      var chart = new google.visualization.ComboChart(document.getElementById('chart_divB'));
    ...
    }
    
    function drawChartC() {
    ...
      var chart = new google.visualization.ComboChart(document.getElementById('chart_divC'));
    ...
    }
    
    and later place the chart wherever needed
    <div id="chart_divA" style="width: 400px; height: 300px;"></div>
    ...
    <div id="chart_divB" style="width: 400px; height: 300px;"></div>
    ...
    <div id="chart_divC" style="width: 400px; height: 300px;"></div>
    
  • Friday, January 25, 2013

    Histograms with Google charts

    It seems that today (Jan 2013) there is no histogram chart in Google charts so continuing my experimenting with column charts in the previous post I decided to look at this particular problem.

    Before looking at charts lets first look at what we have. In a very general sense we have

  • a series of data
  • a list of bins.
    The data points will be distributed into the bins according to some given criteria and the number of entries per bin (called frequency) will be counted.

    While this sounds very abstract a couple of examples will explain this.

    • Example 1: a numerical data series e.g. some kind of measurements like 1.5, 4.03, 2.6 etc. and the bins are disjunct intervals e.g. 0-2,2-4, etc. and the distribution is simply according to mathematical comparison greater or less.
    • Example 2: an ordinary series e.g. the list of grades of a class A,C,A,F,B,B,C,A,A etc. and the bins are the grades A,B,C,D,F and the criteria is simple enough.

    The histogram is then the graphical display of bin values vs. frequency as adjacent bars.

    Since Google charts can do column charts we are halfway there. What is missing is - and what I did - is to generate the two-dimensional array needed as an input for the Google column chart.

    My example covers only the simple numerical case. I start with a data series and some bins and of course a title which should explain what actually has been measured.

    var series = [   1, 3, 5, 7, 2.5, 3.1, 0.45, 5.1, 8.3, 4, 5.11, 3.9 ];
    var seriesTitle = "Length";
    
    var bins      = [   2,    4,    6 ];
    // There should be one for each bin plus an extra for larger values
    var binTitles = ['0-2','2-4','4-6','more'];  
    
    
    The bins should be interpreted as the endpoints of intervals i.e. everything up to and including 2, from 2 to 4, from 4 to 6. If there are values larger than 6 an unnamed bin will be added. The histogram looks like this:

    Starting with these data a two-dimensional array called histo will be created var histo = new Array(); which will eventually look like this:

    [ [ 'Length', 'freq' ]
      [ '0-2',  2 ]
      [ '2-4',  5 ]
      [ '4-6',  3 ]
      [ 'more', 2 ] ]
    

    First there is a function to initialize the histo array

    function initHisto(title,bins) {
      // header line
      histo.push([]);
      histo[0][0]   = title;
      histo[0][1]   = "freq";
    
      // create one row for each bin
      for(b=0; b<binTitles.length; b++ ) {
        // Create new row
        histo.push([]);
        histo[b+1][0]       = ""+binTitles[b];
        histo[b+1][1]       = 0;
      }
    }
    
    initHisto( seriesTitle, bins );
    
    The following function called frequency counts the entries per bin and puts it into the corresponding histo cell.
    var maxFreq     = 0;    // Necessary to set the maximum y-value
    
    function frequency( series, bins ) {
      for(d=0; d<series.length; d++ ) {
        // first bin
        if( series[d]<=bins[0] ) {
            histo[1][1]++;
            continue;
        }
        // last unnamed bin
        if( bins[bins.length-1]<series[d] ) {
            histo[bins.length+1][1]++;
            continue;
        }
        // any bin in between
        for(b=0; b<bins.length-1; b++ ) {
           if( bins[b]<series[d] && series[d]<=bins[b+1] ) {
              histo[b+2][1]++;
           }
        }
      }
    
      for(h=1; h<histo.length; h++ ) {
        if( maxFreq<histo[h][1] ) {
          maxFreq   = histo[h][1];
        }
      }
    }
    
    frequency( series, bins );
    

    Now that the histo array has been constructed it can be fed to the Google charts like google.visualization.arrayToDataTable( histo );. The chart needs some histogram specific tweaking which I'll explain.

    function drawChart1() {
      var data = google.visualization.arrayToDataTable( histo );
    
      var numGrids;
      //  if maxFreq is odd we make it even
      if( maxFreq%2 == 1 ) {
        maxFreq++;
      }
      //  the grid lines should be every even number
      numGrids        = maxFreq/2 +1;
    
      var options = {
        title:  'Histogram',
        legend: { position: 'none' },     // no legend
        bar:    { groupWidth: '99%' },    // in order to increase the thickness of the bars with a little space in between
        vAxis:  { title: histo[0][1], minValue: 0, maxValue: maxFreq, gridlines: { count: numGrids } },
        hAxis:  { title: histo[0][0] },
        backgroundColor: {strokeWidth: 2 },   // to get a nice box
      };
    
      var chart = new google.visualization.ColumnChart(document.getElementById('chart_divH'));
      chart.draw(data, options);
    }
    

    I put all of the above into one section enclosed by <script>..</script> tags but it could be separated and the histo calculation can be done separately.
    Unfortunately the chart options are not quite independent of the data. E.g. the number of grid lines needs to change and be made smaller for higher frequencies in order to display nicely, the groupWidth needs to be made bigger if more bins are displayed in order to see a little distinction between the bars and probably also depending on the final width and height of the chart. The width of the chart needs to increase if a larger number of bins should be displayed nicely.

    This will display the chart in the HTML body part.

    <div id="chart_divH" style="width: 300px; height: 300px;"></div>
    

    If you want to use other types of data series you need to change the frequency function and instead of mathematical greater/less comparisons you need to write the appropriate code for your case. The given 'grades' example could be something like if( series[d]==bins[b] ) { histo[b+1][1]++; }

  • Thursday, January 24, 2013

    Stacked column charts with Google charts (compared to gnuplot)

    Some time ago I had written about stacked histograms in gnuplot.

    Today I wanted to see how to achieve the same result with Google charts.

    For the purpose of this first exercise I was using the client side only and didn't worry about where the data should or could come from (in gnuplot data came from a file, naturally) but I put them directly into the JavaScript code.

    So first take a look at the result.

    As you can see the charts are pretty similar to gnuplot. There is currently one caveat in Google charts where you can't have multiline titles so it's displayed as one line.


    So here is the JavaScript code.
    The code is a slight modification of the Google charts column chart example and of course the calculation of the percentages for the second chart is the only piece where some real additional work is required.
    I left all settings at their defaults (like colours, fonts etc.) except for the obvious y-axis title and height, chart title, thickness of chart border (in order to get a nice box) and the most important isStacked setting.

    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script type="text/javascript">
      google.load("visualization", "1", {packages:["corechart"]});
     
      var dataSet = [
            ['year', 'foo', 'bar', 'rest'],
            ['1900', 20, 10, 20],
            ['2000', 20, 30, 10],
            ['2100', 20, 10, 10]
            ];
    
      // The first chart
    
      google.setOnLoadCallback(drawChart1); 
      function drawChart1() {
        var data = google.visualization.arrayToDataTable( dataSet );
    
        var options = {
          title: 'Stacked histogram\nTotals',
          vAxis: {title: 'total', maxValue: 100},  // sets the maximum value
          backgroundColor: {strokeWidth: 2 },  // to draw a nice box all around the chart
          isStacked: 'true'                    //  = rowstacked in gnuplot
        };
    
        var chart = new google.visualization.ColumnChart(document.getElementById('chart_div1'));
        chart.draw(data, options);
      }
    
      // The second chart
    
      var dataSet2    = dataSet;
      google.setOnLoadCallback(drawChart2);
      function drawChart2() {
        // Calculate the percentages
        var sum = new Array();
        for(row=1; row<dataSet.length; row++) {
          sum[row]        = 0;
          for(col=1; col<dataSet[row].length; col++) {
            sum[row]        += dataSet[row][col];
          }
          for(col=1; col<dataSet[row].length; col++) {
            dataSet2[row][col]      = 100*dataSet[row][col]/sum[row];
          }
        }
    
        var data = google.visualization.arrayToDataTable(dataSet2);
    
        var options = {
          title: 'Stacked histogram\n% Totals',
          vAxis: {title: 'total', maxValue: 100},  // sets the maximum value
          backgroundColor: {strokeWidth: 2 },  // to draw a nice box all around the chart
          isStacked: 'true'                    //  = rowstacked in gnuplot
        };
    
        var chart = new   google.visualization.ColumnChart(document.getElementById('chart_div2'));
        chart.draw(data, options);
      }
    </script>
    
    <div style="display: table;">
      <div style="display: table-row">
        <div id="chart_div1" style="width: 300px; height: 300px; display: table-cell;"></div>
        <div id="chart_div2" style="width: 300px; height: 300px; display: table-cell;"></div>
      </div>
    </div>
    

    Not having worked with Google charts before I did develop this in a couple of hours which shows that Google charts work well and can be learned pretty fast given that you have an idea about charts and its terminology.

    Why Google termed their axes horizontal and vertical rather than using the mathematical standards x and y is the secret of their product team though.

    There is some discussion whether and how to use Google charts (data privacy etc.) since it seems that data are sent to and processed by Google servers (or servers of other chart type providers) but that is beyond the scope of this blog.

    The next step will be to look into the server side piece of Google charts i.e. writing a data provider.