Monday, September 16, 2013

Play2 charts

Play2 charts

Play2 data visualizations


Play2's nature of pure asynchronous web framework instead of all-you-ever-need "wundertute" makes it clear that developer is one who decides what and how to visualize - there's no built in default toolset present.

I decided to 
  • Use javascript library to visualize my data
  • Compare 2 datasets to each other using Radar Chart

Javascript libraries


There's plethora of different libs. Open source, free, you name it. One size doesn't fit all, and it's up to your needs what you're about to choose.

You want to go commercial way: Maybe Highchart? See what they offer: http://www.highcharts.com/demo/

You prefer open source, and can extend given implementation by yourself if needed: Maybe D3? Fancy, indeed! https://github.com/mbostock/d3/wiki/Gallery

Radar Chart

Radar Chart isn't exactly the most common chart, so Highchart & D3 didn't have it included - but wait... There's radar chart extensions to d3, and extensions extension http://nbremer.blogspot.nl/2013/09/making-d3-radar-chart-look-bit-better.html

I decided to go for simple basic implementation. It seemed to me complete adequate to present 2 datasets, one having reference data and one containing data from main entity shown at view. What user will see is single page which holds data of entity and radar chart showing selection of actual data and pre-defined boundaries.

Good old GoF MVC


Play2 is built around MVC-pattern. I decided to get data from store (yaml file) at controller, build there simple model of java structure from lists and maps and pass it to view. This is exactly what you do if your application is simple.
If your needs are more rigid you might want to get data at view thru separate Url. Url may point to Play2 based Json service or some external data source, in which case you'd probably need to develop some data transformations. 

Or do you want to build realtime websocket based live views? Play2 is your partner here. For very fancy asynchronous live views example see http://aredko.blogspot.de/2013/05/real-time-charts-with-play-framework.html

My Model


Model is Yaml file containing 2 datasets, which are read to 2 java classes, i.e. MetricSet which contains multiple Metric objects is populated. Dataset1 can be thought to be reference data, which shows boundaries to which data from dataset2 is to be compared. 

private static List<MetricSet> expected = null;
static {
try {
if (expected == null) {
Map<String, List<MetricSet>> all = (Map<String, List<MetricSet>>) Yaml
.load("default-metrics.yml");
expected = all.get("expectations");
Logger.info("Defaults added");

}
} catch (Exception e) {
Logger.error("Defaults couldn't be added " + e.getMessage(), e);
}
}

My Controller


On controller model which is pre-loaded to static member variable is converted to structures which correspond d3 radarcharts data structures. Getting from My Model to List<List<Map<String, Object>>> is trivial.

public static Result show(long evaluationId) {

..

List<List<Map<String, Object>>> radarMetrics = new ArrayList<List<Map<String, Object>>>();
for (MetricSet metricSet : expected) {
List<Map<String, Object>> ds = new ArrayList<Map<String, Object>>();
for (Metric metric : metricSet.metrics ) {
Map<String, Object> item = new HashMap<String, Object>();
item.put("axis", metric.name);
item.put("value", metric.value);
ds.add(item);
}
radarMetrics.add(ds);
}

return ok(views.html.evaluations.show.render(evaluation, radarMetrics));
}

View


View gets data structures thru metrics parameter, which is of type List[List[Map[String, Object]]] as expressed with Scala. Note that view contains import of Google Gson. View has javascript that transforms metrics parameter to Json with Gson, adds some configuration, and renders radar chart.

@(evaluation: models.jpa.Evaluation)(metrics:List[List[Map[String, Object]]])

@* JSON marshalling *@
@import com.google.gson.Gson

@main("Evaluation") {

<script type='text/javascript' src='@routes.WebJarAssets.at(WebJarAssets.locate("d3.js"))'></script>

<script type='text/javascript' src='http://graves.cl/radar-chart-d3/src/radar-chart.js'></script>
   
<div id="chart" style='float:left'></div>

<script type='text/javascript'>//<![CDATA[ 

// get data
var data = @{Html(new Gson().toJson(metrics))};

// build config
var cfg = {
  w: 500,
  h: 500,
  maxValue: 100,
  levels: 10,
}

// render
RadarChart.draw("#chart", data, cfg);    

//]]>  

</script>

Sources


Further steps


Current design creates radar chart targeted copy of model inside controller as part of transformation, which means data duplication, but also makes controller responsible of tasks which belong to view.

It's possible to modularize given example by providing view helper which knows how to create radar chart compatible data structures from model classes. Provided helper should encapsulate data structure transformations, which are currently on controller, and delegate transformation responsibility to view instead of controller. 

There's manyfold of gains keeping it clear that views are in charge of presentation. When model classes can be transformed to Json data structures using single component inside view you can reuse logic easily in case you need radar chart in multiple pages, but also reacting on changes of javascript library or replacing used library with some other gets lot easier. 

No comments: