Google Chart API Fun with Cocoa and F-Script

Revised August 2009

Articles in this series

     Fundamentals
  1. Exploring Cocoa with F-Script
  2. Learn F-Script in 20 Minutes and Have Fun Playing with Core Image
  3. Scripting Cocoa with F-Script
  4. Creating Cocoa Classes with F-Script
  5. System-wide Scripting with F-Script
  6. Embedding F-Script into Cocoa Applications
  7. Accessing Mac OS X Frameworks with F-Script
     Miscellaneous
  1. Google Chart API Fun with Cocoa and F-Script
  2. The Game of Reverse in F-Script

Introduction

The Web is abuzz with a new Google service: a Web API for creating charts.

The Google Chart API returns a PNG-format image in response to a URL. Several types of image can be generated: line, bar, and pie charts for example. For each image type you can specify attributes such as size, colors, and labels.

Accessing it with F-Script is easy, as we will interactively experiment. In the process we will learn how to call the Google Chart API from Cocoa, how to pass data stored in Cocoa arrays, how to apply special effects using Core Image, and a few other cool things...

Charting "Hello World"

First, let's start with a very simple example, the "Hello World" chart, which is described in the Google Chart API documentation:

To see the Chart API in action, open up a browser window and copy the following URL into it: http://chart.apis.google.com/chart?cht=p3&chd=s:hW&chs=250x100&chl=Hello|World

Press the Enter or Return key and - presto! - you should see the following image:

The URL contains all the data needed to describe the chart. We'll examine that further, but, for now, let's see how we can generate an NSImage for this chart in Cocoa, using F-Script.

Cocoa provides us with the NSURL class to manipulate URLs. We'll use it to retrieve our chart from the Google Chart API and generates an NSImage object. We start by defining an NSString that contains our URL:

chartString := 'http://chart.apis.google.com/chart?cht=p3&chd=s:hW&chs=250x100&chl=Hello|World'.

There is a little caveat: for creating our NSURL object, we need to provide the NSURL class with a percent-escaped URL string. Such a string is easy to generate using the stringByAddingPercentEscapesUsingEncoding: method:

escapedChartString := chartString stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding.

All we have to do now is to instantiate an NSURL object and to create our NSImage using the initWithContentsOfURL: method:

url := NSURL URLWithString:escapedChartString.

image := NSImage alloc initWithContentsOfURL:url.

That's it. The initWithContentsOfURL: method takes care of issuing the HTTP request and to initialize our newly allocated NSImage instance with the result. To look at our image, we can send it the inspect message. It opens an inspector that displays the image in a window:

image inspect

Or we can directly use a Cocoa drawing method. For example:

image compositeToPoint:20<>20 operation:NSCompositeSourceOver

Of course, we can directly copy and paste all the code shown here in the F-Script shell, in order to interactively experiment with it. Here is what our F-Script session may look like:

As with any Cocoa object, we can further explore our NSImage object with the object browser:

sys browse:image

Charting our Disk Space

