4D BubbleChart with JFreeChart
2. February 2010
Recently I worked on some scientific software project where we had tons of charts to deal with. One of the charts we needed to produce was a chart that used a normal XY-Chart to display up to four dimensions. I used the wonderful JFreeChart library, developed by David Gilbert. And by adding a few enhancements I was able to come up with a XY chart, that displays its third and fourth dimension as bubbles. The 3rd dimension is denoted by its diameter and the 4th dimension by a color code. This is what I finally had:
So, How do you do that? Here are the steps needed:
- implement a Dataset understood by JFreeChart that keeps 4 dimensions
- build a renderer that displays bubbles with a specific color and radius
- build a color legend and a size legend for the bubble radius
Of course, one needs a special Dataset. JFreeChart uses the so called XYZDataset as base type for its two dimensional bubble charts. So in order to have a fourth dimension we define a subtype with an additional dimension (C as in Color):
public interface XYZCDataset extends XYZDataset{
public double getC(int seriesIndex, int index);
public double getNormalized(Dimension dimension, int seriesIndex, int index);
public double getMin(Dimension dim);
public double getMax(Dimension dim);
}
The Dimension type is an enum that declares, well dimensions, i.e. X,Y up to Z. The getNormalized Function returns a normalized value (between 0 and 1) of a specific column. Since we need to define a maximum diameter in the plot and a color code, it is convenient to retrieve that from the Dataset (which might cache the necessary reference values). The getMin() and getMax() methods return the minimum and maximum value of any series stored in the dataset (there might be more than one).
Next we need a special renderer that honors the third and fourth dimension of the given Dataset. Here make a copy of the BubbleRenderer of JFreeChart and enhance that code. The most interesting part may be that we need to calculate a color and a radius for any given item. For the color we make use of a radial gradient and use
private Paint createPaint(double colorvalue, Ellipse2D circle) {
Rectangle2D r = circle.getBounds2D();
Color color = Color.getHSBColor(1f - (float) colorvalue, 0.75f, 1f);
Color iColor = new Color(color.getRed(), color.getGreen(), color.getBlue(), 120);
return create(
circle.getCenterX(),
circle.getCenterY(),
iColor,
new Point2D.Double(r.getWidth() / 2D, r.getWidth() / 2D), color);
}
public static Paint create(double x, double y, Color pointColor, Point2D radius, Color backgroundColor) {
return new RadialGradientPaint((float) x, (float) y, (float) radius.getX(), new float[]{0.5f, 1f},
new Color[]{pointColor, backgroundColor});
}
to define the Paint with which we paint the current bubble. It has an alpha value of about 50% in the center and is opaque at the border. For the radius of the given circle we define a default maximum value and scale the radius of each circle with that maximum. The radius of each bubble is determined by a (default) maximum radius and a normalized value of the 3rd dimension between 0 and 1.
We still have to deal with the legends. We need a legend with a color stripe to denote the values of the 4th dimension. The ColorLegend ist just a PaintScaleLegend as it can be found in the JFreeChart package. It defines an Axis and a PaintScale to be able to paint the color stripe and the ticks for it. The legend for the 3rd. dimension which is visualized using the the diameter of the bubbles is painted with a special BubbleLegend. Again this is a PaintScaleLegend, but the draw method is overridden since we do not draw any color stripes but a number of circles with a specific diameter and a label. There is no sophistication in that class besides some fiddling with the layout.
So now alle we have to do is to create the usual JFreeChart from the data. The two legends have a preconfigured location (on the right side of the plot) and are just added to the chart.
public JFreeChart createChart(String title, String xlabel, String ylabel, XYZCDataset dataset) {
if (dataset == null) {
return null;
}
JFreeChart chart = null;
try {
chart = ChartFactory.createBubbleChart( title, xlabel, ylabel,
dataset, PlotOrientation.VERTICAL,
true, true, false);
XYPlot plot = (XYPlot) chart.getPlot();
ColorLegend colorLegend = new ColorLegend("C",
dataset.getMin(Dimension.Z),
dataset.getMax(Dimension.Z));
chart.addSubtitle(colorLegend);
BubbleLegend bubbleLegend = new BubbleLegend("Z",
dataset.getMin(Dimension.Z1),
dataset.getMax(Dimension.Z1));
chart.addSubtitle(bubbleLegend);
LegendTitle ltitle = chart.getLegend();
ltitle.setBackgroundPaint(new Color(0, 0, 0, 0));
plot.setRenderer(new MultiBubbleRenderer());
} catch (Exception exception) {
exception.printStackTrace();
}
return chart;
}
You can find the sources for the 4D Bubble package here.
It as a plain Maven project with a single dependency to the JFreeChart libraries.