Dynamic coloring of PRD Charts

Pentaho Report Designer exposes the jFreeChart objects of charts to a post-processing script. This opens up some interesting possibilities. This post shows some techniques to dynamically change the appearance of a chart. All samples are available in the download package. Let’s play!

Using Colors straight from the Data

In some circumstances it’s useful to get colors straight from the data. Suppose the data refers to actual colors, say of t-shirts sold. There’s several different colors, and they are not known up front as they were in the Triage example. Colors may come and go from the stores and the chart shall always display the colors sold. In the following sample the query provides the product name, color and turnover.


A sales chart may look something like this then:

The dynamic color is produced using a Beanshell script. It scans through the dataset and creates a hashmap that maps products to their colors. This map is then used to assign the colors to the appropriate bars as they appear in the chart’s data model.

// Set the field, that contains the color code for a series.
// It should be a string in hex format i.e. 'FF0000' for red.
COLOR_FIELD = "Color";

// set the field that is used as series 
SERIES_FIELD = "Product";

// the default color to use if none is found (should never happen)
DEFAULT_COLOR = Color.red;


// ------------------------------------------------------------------ //
//  The code below is responsible for assigning the color found in 
//  COLOR_FIELD to the corresponding bar which shows 
//  an item from SERIES_FIELD
// ------------------------------------------------------------------ //

// helper function determine indexes of fields
int findColumnIndex(String name){

	data = runtime.getData();
	for (int i=0;i<data.getColumnCount();i++){
		if (data.getColumnName(i).equals(name)){
			return i;
		}
	}

	return -1;
}

// helper function scans the dataset for colors and maps them in a hash
colorMap = new HashMap();
void mapColors(){

	color = DEFAULT_COLOR;
	data = runtime.getData();

	colorFieldIndex = findColumnIndex(COLOR_FIELD);
	seriesFieldIndex = findColumnIndex(SERIES_FIELD);

	for (int i=0;i<data.getRowCount();i++){
			currSeries = data.getValueAt(i, seriesFieldIndex);
		try{
			colorStr = data.getValueAt(i, colorFieldIndex);
			colorIntValue = Integer.parseInt( colorStr ,16);
			currColor = new Color(colorIntValue);	
			colorMap.put(currSeries, currColor);
		}
		catch(Exception e){
			colorMap.put(currSeries, defaultColor);
			// just move on, break the colors, but don't break the report
		}			
	}
}

// map the colors to a global hashmap key->series value->color
mapColors();

// assign the colors to the bars based on the series 
renderer = chart.getPlot().getRenderer();
keys = chart.getPlot().getDataset().getRowKeys();

for (int i=0;i<keys.size();i++){
	k = keys.get(i);
	renderer.setSeriesPaint(i, colorMap.get(k));
}

A sample report is available in the download package.

Deriving Colors from Values

Sometimes it would be nice to derive the color from the values displayed in the chart. Say a bar chart should display turnover for different products. Each product bar should start out blue at $0 and get greener with higher sales numbers, with the green fully saturated at $1.000.000. When sorted, such interpolations can generate a nice visual effect. Here’s two versions of a chart like this, a randomly generated version and one with the rows ordered by turnover.

The script uses a helper function to interpolate between colors, depending on turnover value.

// helper function to interpolate between colors (distance is between 0.0 and 1.0)
interpolateColor(Color from, Color to, double distance){

	int finalR = (int)((to.getRed()-from.getRed())*distance+from.getRed());
	int finalG = (int)((to.getGreen()-from.getGreen())*distance+from.getGreen());
	int finalB = (int)((to.getBlue()-from.getBlue())*distance+from.getBlue());

	return new Color(finalR, finalG, finalB);
	
}

// grab the interesting objects from jFreeChart
renderer = chart.getPlot().getRenderer();
data = chart.getPlot().getDataset();
keys = data.getRowKeys();

// loop through each bar and give it a color based on its value
for (int i=0;i<keys.size();i++){
	// the closer the value to $1M, the closer the distance is to 1.0 (% of goal reached)
	// be sure to cap it at 1.0, too
	double distance = Math.min(1.0,data.getValue(i,0)/1000000.0);
	// find the value for this key
	renderer.setSeriesPaint(i, interpolateColor(Color.blue, Color.green, distance));
}

