Using Google Chart API as a tag

Sooner or later, one of your clients will ask for graphical representation of something in your application. It may be time-based (revenue per day/month/year), or more arbitrary. Instead of checking available imaging libraries like JFreeChart, and wasting your own CPU cycles when creating images, you can rely on the Google Chart API that is available at http://code.google.com/apis/chart/.

This API supports many charts, some of which do not even resemble traditional graphs. We will come to this later in the recipe.

The source code of the example is available at examples/chapter4/mashup-chart-api.

How to do it...

Some random data to draw from might be useful. A customer entity and an order entity are created in the following code snippets:

public class Customer {

  public String name;
  public List<Order> orders = new ArrayList<Order>();
  
  public Customer() {
    name = RandomStringUtils.randomAlphabetic(10);
    for (int i = 0 ; i< 6 ; i++) {
      orders.add(new Order());
    }
  }
}

Creating orders is even simpler, as shown in the following code snippet:

public class Order {
  publicBigDecimal cost = new BigDecimal(RandomUtils.nextInt(50));
}

The index controller in the Application needs to expose a customer on every call, as shown in the following code snippet. This saves us from changing anything in the routes file:

public static void index() {
  Customer customer = new Customer();
  render(customer);
}

Now the index.html template must be changed, as shown in the following code snippet:

#{extends 'main.html' /}
#{settitle:'Home' /}

<h2>QrCode for customer ${customer.name}</h2>

#{qrcode customer.name, size:150 /}

<h2>Current lead check</h2>

#{metertitle:'Conversion sales rate', value:70 /}

<h2>Some random graphic</h2>

#{linecharttitle:'Some data',labelX: 1..10, labelY:1..4, data:[2, 4, 6, 6, 8, 2, 2.5, 5.55, 10, 1] /}

<h2>Some sales graphic</h2>

#{chart.lctitle:'Sales of customer ' + customer.name,labelY:[0,10,20,30,40,50], labelX:['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],data:customer.orders.subList(0, 6),field:'cost' /}

As you can see here, four new tags are used. The first two are pretty simple and represent usual tags. The views/tags/qrcode.html file looks similar to the following code snippet:

%{
  size = _size?:200
}%
<img src="http://chart.apis.google.com/chart?cht=qr&chl=${_ arg}&chs=${size
  }x${size}">

The views/tags/meter.html file writes the image tag out a little different:

%{
  width = _width?:300
  height = _height?:150
  title = _title?:"No title"

  encodedData = googlechart.DataEncoder.encode([_value], 100)

  out.print('<imgsrc="http://chart.apis.google.com/chart?cht=gm')
  out.print('&chs=' + width + 'x' + height)
  out.print('&chd=e:' + encodedData)
  out.println('&chtt='+title+'">')
%}

The linechart tag allows us to draw arbitrary data handed over into arrays. The file must be placed at views/tags/linechart.html and needs to look like the following code snippet:

%{
  width = _width?:300
  height = _height?:225
  title = _title?:"No title"
  colors = _colors?:"3D7930"

  out.print('<imgsrc="http://chart.apis.google.com/chart?')
  out.print('cht=lc')

  String labelX = _labelX.join("|");
  String labelY = _labelY.join("|");
  out.println("&chxl=0:|"+ labelX + "|1:|" + labelY);

  out.print('&chs=' + width + 'x' + height)
  out.print('&chtt=' + title)
  out.print('&chco=' + colors)
  dataEncoded = googlechart.DataEncoder.encode(_data)
  out.print('&chd=e:' + dataEncoded)

  maxValue = googlechart.DataEncoder.getMax(_data)
  out.print('&chxr=0,0,' + maxValue)
  out.print('&chxt=x,y')
  out.print("&chls=1,6,3");

  out.print('">')

}%

The remaining tag, used as #{chart.lc} in the index template, is a so-called fast tag and uses Java instead of the template language; therefore, it is a simple class that extends from the standard FastTags class, as shown in the following code snippet:

@FastTags.Namespace("chart")
public class ChartTags extends FastTags {

