Quick Links

Other stuff

Site owners

  • Bruce Mcpherson

Embedding Google Motion Charts in Excel

What are google visualization charts

Google provides a growing number of cool charts that are not available in Excel. For this article we are going to focus on the the Google Motion Visualizations. Please take a look here to become familiar with what they are.  

There are a number of ways to create a google chart, some which are;
  1. Create a web page that contains the calls to the google visualization API, as well as the data to initialize the chart.
  2. Embed it as a gadget in a googledocs spreadsheet
  3. Embed as a gadget in a web page, and get the data as a feed from a website, usually from a googledocs spreadsheet
In this article we will look at how to create a web page directly from Excel, and embed it in your excel spreadsheet. The web page created can also be loaded on to a web site, as per this example here. As usual a fully functional workbook with all the code is available in the downloads section

  If you are going straight to the download  before reading this article, at least read the note on Flash, since it will not work until you have followed those instructions.

Using Excel data to populate Google motion charts

Google Motion is one of a family of visualization capabilities available through the Google Visualization API. If you are using googledocs, it is quite straightforward to include in your spreadsheet, just as it is simple to create a webpage with an embedded chart that takes its data from your googledocs spreadsheet. 

If you don't use googledocs though, you either have to convert your data to feed a web page embedded visualization gadget, or you have to write some JavaScript that calls the Google API, and includes the data as part of the code. In this article we will write a complete web page directly from your selected excel data,  and display the Google chart embedded in a form in your excel spreadsheet. Since we are creating a temporary html file,. you can of course load that directly on your website or open locally with a browser.

Before we start - a note about Flash

Google uses Flash to display these interactive charts. By default the Flash player is not able to access files that are held locally. Since you will be  embedding these charts in your workbook, the  .html file will be created locally and will need to be accessed locally . To get round this you need to first  follow the instructions on the macromedia website for creating a trusted location for local, flash enabled files. The excel form in the downloadable example gives you the opportunity to define where that is so that all .html files are created there.

What does the generated html code look like

We are going to write a handler in excel that will convert spreadsheet data, that looks like this in our example

... repeat many times...
  
....to an html file that looks like this

<html>
<head>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">

google.load('visualization', '1', {packages: ['motionchart']});
function funmotion() {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Function');
data.addColumn('date', 'Load Date');
data.addColumn('number', 'Differentiator');
data.addColumn('number', 'Complexity');
data.addColumn('number', 'Volume');

data.addRows(730);
data.setValue(0, 0, 'Box C');
data.setValue(0, 1, new Date(2009,8,7));
data.setValue(0, 2, 40.34);
data.setValue(0, 3, 1.35);
data.setValue(0, 4, 47);
data.setValue(1, 0, 'Box F');
data.setValue(1, 1, new Date(2009,8,7));
data.setValue(1, 2, 32.05);
data.setValue(1, 3, 0.95);
data.setValue(1, 4, 120);

.... repeat many times...
;
data.setValue(729, 0, 'Box S');
data.setValue(729, 1, new Date(2012,11,31));
data.setValue(729, 2, 49.98);
data.setValue(729, 3, 0.85);
data.setValue(729, 4, 151);
var motionchart = new google.visualization.MotionChart(document.getElementById('divmotion'));
motionchart.draw(data, {width: 696, height:480}); }
google.setOnLoadCallback(funmotion);</script></head>
<body>
<div id="divmotion" style="width: 696px; height: 480px;"></div>
</body>
</html>

A note on the requirements for the Google motion chart

At least 3 columns of data are required, the first being what's called the entity, the second a date, and the third would likely be a number. Subsequent fields would also be numbers and would be available interactively through the chart. The downloadable example does not validate this but a production example probably should, otherwise you could leave it to the chart to complain about the data type if there is a problem.

The downloadable tool

To get you started a downloadable workbook will provide all the necessary code, including a form to control the process and display the finished product. This is the tool in action.  You simply select the range where the column headings are and push the make the chart button. If you need to load the result to a web page, the temporary file (googMot.html) can be found in the directory you specify on the form as being trusted to Flash (and for which you have previously followed the instructions on Flash trusted locations above)


How does it work

You will find a number of classes in the workbook that you should just be able to use as is in whatever application you plan to develop, and which I'll go into later. For the example, the main module is pretty straightforward, and yours should be too.

Public Sub showChart()
    Dim GoogMot As cGoogleChartInput, rHeadings As Range
    'create the chart
    Set GoogMot = New cGoogleChartInput
    If Len(fGoogleChart.RefEdit1.Text) > 0 Then
        Set rHeadings = Range(fGoogleChart.RefEdit1.Text)
        With fGoogleChart.WebBrowser1
            If GoogMot.init(fGoogleChart.tbTrusted.Value & "googmot.html", rHeadings, , _
                        .Width, .Height) Then
                GoogMot.createmotionFile
            ' show the chart

                .Navigate GoogMot.htmlName
                Do
                    DoEvents
                Loop Until .ReadyState = READYSTATE_COMPLETE
            End If
        End With
    Else
        MsgBox ("Please supply a range where the column titles are")
    
    End If
End Sub

This is called from the form as follows on the appropriate command button click. 

Private Sub cbShowChart_Click()
    showChart
End Sub

Whats happening here is
  • Instantiation of a cGoogleChartInput custom class
  • Identification of the spreadsheet range that contains the column titles (all data up till the first blank line will be plotted)
  • Initialization of the cGoogleChartinput class with the appropriate parameters
  • Creation of the html file with the .createmotionFile method
  • Navigation to the newly created html file using the embedded web-browser object.

The provided classes

There are a number of classes provided, one of which is the topic of this article namely cGoogleChartInput. This makes use of a set of other custom classes, cCell, CDataColumn, cDataRow, cDataSet, cDataSets and cHeadingRow. These are a suite of classes that allow the separation of physical spreadsheet location and data manipulation to be separated and encapsulated, thus simplifying the coding for this and other processes. They are not the subject of this article but you can read about them here.

cGoogleChartInput

This is the class that does all the work. For future articles I will be extending it to deal with other google visualization capabilities, but in this example only the motion chart has been implemented. All the code for this in the example workbook. We will just look at selected methods.

Lets take a look at the .init method of this class. You will notice an optional argument headOrderArray. This is to allow you to vary the order of the columns as supplied by the range rWhere. If you recall, Google has rules about the first 3 columns, so if your data doesn't happen to be in the right order, this allows you to modify the order, or omit columns, without re-arranging your spreadsheet.  A typical argument might be Array("Function","Load Date","Volume"). If it is omitted then all the columns in the range will be used in the natural order.

Another item of interest here is that we are reading in the data here, as well as setting up the parameters, using the .getdata method.

Public Function init(fn As String, rWhere As Range, Optional headOrderArray As Variant = Empty, _
                        Optional gwidth As Long = cWidth, Optional gheight = cHeight) As Boolean
    Dim hcell As cCell
    Dim nHeads As Long, s As String
    Set pWhere = rWhere
    pWidth = gwidth
    pHeight = gheight
    phtmlName = fn
    init = False
    nHeads = 0
    s = ""
    If getData Then
        init = True
        ' we got the data ok
        If IsEmpty(headOrderArray) Then
            ' all columns are required
            Set pHeadOrder = pdSet.Headings
        Else
        ' a subset or reordering is required
            Set pHeadOrder = New Collection
            For nHeads = LBound(headOrderArray) To UBound(headOrderArray)
                Set hcell = pdSet.HeadingRow.Exists(CStr(headOrderArray(nHeads)))
                If Not hcell Is Nothing Then
                    pHeadOrder.Add hcell, pdSet.HeadingRow.makekey(hcell.Value)
                Else
                    s = s & headOrderArray(nHeads) & ","
                End If
            Next nHeads
            If Len(s) > 0 Then
                MsgBox "These fields do not exist " & s
                init = False
            End If
        End If

    End If
End Function

The getdata method uses the data manipluation classes mentioned earlier, so collecting the data to be transformed is a simple matter of instantiation of a cDataSet class, followed by a call to its .populateData method.

Private Function getData() As Boolean
    Set pdSet = New cDataSet
    ' just need to provide the range where data headings are
    With pdSet
        .populateData pWhere
        If .Where Is Nothing Then
            MsgBox ("No data to process")
            getData = False
        Else
            getData = True
        End If
    End With
End Function

The only other Public method, aside from .init is .createmotionFile, which actually generates the html and javasscript required to execute the google chart. Again you will see the use of the cDataSet, cCell and cDataRow class to traverse the data previously read by .populateData.

Public Sub createmotionFile()
    Dim s As String, hcell As cCell, dcell As cCell, nr As Long, nc As Long, dr As cDataRow
    ' generate html file as input to google chart
    If createHtmlFile Then
        Print #phtmlHandle, htmlPreamble & googleScriptPreamble
        Print #phtmlHandle, motionScriptStart
        ' column headings
        For Each hcell In pHeadOrder
            Print #phtmlHandle, _
                "data.addColumn('" & getColTextforType(hcell) & "', '" & hcell.toString & "');"
        Next hcell
        ' row values
        Print #phtmlHandle, _
            "data.addRows(" & pdSet.Rows.Count & ");"
        nr = 0
        For Each dr In pdSet.Rows
            nc = 0
            For Each hcell In pHeadOrder
                Set dcell = dr.Cell(hcell.Column)
                Print #phtmlHandle, _
                    "data.setValue(" & CStr(nr) & ", " & CStr(nc) & ", " & getTextforType(dcell) & ");"
                nc = nc + 1
            Next hcell
            nr = nr + 1
        Next dr
        Print #phtmlHandle, motionScriptFinish & googleScriptWrapup & motionscriptBody
        Close #phtmlHandle
    End If
End Sub

The only public property available is htmlName, which returns the name of the file that has been generated by .createmotionFile.

Public Property Get htmlName() As String
    htmlName = phtmlName
End Property

Summary and next steps

This kind of technique opens the growing world of google charts and visualizations to excel users who do not want to use googledocs. Watch this space for future articles that serialize Excel data so it can be used as input to embedded google gadgets, as well as further implementations of additional google visualization. You can download the workbook associated with this code and other useful items here. All code is freely available for non commercial use. Please feel free to contact me with suggestions, updates, comments, bug fixes or code enhancements.