smartGWT and FusionCharts

In the previous post we added support for login security to Serendipity. In this post, we're going to add support for interactive charting to Serendipity.

In this post, we'll:

  1. Identify some options
  2. Install FusionCharts
  3. Implement support for FusionCharts
  4. Test the application in development mode

Note: You can download the sample application from the CRMdipity (Se-r-en-dipity) project's download page.

1. Identify some options

There are several products that we could leverage in order to add support for interactive charting to Serendipity. For example:

  • Axiis: A data visualisation framework.
  • flot: A pure Javascript plotting library for jQuery.
  • amCharts: A set of Flash and JavaScript charts.
  • JFreeChart: A Java charting library.

You might also like to take a look at jgraph, yFiles and Tom Sawyer Software. However, in this post we're going to look at FusionCharts.

2. Install FusionCharts

Download and then unzip FusionCharts. I downloaded:

  • FusionChartsFree.zip - Version 2.2 of FusionCharts Free

Copy the .swf files from the FusionChartsFree/Charts directory into the project's war/charts/fusioncharts/flash directory. Copy the .xml files from the FusionChartsFree/Gallery/Data directory into the project's war/charts/fusioncharts/data directory and copy the FusionCharts.js file from the FusionChartsFree/JSClass directory into the project's war/charts/fusioncharts/javascript directory.

3. Implement support for FusionCharts

While including JavaScript in your Java code is very easy with JSNI, sometimes you just want to include a JavaScript file. The easiest way to do this is to use a <script> tag in your module's gwt.xml file. While you can link to scripts directly from host pages, doing so can be problematic — modules cannot be easily distributed and reused if they depend on an external JavaScript file. 

Serendipity's updated module definition file:

...
  
  <!-- Other module inherits -->
  <inherits name="com.gwtplatform.mvp.Mvp" />
  <inherits name="com.gwtplatform.dispatch.Dispatch" />
  
  <!-- begin FusionCharts include files -->
  <inherits name="com.smartgwt.SmartGwtPluginBridges" />
  <script src="../charts/fusioncharts/javascript/FusionCharts.js"></script>
  <!-- end FusionCharts include files -->  

  <inherits name="com.smartgwt.SmartGwtNoTheme" />
  <inherits name="com.smartclient.theme.enterpriseblue.EnterpriseBlue" />
  <inherits name="com.smartclient.theme.enterpriseblue.EnterpriseBlueResources" />
  
...

As well as including the FusionCharts.js file we also need to inherit smartGWT's PluginBridges module. Now we can implement a simple wrapper class by taking advantage of smartGWT's Flashlet widget.

The FusionChart class:

import java.util.HashMap;

import com.smartgwt.client.widgets.plugins.Flashlet;

public class FusionChart extends Flashlet {
  
  private static int count = 0;
  
  private String swfId;

  public FusionChart(String src, String width, String height, String dataUrl) {
    super();
    
    setCodeBase("http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0");
    setClassID("clsid:d27cdb6e-ae6d-11cf-96b8-444553540000");
    setPluginsPage("http://www.macromedia.com/go/getflashplayer");    
    
    swfId = "fusionChartId_" + count;
    ++count;
    
    setID(swfId);
    setName(swfId);
    setSrc("charts/fusioncharts/flash/" + src);
    setSize(width, height);
    
    HashMap<String, String> hashMap = new HashMap<String, String>();
    
    hashMap.put("id", swfId);
    hashMap.put("flashvars", "&id=" + swfId + "&chartWidth=" + width + "&chartHeight=" + height + 
        "&registerWithJS=1" + "&debugMode=0" + "&dataURL=" + "charts/fusioncharts/data/" + dataUrl);
    
    // hashMap.put("allowscriptaccess", "always");
    // hashMap.put("bgcolor", "#ffffff");
	// hashMap.put("quality", "high");
    
    // If you embed the chart into your web page, and the page has layers such as drop-down menus or 
    // drop-down forms, and you want them appear above the chart, you need to add the following  line 
    // to your code. 
    // hashMap.put("wmode", "opaque" ); 
    
    setParams(hashMap);
    
    // setCanSelectText(true);
  }
}

The Dashboards View:

...

public class DashboardsView extends ViewWithUiHandlers<DashboardsUiHandlers> implements
  DashboardsPresenter.MyView {

  private static final String CONTEXT_AREA_WIDTH = "*";
 
  private VLayout panel;

  public DashboardsView() {
    super();

    panel = new VLayout();
    panel.setStyleName("crm-ContextArea");
    panel.setWidth(CONTEXT_AREA_WIDTH);
    
    panel.setOverflow(Overflow.AUTO);
    
    drawFusionCharts(panel);
  }

  private void drawFusionCharts(VLayout panel) {
    
    FusionChart chart1 = new FusionChart("FCF_StackedColumn2D.swf", "400", "350", "StCol2D.xml"); 
       
    FusionChart chart2 = new FusionChart("FCF_StackedBar2D.swf", "400", "350", "StBar2D.xml");  
	
    FusionChart chart3 = new FusionChart("FCF_Doughnut2D.swf", "400", "350", "Doughnut2D.xml"); 
    
    FusionChart chart4 = new FusionChart("FCF_Funnel.swf", "350", "300", "Funnel.xml"); 
    
    HLayout northLayout = new HLayout();
    northLayout.setHeight("50%");
    northLayout.setBackgroundColor("#FFFFFF");
    
    northLayout.addMember(chart1);
    northLayout.addMember(chart2);    
    
    HLayout southLayout = new HLayout();
    southLayout.setHeight("50%");    
    southLayout.setBackgroundColor("#FFFFFF");

    southLayout.addMember(chart3);
    southLayout.addMember(chart4);
    
    panel.addMember(northLayout);
    panel.addMember(southLayout);
  }

  @Override
  public Widget asWidget() {
    return panel;
  } 

  ...

}

Take a look at the Dashboards View's constructor and you will notice that it calls the nested layout containers (the panels) setOverflow method. This ensures that when the browser window is resized, scroll bars will be drawn when they are needed.

The Dashboards Presenter:

...

public class DashboardsPresenter extends
    Presenter<DashboardsPresenter.MyView, DashboardsPresenter.MyProxy> implements
    DashboardsUiHandlers {

  // don't forget to update SerendipityGinjector & ClientModule
  @ProxyCodeSplit
  @NameToken(NameTokens.dashboards)
  @UseGatekeeper(LoggedInGatekeeper.class)
  public interface MyProxy extends Proxy<DashboardsPresenter>, Place {
  }

  public interface MyView extends View, HasUiHandlers<DashboardsUiHandlers> {
    void setResultSet();
  }

  @Inject
  public DashboardsPresenter(EventBus eventBus, MyView view, MyProxy proxy) {
    super(eventBus, view, proxy);

    getView().setUiHandlers(this);
  }

  @Override
  protected void onReveal() {
    super.onReveal();

    MainPagePresenter.getNavigationPaneHeader().setContextAreaHeaderLabelContents(
        Serendipity.getConstants().dashboardsMenuItemName());
    MainPagePresenter.getNavigationPane().selectRecord(NameTokens.dashboards);
  }

  @Override
  protected void revealInParent() {
    RevealContentEvent.fire(this, MainPagePresenter.TYPE_SetContextAreaContent, this);
  }
}

4. Test the application in development mode

At this point, you should be able to compile Serendipity and launch it from within Eclipse.

The nested Dashboards presenter and view:

 

The nested Dashboards presenter and view:

 

Note: You can download the sample application from the CRMdipity (Se-r-en-dipity) project's download page.