  public static void _lc(Map<?, ?>args, Closure body, PrintWriter out, ExecutableTemplate template, intfromLine) throws Exception {
      out.print("<imgsrc="http://chart.apis.google.com/chart?");
      out.print("cht=lc");

      out.print("&chs=" + get("width", "400", args) + "x" + get("height", "200", args));
      out.print("&chtt=" + get("title", "Standard title", args));
      out.print("&chco=" + get("colors", "3D7930", args));

      String labelX = StringUtils.join((List<String>)args.get("labelX"), "|");
      String labelY = StringUtils.join((List<String>)args.get("labelY"), "|");
      out.println("&chxl=0:|"+ labelX + "|1:|" + labelY);

      List<Object> data = (List<Object>) args.get("data");

      String fieldName = args.get("field").toString();


      List<Number>xValues = new ArrayList<Number>();
      for (Object obj : data) {
        Class clazz = obj.getClass();
        Field field = clazz.getField(fieldName);
        Number currentX = (Number) field.get(obj);
        xValues.add(currentX);
      }

      String dataString = DataEncoder.encode(xValues);

      out.print("&chd=e:" + dataString);
      out.print("&chxs=0,00AA00,14,0.5,l,676767");
      out.print("&chxt=x,y");
      out.print("&chxr=0,0," + DataEncoder.getMax(xValues));
      out.print("&chg=20,25");
      out.print("&chls=1,6,3");
      out.print("">");
    }

    private static String get(String key, String defaultValue, Map<?,?>args) {
      if (args.containsKey(key)) {
        return args.get(key).toString();
    }
    return defaultValue;
  }
}

If you have read the above tags and the last Java class, then you might have seen the usage of the DataEncoder class. The Google chart API needs the supplied data in a special format. The DataEncoder code snippet is as follows:

public class DataEncoder {

  public static String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.";
  public static int length = chars.length();

  public static String encode(List<Number> numbers, intmaxValue) {
    String data = "";
    for (Number number : numbers) {
     double scaledVal = Math.floor(length * length * number.intValue() / maxValue);
      
      if (scaledVal> (length * length ) -1) {
        data += "..";
      } 
      else if (scaledVal< 0) {
      data += "__";
      } 
      else {
        int quotient = (int) Math.floor(scaledVal / length);
        int remainder = (int) scaledVal - (length * quotient);
        data += chars.charAt(quotient) + "" + chars.charAt(remainder);
      }
    }
    Logger.debug("Called with %s and %s => %s", numbers, maxValue, data);
    return data;
  }
  
  public static String encode(List<Number> numbers) {
    return encode(numbers, getMax(numbers));
  }
  
  public static intgetMax(List<Number> numbers) {
    Number max = numbers.get(0);
    for (Number number : numbers.subList(1, numbers.size())) {
      if (number.doubleValue() >max.doubleValue()) {
        max = number;
      }
    }

    return (int) Math.ceil(max.doubleValue());
  }
}

This formatter always produces the extended format, which is a little bit longer, but can represent all the data handed to it.

How it works...

A lot of code has been produced, so what has been done? Instead of writing the Google image code all over again, tags were created to ease usage. No one can remember all those parameters which are needed for this or that type of graph. Also, the template code looks much cleaner, because you actually get to know by reading what kind of graphic is supposed to be seen.

I will not explain here the myths of the Google APIs or how the data encoder is working. It is created from the example JavaScript code on the chart API web pages. The request parameters are extensively explained in the documentation. I will only show the specialties of the tags.

Taking a closer look at the #{qrcode} tag reveals the usage of a default parameter for the size. As long as it is not taken over as argument, as well as setting the title parameter of the graphic.

The #{meter} tag uses a big Groovy scriptlet for executing its logic. Inside the script you can access the request output stream via the out variable. Furthermore, the data encoder is called with its full class path and name as you cannot import classes inside a template.

The #{linechart} tag is pretty long for a single tag. You should think whether it makes more sense to write such a big logic inside the template with Groovy, or to use a fast tag direct instead. Fast tags have the possibility of being unit tested for example. As you can see by the use of the join() method on the labelX and labelY arrays, writing pure Groovy code is not a problem here. When used correctly, this tag allows the developer to input arbitrary data into the variable, as long as it is an array consisting of numbers. So, this is the generic version of a drawing tag.

The#{chart.lc} tag is a more concrete version of the #{linechart} tag, because you use the data of a certain field as the input. The data is a list of arbitrary objects, but additionally you specify the field where data of each object should be extracted from. The first six orders of a customer are used as data input as you can see in the index template. Inside every order, the content of the cost property is used as data. This saves the developer the hassle of always having to provide an array of data to the render() method. This is the big difference to the #{linechart} tag.

As #{chart.lc} is a fast tag, its implementation is in Java. Looking at the class, the @Namespace annotation before the class definition shows where the chart prefix is coming from. This helps you to have same named tags in your application. Every tag you want to implement in Java has to be a method, which must be public, static, return void, and must begin with an underscore. Also, the arguments must match. However, it may throw any exception or none at all. This helps to keep the code free from exceptions in this case, as no error handling is done. If the property you defined to check for does not exist, the whole tag crashes. One should, of course, never do this in a production environment. You should output nothing, but create an error log message. Basically, the tag does the same as the #{linechart} does. It joins the labels for x and y axis as needed, then iterates through the array of objects. For each object, the field is read from the object with the help of the reflection API.

In case you are wondering why the DataEncoder class has the getMax() method exposed, it is needed to keep the graph scaled.

There's more...

Before going on, you should delve deeper into tags by taking a look at the Play framework source code, which shows off some nice examples and tricks to keep in mind.

Getting request data inside a fast tag

It is no problem to get the request or the parameters inside a fast tag. Access the request and all its subsequent data structures via the following code:

Request req = Http.Request().current();

This ensures thread safety by always returning the request of the current thread.

The Google Chart API

The Google Chart API is really powerful and complex. I have barely scratched the surface here. You will see that when you check the documentation at http://code.google.com/apis/chart/docs/chart_params.html. An even better place to look at the Google Chart API is the gallery at http://imagecharteditor.appspot.com, where you can try out different image types in the browser. The API features several charts with magnitudes of options.

Make a graceful and more performant implementation

There is really a lot of room for improvement in this example. First, the tags are nowhere near graceful. Many different errors can occur, like not handing over mandatory parameters, class cast exceptions when converting the data to numbers, or defining a non existing property and then calling the reflection API. As a little practice, you could try to improve this before adding new graph options. If you want to create new charts, then the simplest way is to use the chart wizard provided by Google to find out which parameter changes the behavior.

Considering privacy when transmitting data

By using the Google Chart API you are actually transmitting quite a lot of data out of your system, in clear text. You should be aware that this might pose a privacy problem. Personally, I would not submit sensitive data like my daily revenue through the Internet just to have it graphed. On the other hand, I would not have a problem with the average response times of my server from yesterday. Always think about such facts before creating mashups.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.136.119