A sample report is available in the download package.

Gradients

Gradient Backgrounds

The jFreeChart engine supports gradients, which also allows for some neat effects. Say management wants to see some “stellar” performance, so there must be a night sky in the background of the chart. A gradient image can be used for that, or if you’d like to (or have to) do it dynamically, create the gradient using a short script.

The night sky in the above chart is produced by a 2-color gradient. Check out the script for that:

// grab the interesting objects from jFreeChart
plot = chart.getPlot();

// this defines the gradient for the background
g = new GradientPaint(
	0.0f, 0.0f, new Color(0, 51, 111), // dark blue
	0.0f, 0.0f, new Color(0,  0,  4)      // almost black
);

plot.setBackgroundPaint(g);

Gradient Bars

The bars can also have gradients applied to them. It’s just a matter of assigning a gradient paint to them instead of a fixed color.

And here’s the script to do that:

// grab the interesting objects from jFreeChart
plot = chart.getPlot();
renderer = plot.getRenderer();
data = chart.getPlot().getDataset();
keys = data.getRowKeys();

// this defines the gradient for the background
g = new GradientPaint(
	0.0f, 0.0f, new Color(0, 51, 111), // dark blue
	0.0f, 0.0f, new Color(0,  0,  4)   // almost black
);

plot.setBackgroundPaint(g);

// the gradient for the bars 

gb = new GradientPaint(
	0.0f, 0.0f, new Color(255, 255, 222), // bright yellow
	0.0f, 0.0f, new Color(255,  255,  100)   // deep yellow
);

// loop through each bar and give it a gradient paint
for (int i=0;i<keys.size();i++){
	renderer.setSeriesPaint(i, gb);
}

Gradients in 3D Charts

When applying gradients to 3D charts, you’ll have to specify proper anchor coordinates for each gradient. In the 2D examples they were specified as 0, and jFreeChart worked them out automatically. The anchor coordinates are in relation to the upper left corner of the image. Check out the sample:

The script applied to the 3D chart had the following gradient definitions:

// this defines the gradient for the background
g = new GradientPaint(
	0.0f, 200.0f, new Color(0, 51, 111), // dark blue
	0.0f, 0.0f, new Color(0,  0,  4)   // almost black
);
// the gradient for the bars 
gb = new GradientPaint(
	0.0f, 30.0f, new Color(255, 255, 222), // bright yellow
	0.0f, 250.0f, new Color(255,  255,  100)   // deep yellow
);

The background definition has the dark blue near the bottom of the chart (200px down) and the black at the top (0px down). The bars are similarly defined with the bright yellow near the top (30px down) and the deep yellow near the bottom (250px down);

Altering the x values of the paint will give tilted/diagonal gradients. You may also want to check out cyclic gradients. Check out the GradientPaint docs for details.

A sample report is available in the download package.

Shaded Bars

When viewed straight on, a round pillar appears much like a shaded bar. Let’s see if that fact can be used to “round off” the bars on the chart a bit. The jFreeChart library happily accepts gradients with multiple colors. So when management suddenly decides that the night sky thing should be replaced by a “distant light illuminating our pillars of success”, you may come up with something like this.

The script employs the same techniques as before. The only difference is that it uses gradients with multiple colors in them.

// grab the interesting objects from jFreeChart
plot = chart.getPlot();
renderer = plot.getRenderer();
data = chart.getPlot().getDataset();
keys = data.getRowKeys();


// background gradient (3 step)
Color[] bgColors = {
	new Color(14,36,43),
	new Color(19,40,42),
	new Color(247,240,255)
};

float[] bgDistances = {
	0.0f, 0.25f, 0.8f
};

bg = new LinearGradientPaint(
	0.0f, 400.0f,  // anchor from
	500.0f, 0.0f, // anchor to
	bgDistances,
	bgColors
);

plot.setBackgroundPaint(bg);


