Tuesday, January 12, 2010

GXT: How to save a chart

GXT allows you to create some nice looking charts. However once you want to export them in a PDF or save as image, you are on your own.
GXT charts are based on the OFCGWT project.
Here is one way to do it.
  • Create chart.
String url = "open-flash-chart.swf";
final Chart chart = new Chart(url);
//Load and display chart. See GXT examples.
  • Store the flashID
id=chart.getSwfId()
  • get the image data
chartData = getImageData(id) usng JNI call to javascript method.

private native String getImageData(String id) /*-{ var swf = $doc.getElementById(id); var data = swf.get_img_binary(); return data; }-*/;

  • Create a remote service proxy to decode the 64 bytes encoded data.
final DataSourceServiceAsync dataSourceService = DataSourceService.Locator.getInstance();
dataSourceService.getImageToken(chartData, new SecuredAsyncCallback() {

@Override
public void onSuccess(String result) {
createImageDialog(GWT.getHostPageBaseURL()+"image?var=img_" + result);
}

@Override
protected void onOtherException(Throwable exception) {
MessageBox.alert("Alert", "Remote Procedure Call Failure ?" + exception.getMessage(), null);
}

@Override
protected void onSecurityException(ApplicationSecurityException exception) {
MessageBox.alert("Alert", "Security exception catched.\n" + exception.getMessage(), null);
}
});

  • The server side looks like this:
public String getImageToken(String base64image) {
try {
imageBytes = Base64.decode(base64image);
} catch (Base64DecoderException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String token = getMd5(base64image);
System.out.println("Token: " +token);
if (token == null) token = Math.round(Math.random()) + "";
// getThreadLocalRequest().getSession().setAttribute("img_" + token, imageBytes);
HttpServletRequest request = ServletUtils.getRequest();
HttpSession session = request.getSession();
session.setAttribute("img_" + token, imageBytes);
return token;
}

private String getMd5(String base64image) {
String token = null;
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(base64image.getBytes());
BigInteger hash = new BigInteger(1, md5.digest());
token = hash.toString(16);
}
catch (NoSuchAlgorithmException nsae) {}
return token;
}
public String[] getTokenList() {
return tokenList;
}
Note the use of ServletUtils.getRequest() instead of getThreadLocalRequest() to retrieve the httpsession, since I am using GWT-SL (GWT with Spring).

I also use com.google.appengine.repackaged.com.google.common.util.Base64;
that is supposed to work on Google Application Engine.
  • One more thing to setup on the server side web.xml

add the following to web.xml
<servlet>
<servlet-name>ImageByteReader</servlet-name>
<servlet-class>com.tr.gxt.aoi.server.IO.ImageByteReader</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ImageByteReader</servlet-name>
<url-pattern>/image</url-pattern>
</servlet-mapping>

create the following class to format the response as a png image:
public class ImageByteReader extends HttpServlet {

private static final long serialVersionUID = -8873813591069236947L;

protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException {
try {
System.out.println("ImageByteReader");
String variable = request.getParameter("var");
HttpSession session = request.getSession();
byte[] imageBytes = (byte[]) session.getAttribute(variable);
response.reset();
int contentLength = imageBytes.length;
response.setContentLength(contentLength);
response.setContentType("application");
response.setHeader("Content-Disposition", "attachment; filename=chart.png");
BufferedOutputStream output = new BufferedOutputStream(response.getOutputStream());
output.write(imageBytes);
output.flush();
output.close();
}
catch (Exception e) {}
}
}
  • Once you get a successful rcp call you call the following fct to display the chart in a dialog box as an image!:
private void createImageDialog(String imgurl) {
final DialogBox imageDb = new DialogBox();
imageDb.setText("Image Capture of Chart");

VerticalPanel dbContents = new VerticalPanel();
dbContents.setSpacing(4);
imageDb.setWidget(dbContents);

Image chartImg = new Image(imgurl);
chartImg.setSize("250", "200");
dbContents.add(chartImg);
Button closeButton = new Button("Close", new SelectionListener() {
@Override
public void componentSelected(ButtonEvent ce) {
imageDb.hide();
}
});
dbContents.add(closeButton);
dbContents.setHorizontalAlign(HorizontalAlignment.RIGHT);

imageDb.center();
imageDb.show();
}


The /image suffix will trigger the ImageByteReader class to convert the httpresponse.
Image chartImg = new Image(imgurl); will do the rest...


No comments:

Post a Comment