Mobile Dashboard Reporting powered by JAX-RS and Highcharts

When we developed this sales reporting solution for the insurance sector, we went for a mobile, browser-based dashboard that renders the reports on the client-side and thus enable a high degree of interactivity. That means that once the reporting data is delivered, the client should be able to e.g. drill down into the data or slide along the time axis. This article focuses on the technical aspects of the data delivery in JSON format and interactive charting in the browser.

The basic idea was to develop a data bridge which receives the reporting data from the server side and provide it conveniently for consumption on the client side. The data is delivered on-demand to the HTML single-page application using AJAX to load JSON documents. This gives us very high flexibility (as we will see) and is in contrast to the conventional mixing of data into HTML pages that happens on the server side (think of JSP/ASP.NET).

In our approach, the server is solely responsible for providing a data API (e.g. with support for time-sliced queries) and for delivering the data in the universal JSON format, i.e. it encodes data as code that can be interpreted by a broad range of different client platforms. This reduces the complexity of the server application by leaving the interpretation and visualization to the client application of the solution. The actual display of the data may even differ between the clients like desktop, browser or mobile applications.

The present browser application is based on Highcharts and jQuery. We chose Highcharts since it’s a mature JavaScript charting framework.

Screenshot a of sample Sales Mobile Dashboard.

Providing Reporting Data to the Browser via JAX-RS

Reporting data is known to have a multidimensional nature, while practically reports (in the ROLAP flavor) tend to have several views or perspectives cutting the data cube. Additionally reporting data is time-sliced. For instance, the report data of 2001 is not going to change anymore. This is a perfect situation for caching, for instance in HTML5’s Web Storage or a mobile browser’s database. Furthermore the time-slicing allows querying for distinct portions of the reporting data. Dividing the load leads to achieving an acceptable response time (in opposite to mobile data warehouses).

The choice for the protocol to provide and consume the report data was easy, since HTTP is the most familiar protocol to browsers. The strong points of that decision came up later, when all pieces of that puzzle fit together.

Applying a resource-oriented approach first requires some simple addressability, which will ease the clients to formulate follow-up-requests. Next a collection of resource representations needs to be defined. For the given setup it was evident to focus on JSON. The listing below shows the key part of the JAX-RS bean serving the report data in JSON format using the Jackson JSON processor.

// JAX-RS Resource bean
public abstract class ReportingResource {
	...
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public final Response getReportData(
        @HeaderParam(DATE_FROM_HEADER) final Date dateFrom,
		@HeaderParam(DATE_UNTIL_HEADER) final Date dateUntil,
        @Context final HttpHeaders httpHeaders,
		@Context final SecurityContext securityContext)
	{
		// require authentication and a certain user role
		if (securityContext.getUserPrincipal() == null
            || !securityContext.isUserInRole(REQUIRED_USER_ROLE))
        {
			return Response.status(Status.UNAUTHORIZED).build();
		}

		return Response.ok(
		    getReportDataInDateRange(dateFrom, dateUntil, httpHeaders))
		    .build();
	}
	...
}

The Data-as-Code Approach using JSON

The principle data-as-code applies here, as seen in the next listing: JSON is a data format and at the same time part of the JavaScript language specification. Furthermore it demonstrates the memory model of JavaScript-runtime-environments.

{
  "data": [
    {
      "lobNames": [
        "LIABILITY_POLICY_PREMIUM",
        "LIABILITY_OFFER_PREMIUM"
      ],
      "currency": "EUR",
      "periodBegin": "2012-08-01",
      "name": "PremiumLobTimelineTimeslice",
      "LIABILITY_POLICY_PREMIUM": 1461812,
      "LIABILITY_OFFER_PREMIUM": 0
    },
    {
      "lobNames": [
        "LIABILITY_POLICY_PREMIUM",
        "LIABILITY_OFFER_PREMIUM",
        "PROPERTY_OFFER_PREMIUM",
        "PROPERTY_POLICY_PREMIUM"
      ],
      "currency": "EUR",
      "periodBegin": "2012-09-01",
      "name": "PremiumLobTimelineTimeslice",
      "LIABILITY_POLICY_PREMIUM": 1081483,
      "LIABILITY_OFFER_PREMIUM": 129314,
      "PROPERTY_OFFER_PREMIUM": 1240473,
      "PROPERTY_POLICY_PREMIUM": 61074854
    }
  ]
}

We use jQuery Ajax requests to asynchronously fetch specific data from server REST/JAX-RS resources that follow the mentioned addressability rules. The data is basically fetched in time-slices, as seen in the listing below (dateFrom and dateUntil). We can restrict the data exchange to just those portions of the report data, which are of current interest to the user.

// Excerpt from Reporting.js
reporting.Reporting = {
	/* ... */
loadData : function(reportType, noCache) {
		/* ... */
		var headerParams = {
		'dateFrom' : reporting.utils.DateNavigator.getStartDate(),
		'dateUntil': reporting.utils.DateNavigator.getEndDate()
		};
		jQuery.ajax({
			url : url,
			dataType : 'json',
			cache : !noCache,
			async : false,
			headers : headerParams,
			success : onSuccess,
			error : onError
		});
		return resultData;
	}
	/* ... */
}

Rendering Data Charts in the Browser

Regarding the JSON structure Highcharts has so-called formatters, like yAxis.labels.formatter, that allow easy access to row-wise reporting data. The minimum requirement is to have an ordered JSON array below data (see listing above). In some edge cases (as in the subsequent listing) it is also possible to re-group the reporting data, so that it fits e.g. for a stacked bar chart like in this example.

// Highcharts configuration
renderChartConfig : function(data) {
	// values for xAxis
	var categories = [];
	// stack of lob (line of business) totals
	var series = [];

	//...

	for ( var i = 0; i < data.length; i++) {
		categories[i] = Highcharts.dateFormat(
				'%b %y', reporting.utils.DateUtil.parseUtcMillis(data[i].periodBegin));
		// series[j].data[i] = data[i][lobName];
	}

	var config = {
		// ...
		xAxis : {
			categories : categories,
			// ...
		},
		yAxis : {
			labels : {
				formatter : function() {
					return Highcharts.numberFormat(
							this.value, 0, '.', ',') + ' &euro;';
				}
			},
		},
		legend : {
			labelFormatter : function() {
				return this.name;
			},
		},
		tooltip : {
			formatter : function() {
				return '<strong>' + this.x + '</strong>'
					+'<br/>' + this.series.name + ': '
					+ Highcharts.numberFormat(this.y, 0, '.', ',') + '<br/>'
					+ 'Total: ' + Highcharts.numberFormat(
							this.point.stackTotal, 0, '.', ',');
			}
		},
		series : series
	};
	return config;
}

Conclusion

Realizing the server side with JAX-RS and JSON marshalling resulted in a modular and extensible solution. Since reporting needs to have a vital dynamic over time, it should be possible to create a new perspective or view (on the data cube) in no time.

This is achieved by decoupling the client application’s use of the data from the server and its internals of how to provide the data. In other terms, providing data does not mean to know how it will be interpreted. The complexity of browsers and client diversity mentioned above can be moved to the JavaScript frameworks and so away from the server-side.

Share

Leave a Reply

*