Generating the "Hello World" pie chart is fun (that's how you know you are a geek) but this would be better with actual meaningful values. For instance, let's try to generate a pie chart that shows the proportion of free and used space of our startup disk. The first step is to get at those values. To do that we can use the Mac OS X Scripting Bridge, which allows manipulating other applications as if they were Cocoa objects (see System-wide Scripting with F-Script for more on this topic).

Finder := SBApplication applicationWithBundleIdentifier:'com.apple.finder'.

capacity  := Finder startupDisk capacity.

freeSpace := Finder startupDisk freeSpace.

Now we need to compute the proportions of free space and used space:

percentFree := freeSpace * 100 / capacity.

percentUsed := 100 - percentFree.

We can then call the Google Chart API. As you can see below, we construct the URL using F-Script's string concatenation operator (i.e., ++) to specify values in the URL. As described in the Google Chart API documentation, the data points must be provided with the chd parameter. Also, we are using the chco parameter to specify other colors for the chart, and the chf to specify a background color. We have also modified the chs parameter to make the chart bigger, and added a title using the chtt parameter.

chartString := 'http://chart.apis.google.com/chart?cht=p3&chs=400x200&chco=00AA00&chf=bg,s,F5F5F5&chl=free|used&chtt=Startup disk space&chd=t:'
               ++ percentFree printString ++ ',' ++ percentUsed printString.

escapedChartString := chartString stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding.

url := NSURL URLWithString:escapedChartString.

image := NSImage alloc initWithContentsOfURL:url.

image inspect.

In our code, the cht parameter is used to specify the chart type. By changing its value from p3 to p, we can get a 2D pie chart:

Adding Special Effects with Core Image

Our chart is nice but what about making it shiner with some special effect? To do that, we are going to use the Core Image framework provide by Mac OS X. Core Image provides a powerful model for image processing and comes with dozens of built-in effects, in the form of CIFilter objects. To make use of Core Image, we must first create a CIImage object from our chart. CIImage provides various creation methods, including imageWithContentsOfURL: which is well suited in our case (for more on using Core Image filters with F-Script, you can read Learn F-Script in 20 Minutes and Have Fun Playing with Core Image).

ciimage := CIImage imageWithContentsOfURL:url.

Then we create two Core Image filters. The first one, a CIStarShineGenerator, is used to generate a "star shine" image that we compose over our chart image using the second one, a CISourceOverCompositing.

starShineGenerator := CIFilter filterWithName:'CIStarShineGenerator'.
starShineGenerator setDefaults.
starShineGenerator setValue:4 forKey:'inputRadius'.
starShineGenerator setValue:(CIVector vectorWithX:167 Y:24) forKey:'inputCenter'.

starShineImage := starShineGenerator valueForKey:'outputImage'.

sourceOverCompositing := CIFilter filterWithName:'CISourceOverCompositing'.
sourceOverCompositing setValue:starShineImage forKey:'inputImage'.
sourceOverCompositing setValue:ciimage        forKey:'inputBackgroundImage'.

finalImage := sourceOverCompositing valueForKey:'outputImage'.

We can then look at the result by drawing finalImage (F-Script does not yet provide an inspect method for CIImage objects):

finalImage drawAtPoint:150<>150 fromRect:ciimage extent operation:NSCompositeSourceOver fraction:1.

Charting Cocoa Arrays

We often want to chart tabular data. For instance, supose that, using F-Script, you recently created an awesome application and now want to look at the evolution of your sales over the last few months.

We will first create NSArray objects that contain our sales data. This will serve to illustrate a common need: charting data stored in arrays.

months := {'May', 'June', 'July', 'August', 'September', 'October', 'November'}.

sales := {2400, 6800, 6205, 9365, 15000, 24206, 41034}.

As you can see, there were 2400 licenses sold in May, 6800 in June and so on...

The Google Chart API expects data points to be expressed on a scale that goes from 0 to 100. Therefore, we are going to scale the data from our sales array (our month array is only used to provide labels on the x axis). To do that, we first find the biggest number in our array (it will also be used to specify the range associated to the y axis), using an operation called reduction that is carried on by the \ method. This method takes one argument, a block, and cumulatively evaluates it on the elements of the receiver. You can learn more about reduction and other array programming operations in the F-Script Guide (pdf file).

salesMax := sales \ #max:.

Then we project our data set on a scale from 0 to 100:

salesScaled := sales * 100 / salesMax.

We can now prepare the strings that we will put in our Google Chart URL. Data points must be separated by comas, and labels must be separated by pipes:

salesString := salesScaled componentsJoinedByString:','.

monthsString := months componentsJoinedByString:'|'.

componentsJoinedByString: is a standard Cocoa method provided by NSArray which constructs and returns an NSString object that is the result of interposing a given separator between the elements of the receiver’s array. Alternatively, we could have used reduction to construct our strings:

salesString := salesScaled \ [:a :b| a description ++ ',' ++ b description].

monthsString := months \ [:a :b| a ++ '|' ++ b].

Finally, we can construct our ULR, call the Chart API and inspect the resulting image:

chartString := 'http://chart.apis.google.com/chart?cht=bvs&chs=470x320&chf=bg,s,F0F0F0&chtt=Sales&chbh=30,30&chg=0,25,1,0&chco=4E6B9A&chxt=x,y&chxl=0:|' 
                ++ monthsString ++ '&chxr=1,0,' ++ salesMax description ++'&chd=t:' ++ salesString.

escapedChartString := chartString stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding.

image := NSImage alloc initWithContentsOfURL:(NSURL URLWithString:escapedChartString).

image inspect.

Modifying the various parameters in the URL (which are all documented by Google in the developer's guide), we change the appearance of the chart. For example, setting the cht (chart type) parameter to lc produces a line chart:

Finally, saving the image on disk is easy using standard Cocoa methods. For instance the following code saves our image in our Desktop folder using the TIFF format:

image TIFFRepresentation writeToFile:'~/Desktop/sales.tiff' stringByExpandingTildeInPath atomically:YES

Happy charting!


Copyright © 2009 Philippe Mougin