// bar gradient
Color[] colors = {
	
	new Color(68,77,95),
	new Color(24,49,52),
	new Color(10,30,25),
	new Color(24,53,47),
	new Color(139,132,158),
	new Color(237,230,255)	
	
};
float[] distances = {
	0.0f,
	0.046511f,
	0.116279f,
	0.425348f,
	0.744186f,
	1.0f
};

// loop through each bar and give it a gradient paint
for (int i=0;i<keys.size();i++){

	float barLeft = 68.0f+i*99.8f;
	float barRight = barLeft+75.0f;
	g = new LinearGradientPaint(
		barLeft, 0.0f,  // anchor from
		barRight, 0.0f, // anchor to
		distances,
		colors
	);
	
	renderer.setSeriesPaint(i, g);
}

A sample report is available in the download package.

Conclusion

While the above techniques and tricks produce interesting results, especially when at the hands of the artistically inclined, I’d like to point out that code, no matter how trivial it may seem, should probably not be used too much in reporting. Whatever works using the UI should be solved using the UI. I can’t imagine how maintaining color gradients in a variety of charts manually would be worth the time required to do it, for example. It’s fun to experiment around with scripting and from time to time something useful may come from it. In practice I’d still try to mostly rely on the standard features of the reporting engine. Reserve the special scripting for special occasions. And then do rock the house! :)

Downloads

All of the above reports a available in the sample package. The sample reports were created using PRD 3.8.

Go crazy! ;)
Slawo

10 comments to Dynamic coloring of PRD Charts

  • Dan Kinsley

    Slawomir,
    What a great couple of posts! I have wanted to do something like this for a long time, but never would have thought of doing it this way, very creative. I had great luck pretty much just copy & pasting your code examples for charts with Category Datasets, but it didn’t seem to work for a chart I had that used an XY Dataset (well a TimeSeries in this particular example). I played around a bit and was able to get it working with the following code:


    ...

    // grab the interesting objects from jFreeChart
    renderer = chart.getPlot().getRenderer();
    data = chart.getPlot().getDataset();

    // loop through each bar and give it a color based on its contents
    for (int i=0;i<data.getSeriesCount() ;i++){

    Color c = Color.darkGray;

    switch(data.getSeriesKey(i).toString()){

    ...

    I figured I should post it in case anyone else experiences similar issues. Thanks again!

  • Slawomir Chodnicki

    Hi Dan,

    yeah, depending on the chart type and PRD/jFreeChart version the object structure changes slightly. Thanks for sharing!

    Cheers
    Slawo

  • Bob

    That works great for bar charts. What changes need to be made to make it work with pie charts?

  • Slawomir Chodnicki

    Bob,
    The best way to find out is probably to get your hands at the jFreeChart javadoc and samples and figure it out. It’s what I did to find a solution for the bar charts.

    Best
    Slawo

  • Shaun

    Hey Bob,

    Were you able to figure out the way to apply dynamic colours for pie charts?

    Regards,
    Shaun

  • Bob

    Hi

    Not yet, Im hoping to look at it this week. Thanks for the tip Slawo, I hope the solution is easy :)

  • Shaun

    Hi,

    Do keep us posted, still trying to figure it out.

  • penthgen

    Hi Slawomir, wanted to ask you if you could help us to get this to work with pie charts.

    We have tried to implement Solution 2: Assigning Colors to Series dynamically of your post Color Coding and PRD Charts

    But unfortunately we cant get to work with pie charts within PRD
    We have read the javadoc too, but couldnt find anything there.

    Thank you for your help

  • felix

    for pie charts:
    plot = chart.getPlot();
    data = plot.getDataset();
    keys = data.getKeys();

    for (int i=0;i<keys.size();i++){

    if(keys.get(i).equals("ERROR")){
    plot.setSectionPaint(i, Color.orange);
    }

    if(keys.get(i).equals("OK")){
    plot.setSectionPaint(i, Color.green);
    }

    if(keys.get(i).equals("FAILURE")){
    plot.setSectionPaint(i, Color.red);
    }

    if(keys.get(i).equals("FAULT")){
    plot.setSectionPaint(i, Color.yellow);
    }
    }

  • Meherun

    Is it possible to fix the plot height? I mean when there are multi lines of legends underneath the plot, plot height decreases. I want to fix the plot height

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>