Macca Blog

My life experiences with code and design

JavaFX Graphing and Charting with Hudson

Posted on by Mark

With the release of JavaFX 1.2, came a whole heap of API changes and new features.

Some of these new features include the charting API, this impressive API includes charts for Bar Graphs, Pie Charts, Bubble Charts, Lines Charts, and more. My own graphing API that I was building was solid, but did not cover as many areas as the official API.

I thought I would go through a great demo of what you can achieve with this API, in particular I want to build a small JavaFX applet, that sends a request to a Hudson server, get some data and build some graphs.

Hudson (one of the most popular Continuous Integration applications on the market), has an API built into it that allows a user to send a http request to get previous build information, as well as other remote information.

Goal

My intention here is to expose a nice way to use JavaFX, Java and the Charting API to create a practical applet that can give developers a visual overview of their build history.

Overview

This implementation actually has a very small number of steps involved:

  1. Build a small Java library that queries Hudson for XML
  2. Build a JavaFX app that uses that library to parse and build a simple Bar Graph

#1, The Java Library

This java library will make use of the popular XML library dom4J to parse the Hudson data from a simple http request.

The hudson server:

I want to make this demonstration accessible to anyone that wants to run it, so I needed to find a public Hudson server, I did a quick Google search and came across (via the Huson wiki page) the publicly accessible deadlock netbeans Hudson server.

FYI, to quickly summarize, you send a http request to the hudson server like http://deadlock.netbeans.org/hudson/api/xml and you get XML back… (you can also get JSON on a per build basic or for the whole project!).

The java code for the request is as follows:

package hudsonremoteaccess;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.io.DocumentResult;
import org.dom4j.io.DocumentSource;

/**
* @author Mark Macumber
*/
public class Hudson {

public Document getXMLHudsonQuery() {
try {
String webUrl = "http://deadlock.netbeans.org/hudson/job/FindBugs/api/xml?depth=1&wrapper=builds&xpath=//build&exclude=//build/artifact&exclude=//build/culprit&exclude=//build/changeSet";
Document xmlDocument = DocumentHelper.parseText(getDataFromUrl(webUrl));
xmlDocument = applyXsltStylesheet(xmlDocument);
return xmlDocument;
} catch (Exception ex) {
System.err.println(ex.getMessage());
}
return null;
}

private Document applyXsltStylesheet(Document document){
try {
// load the transformer using JAXP
TransformerFactory factory = TransformerFactory.newInstance();

InputStream is = this.getClass().getResourceAsStream("/hudsonremoteaccess/resources/styleSheet.xsl");
Transformer transformer = factory.newTransformer(new StreamSource(is));

// now lets style the given document
DocumentSource source = new DocumentSource( document );
DocumentResult result = new DocumentResult();
transformer.transform( source, result );

return result.getDocument();
} catch (TransformerException ex) {
Logger.getLogger(Hudson.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}

private String getDataFromUrl(String webUrl) {
try {
URL url = new URL(webUrl);
URLConnection connection = url.openConnection();
StringBuilder builder = extractDataFromStream(connection);
String response = builder.toString();
return response;
} catch (Exception e) {
System.err.println("Something went wrong...");
e.printStackTrace();
}
return null;
}

private StringBuilder extractDataFromStream(URLConnection connection) throws IOException {
String line;
StringBuilder builder = new StringBuilder();
InputStream is = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
while ((line = reader.readLine()) != null) {
builder.append(line);
}
return builder;
}
}

FYI, you will need to reference the jaxen and dom4j JAR files to run the XML parser and query the results…

#2, The JavaFX code…

This example will show the build time for the Hudson project, many many other graphs are possible, but for the purposes of this demo, I will just be doing one…

As you can see below, the code (with a reference to the Java library above) is very simple.

package hudsongraphs;

import javafx.stage.Stage;
import javafx.scene.Scene;
import hudsonremoteaccess.Hudson;
import org.dom4j.Document;
import org.dom4j.Element;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.part.CategoryAxis;
import javafx.scene.chart.part.NumberAxis;
import javafx.scene.text.Font;
import javafx.scene.paint.Color;

/**
* @author Mark Macumber
*/
var hudson:Hudson = new Hudson();
var xmlDoc:Document = hudson.getXMLHudsonQuery();
var buildNumbers = xmlDoc.selectNodes("//number");
var buildDurations = xmlDoc.selectNodes("//duration");
var numbers:String[] = [];
var durations:Number[] = [];

for (bNumber in buildNumbers) {
insert (bNumber as Element).getData() as String into numbers;
var bDuration = buildDurations.get(indexof bNumber);
var durationInMilliseconds = Number.valueOf((bDuration as Element).getData() as String);
var durationInMinutes = ((durationInMilliseconds/1000)/60);
insert durationInMinutes into durations;
}

Stage {
title: "Hudson Build Duration Timeline"
width: 800
height: 450
scene: Scene {
content: [
BarChart {
title: "Hudson Build Duration Timeline"
width: 750
titleFont: Font { size: 24 }
categoryGap: 2
categoryAxis: CategoryAxis {
categories: numbers
}
valueAxis: NumberAxis {
label: "Build Duration (minutes)"
upperBound: 500
tickUnit: 100
}
data: [
BarChart.Series {
name: "Build #'s"
fill: Color.GREEN
data: for (j in [0..<sizeof durations]) {
BarChart.Data {
category: numbers[j]
value: durations[j]
fill: Color.GREEN
}
}
}
]
}
]
}
}

Basically, you just get the XML, parse it, then build the graph pieces using the JavaFX sequences.

Simple!!!

The screen shot of what this produces is below:

Pretty huh? :)

The above could EASILY be turned into an Applet that could be actually put onto a hosted site within Hudson, just to make it more “integrated” (Netbeans has the easy option to make an Applet out of a JavaFX application).

You should have all of the data to make the application above work, but if not, please do not hesitate to contact me.

I love feedback, please, if you have any advice, comments, criticisms, improvements, etc… please drop me a line.

Cheers,
Mark

This entry was posted in Charting, Graphing, Hudson, Java, JavaFX. Bookmark the permalink.

2 Responses to JavaFX Graphing and Charting with Hudson

  1. Stephen Chin

    Very nice application of the new charting support! This would make a great addition to the JFXtras Samples site: http://jfxtras.org/portal/samples

  2. dontcare

    you may want to look at vtd-xml, the latest and most advanced xml processing api

    vtd-xml

Leave a Comment

Your email address will not be published. Required fields are marked *


*