Munish Gogna has been a software developer since 2006. His languages and technologies include: Java, J2EE, SQL, Spring, Hibernate as well as basic shell scripting (Unix flavors). He is currently working at a swiss private bank in Singapore. Munish has posted 6 posts at DZone. View Full User Profile

Custom Facelets Chart Component Using Open Source Flash

12.13.2010
| 5522 views |
  • submit to reddit
You must have heard the old maxim that there are lies, damned lies, statistics and Excel charts - well, anyone who's ever tried to do any kind of serious reporting of statistics on the web knows that you need to be able to produce good graphs. For years there have been libraries such as JFreeChart that do a reasonable job of graph-drawing in static image way, in recent years arrival of Google Analytics (google finance's price history chart e.g.) raised the quality bar substantially. Suddenly everyone including your line manager wanted fancy line graphs that show the numbers represented by each point when you roll the mouse along the line (I am not going to use google charts as it requires internet connection to draw charts. If your application is installed in some secured environment where the outside connectivity is not allowed than you are in big trouble).

After googling for some time I came across Open Flash Chart Project (http://teethgrinder.co.uk/open-flash-chart). Open Flash Chart lets everyone have interactive Flash charts on their website. So today I am going to discuss how we can use open source flash to create stunning flash charts in Java. I am going to develop a JSF facelet component that can be easily plugged into any JSF application. The best part about the Open Flash chart is that it consumes JSON data, so the component that I am going to develop will be able to handle any type of chart (pie, bar, line etc.), we just need to feed chart data in JSON format and rest will be taken care by the component.

Custom Chart Component
A custom JSF component is represented by a Java Class that extends from UIComponentBase. An instance of this class is created whenever a new page is rendered that contains the component. I am not going into details of how we create the custom components. Let's move straight to the Component definition.
package gognamunish.jofc.poc;

import java.io.IOException;

import javax.el.ValueExpression;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

/**
* Custom chart component for Open Source Flash
*
* @author Munish Gogna
*
*/
public class JOFCChartUIComponent extends UIComponentBase {

private static final String COMPONENT_FAMILY = "JOFCChart";

private String width = "400"; // default width
private String height = "400"; // default height
private String jsonSource;

@Override
public void encodeBegin(FacesContext context) throws IOException {
super.encodeBegin(context);

final ResponseWriter writer = context.getResponseWriter();

writer.startElement("script type=\"text/javascript\" src=\"../jofc/js/swfobject.js\"",this);
writer.endElement("script");
writer.startElement("script type=\"text/javascript\" src=\"../jofc/js/json2.js\"",this);
writer.endElement("script");
writer.startElement("script type=\"text/javascript\"", this);
writer.write("swfobject.embedSWF (");
writer.write("\"../jofc/open-flash-chart.swf\", \"jofc_chart\",");
writer.write("\"" + getWidth() + " \", \"" + getHeight() + " \", \"9.0.0\", \"expressInstall.swf\", ");
writer.write("{\"get-data\":\"ofc_get_data\"} );");
writer.write("function ofc_get_data() { return JSON.stringify( " +getJSONSource() + ");}");
writer.endElement("script");
}

@Override
public void encodeEnd(FacesContext context) throws IOException {
super.encodeEnd(context);
}

@Override
public Object saveState(FacesContext facesContext) {
Object values[] = new Object[4];
values[0] = super.saveState(facesContext);
values[1] = this.getAttributes().get(JOFCChart.WIDTH);
values[2] = this.getAttributes().get(JOFCChart.HEIGHT);
values[3] = this.getAttributes().get(JOFCChart.JSON_SOURCE);
return values;

}

@Override
public void restoreState(FacesContext facesContext, Object state) {
Object values[] = (Object[]) state;
super.restoreState(facesContext, values[0]);
this.getAttributes().put(JOFCChart.WIDTH, values[1]);
this.getAttributes().put(JOFCChart.HEIGHT, values[2]);
this.getAttributes().put(JOFCChart.JSON_SOURCE, values[3]);
}

@Override
public String getFamily() {
return COMPONENT_FAMILY;
}

public String getWidth() {
ValueExpression ve = getValueExpression(JOFCChart.WIDTH);
if (ve != null) {
return (String) ve.getValue(getFacesContext().getELContext());
}
return width;
}

public String getJSONSource() {
ValueExpression ve = getValueExpression(JOFCChart.JSON_SOURCE);
if (ve != null) {
return (String) ve.getValue(getFacesContext().getELContext());
}
return jsonSource;
}


public String getHeight() {
ValueExpression ve = getValueExpression(JOFCChart.HEIGHT);
if (ve != null) {
return (String) ve.getValue(getFacesContext().getELContext());
}
return height;
}


public void setWidth(String width) {
this.width = width;
}

public void setHeight(String height) {
this.height = height;
}

public void setJsonSource(String jsonSource) {
this.jsonSource = jsonSource;
}
}
Note: Please note that we have not defined any separate renderer for our component, the component renders html on its own.

Let's define the descriptor for our custom facelet component - jofc_taglib.xml
<facelet-taglib>
<namespace> http://gognamunish.jofc.poc </namespace>
<tag>
<tag-name>chart</tag-name>
<component>
<component-type>JOFCChart</component-type>
</component>
</tag>
</facelet-taglib>
JSF Facelets components need to be registered in a both web.xml and faces-config.xml files.

web.xml entry:
<context-param>
<param-name>facelets.LIBRARIES</param-name>
<param-value>/WEB-INF/jofc_taglib.xml</param-value>
</context-param>
faces-config entry:
<component>
<component-type>JOFCChart</component-type>
<component-class>gognamunish.jofc.poc.JOFCChartUIComponent</component-class>
</component>
JSON data generation
As I told earlier that Open Flash works with JSON data which means we will have to feed our custom chart component with some JSON data. We have two options here, either we create JSON data ourselves or use some library. I chose second option i.e. http://code.google.com/p/jofc2/ Java API. This library is not 100% stable and has got few bugs (read between the lines in the next java code).

Let's try to plot sample admissions data for the month of October 2010 using line chart. This example demonstrates several of the things that you can do with Line Charts.
package gognamunish.jofc.data;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import jofc2.OFC;
import jofc2.model.Chart;
import jofc2.model.Text;
import jofc2.model.axis.Label;
import jofc2.model.axis.XAxis;
import jofc2.model.axis.XAxisLabels;
import jofc2.model.axis.YAxis;
import jofc2.model.axis.Label.Rotation;
import jofc2.model.elements.LineChart;
import jofc2.model.elements.LineChart.Dot;

/**
* Source class for our sample Admission chart
*
* @author Munish Gogna
*
*/
public class DataSource {

private List admissionDataList;

/**
* Returns the JSON data which defines the elements of the OFC2 charts.
*/
public String getJOFCChartData() {
Chart chart = new Chart();
chart.setTitle(new Text("Admission Data for October 2010"));
XAxis xAxis = new XAxis();
XAxisLabels xAxisLabels = new XAxisLabels();
xAxisLabels.addLabels(getXLabels());
xAxis.setXAxisLabels(xAxisLabels);
chart.setXAxis(xAxis);

YAxis yAxis = new YAxis();
yAxis.setMax(getAdmissionList().size() + 1);
chart.setYAxis(yAxis);

LineChart lineChart = new LineChart();
lineChart.setText("copyright: Munish Gogna 2010");
lineChart.addDots(getValues());
lineChart.setFontSize(10);

chart.addElements(lineChart);

// JOFC API BUG - The enum Rotation.VERTICAL should have value 90 as opposed to -90
return OFC.getInstance().render(chart).replace("-90", "90");
}

private List getXLabels() {
List xLabelsList = new ArrayList();
for (AdmissionData cd : getAdmissionList()) {
Label label = new Label();
label.setText(cd.month);
// for displaying the X labels vertically
label.setRotation(Rotation.VERTICAL);
xLabelsList.add(label);
}
return xLabelsList;
}

private List getValues() {
List valuesList = new ArrayList();
Dot dot;
for (AdmissionData cd : getAdmissionList()) {
dot = new Dot(cd.noOfStudents);
dot.setTooltip("#val# on #x_label#");
dot.setDotSize(2);
dot.setHaloSize(3);
dot.setColour("#FF0000");
valuesList.add(dot);
}
return valuesList;
}

private List getAdmissionList() {
if (admissionDataList == null) {
admissionDataList = new ArrayList();
Random random = new Random();
AdmissionData data;
for (int i = 1; i <= 30; i++) {
data = new AdmissionData("October" + i, random.nextInt(30));
admissionDataList.add(data);
}
}
return admissionDataList;
}

/** helper class for plotting admission related data */
static class AdmissionData {

public String month;
public int noOfStudents;

AdmissionData(String month, int noOfStudents) {
this.month = month;
this.noOfStudents = noOfStudents;
}
}
}

In absence of JOFC2 Java API library we would have to provide following json string to our chart component for the line chart we are looking for. Don't be scared JSON is not meant to be consumed by human :)
{"is_thousand_separator_disabled":0,"is_decimal_separator_comma":0,"title":{"text":"Admission Data for October 2010"},"y_axis":{"max":31},"x_axis":

{"labels":{"labels":[{"text":"October1","rotate":"90"},{"text":"October2","rotate":"90"},{"text":"October3","rotate":"90"},

{"text":"October4","rotate":"90"},{"text":"October5","rotate":"90"},{"text":"October6","rotate":"90"},{"text":"October7","rotate":"90"},

{"text":"October8","rotate":"90"},{"text":"October9","rotate":"90"},{"text":"October10","rotate":"90"},{"text":"October11","rotate":"90"},

{"text":"October12","rotate":"90"},{"text":"October13","rotate":"90"},{"text":"October14","rotate":"90"},{"text":"October15","rotate":"90"},

{"text":"October16","rotate":"90"},{"text":"October17","rotate":"90"},{"text":"October18","rotate":"90"},{"text":"October19","rotate":"90"},

{"text":"October20","rotate":"90"},{"text":"October21","rotate":"90"},{"text":"October22","rotate":"90"},{"text":"October23","rotate":"90"},

{"text":"October24","rotate":"90"},{"text":"October25","rotate":"90"},{"text":"October26","rotate":"90"},{"text":"October27","rotate":"90"},

{"text":"October28","rotate":"90"},{"text":"October29","rotate":"90"},

{"text":"October30","rotate":"90"}]}},"num_decimals":2,"is_fixed_num_decimals_forced":0,"elements":[{"font-size":10,"text":"copyright: Munish Gogna

2010","type":"line","dot-style":{"halo-size":2,"type":"solid-dot","dot-size":2},"on-show":{"type":""},"values":[{"value":15,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":16,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":14,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":25,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":28,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":6,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":23,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":6,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":20,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":21,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":24,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":3,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":17,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":25,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":13,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":15,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":23,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":1,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":23,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":18,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":20,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":7,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":5,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":8,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":11,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":2,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":11,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":7,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":2,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":7,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"}],"loop":false}]}


So far so good, let's see how our chart rather interactive chart looks like?

Last step - using the component
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:jofc="http://gognamunish.jofc.poc"
xmlns:p="http://primefaces.prime.com.tr/ui">

<f:view contentType="text/html">
<h:body>
<jofc:chart width="500" height="400" jsonSource="#{data.JOFCChartData}" />
<div id="jofc_chart" />
</h:body>
</f:view>
</html>
and here appears the magical flash movie:
admission chart

What you see above is a static image but in reality you will get a flash movie, as we move the mouse along the line the tool tip changes accordingly (at the time of taking this screenshot mouse pointer was on October 24)

This was just a poc , this component can be improved in many ways. I leave the rest to you guys.
Published at DZone with permission of its author, Munish Gogna.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)