Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retain Formatting Using Group By Aggregation in Google Visualization API

Using Google's Visualization API, I use google.visualization.data.group to create sub-tables based on my raw data. My raw data uses the trick of {v:"US", f:"United States"} to display something other than the value, but when I use the aggregation function, the formatting is eliminated leaving only the "US" portion.

Is there any way to retain the original formatting, or an easy way to add it back on DataTables created using the group aggregation?

Sample data:

[2010, {v:"MA", f:"Morocco"}, {v:"002", f:"Africa"}, {v:"002", f:"Northern Africa"}, 21.12724],
[2010, {v:"AW", f:"Aruba"}, {v:"019", f:"Americas  "}, {v:"019", f:"Caribbean"}, 0.98],
[2010, {v:"AF", f:"Afghanistan"}, {v:"142", f:"Asia"}, {v:"142", f:"Southern Asia"}, 0.9861],
[2010, {v:"AO", f:"Angola"}, {v:"002", f:"Africa"}, {v:"002", f:"Middle Africa"}, 5.11774],

Aggregation Function:

var countryData = google.visualization.data.group(
  rawData, 
  [0, 1], 
  [{'column': 4, 'aggregation': google.visualization.data.sum, 'type': 'number'}]
);

Edit:

On further thought, it's probably impossible to group with format since there is no guarantee that the format for each value will be consistent. With that in mind, it's probably better (or only possible) to write a function that will add the formats to each column of my data. So the question becomes, "How the heck do I do that?"

I would really rather not create my raw data as unformatted values alone, and then additional tables for looking up the formats for each value. That would require an additional 2 tables (one for regions which is 28 rows, one for countries which is over 240 rows), and then creating two functions to look through each value in the grouped table (which will have 30+ years of data, meaning thousands of lines) adding the values.

That seems like a really complex solution.

Is there some way to do this with modifier functions? Can I write a function to return each value in the table as a {v: "US", f: "United States"} formatted object? Or is there an easy way to write a column formatter that will look up the appropriate value in my original table and adopt that format? Which would cause the least headaches both for me (who has to write it), and for the users (who have to load it)?

EDIT 2:

It looks like I should be able to create a formatter for the new table using something like this:

function (dt, row) {
    return {
        v: (dt.getValue(row, 1) / 1000000),
        f: (dt.getValue(row, 1) / 1000000) + 'M'
    }
}

But the issue becomes that since I am not dealing with number formats, I would have to create some sort of lookup table that will take the value, look it up in a lookup table, and then return the appropriate format. It also looks like I may have to loop through the whole table, row by row, which is thousands of lines.

I can't imagine that there isn't an easy way to do this without some brute force looping and assigning values.

EDIT 3:

So I tried something tricky. Rather than setting each row as a value/format, I created the value/format portion as a string, and then after grouping used eval() to evaluate the objects. This worked great. Here is the data:

[2010, "{v: 'MA', f: 'Morocco'}", 21.13],
[2010, "{v: 'AW', f: 'Aruba'}", 0.98],
[2010, "{v: 'AF', f: 'Afghanistan'}", 0.99],
[2010, "{v: 'AO', f: 'Angola'}", 5.12],

Here is the new code:

  var countryCount = countryData.getColumnRange(0).count;

  for (var i = 0; i <= countryCount; i++) {
    countryData.setValue(i, 1, eval('(' + countryData.getValue(i,1) + ')'));
  };

The issue is that when I output this to a Google DataTable, it shows {v: 'AE', f: 'United Arab Emirates'} despite the fact that checking the result with eval properly gives me:

>>> eval('(' + countryData.getValue(i,1) + ')')
Object v="AE" f="United Arab Emirates"

So what am I doing wrong here?

like image 230
jmac Avatar asked Dec 28 '12 00:12

jmac


1 Answers

Okay, I figured this one out (how obnoxiously complex it was).

I tried a new approach. I reformatted my data and then created a function to return a value/format based on a divider within that string. So my data now looks like this:

[2010, "'MA'|'Morocco'", 21.13],
[2010, "'AW'|'Aruba'", 0.98],
[2010, "'AF'|'Afghanistan'", 0.99],
[2010, "'AO'|'Angola'", 5.12],

I then use this to get the divider location for column 1:

var countryCount = countryData.getNumberOfRows();

for (var i = 0; i <= countryCount; i++) {
  var stringToSplit = countryData.getValue(i,1);
  var dividerLocation = stringToSplit.indexOf("|");
  alert("Divider: " + dividerLocation + ", String: " + stringToSplit);
  countryData.setValue(i, 1, splitFormat(dividerLocation, stringToSplit));
};

And then I use this function to split the string:

  function splitFormat(dividerLocation, stringToSplit) {
    // alert("entered splitFormat Function");
    var stringValue = "";
    var formatValue = "";
    stringValue = stringToSplit.substring(0, dividerLocation);
    formatValue = stringToSplit.substring(dividerLocation + 1)
    alert("v: " + stringValue + ", f: " + formatValue);
    return {
      v: stringValue,
      f: formatValue
    }
      }

The issue is that I am defining column 1 of my data as 'string', but firebug is telling me that the object returned from the splitFormat() function is an object (since it is an array I suppose). Even if I set the original datatable with a v: and f: component, it doesn't want to accept a returned array object value, as FireBug gives me the following oh-so-helpful advice:

"Error: Type mismatch. Value [object Object] does not match type string in column index 1 (table.I.js,137)"

The issue is that while you can define a DataTable using the {v: , f:} syntax, you can't return that syntax back in to the table because the value for that column is set as string. Instead I used the "setFormattedValue" property of the DataTable to fix the issue:

  function drawVisualization() {
    var countryTable = new google.visualization.Table(document.getElementById('table'));

    var countryCount = countryData.getNumberOfRows() - 1;

    for (var i = 0; i <= countryCount; i++) {
      var stringToSplit = countryData.getValue(i,1);
      var dividerLocation = stringToSplit.indexOf("|");
      var stringValue = stringToSplit.substring(0, dividerLocation);
      var stringFormat = stringToSplit.substring(dividerLocation + 1);
      countryData.setValue(i, 1, stringValue);
      countryData.setFormattedValue(i, 1, stringFormat);
    };

This properly gave me the appropriate values for both, even though it is a bit intensive for large data sets. If anyone knows of an easier way to do this, then I would be more than happy to hear it.

like image 182
jmac Avatar answered Sep 25 '22 04:09

jmac