Code snippets and advice in posts matching 'headless' have been helpful. Not for lack of trying, I have been unable to generate correctly rendered chart images in headless mode.
Safari: Black with very faint, dark red artifacts of each bar barely visible
Mac Preview: " " " " " " " " " " " "
Firefox: Orange background with Green and Orange bars
GIMP: " " " " " " "
FlickR: " " " " " " "
The output chart images are here: https://www.flickr.com/photos/68694632@ ... 391994689/
The first image at the link above with the Orange background and green bars (busting the shaded 3D effect), as displayed in Firefox, GIMP, and FlickR. The same image file shows as black on Mac using both Mac Preview, and Safari.
The second image at the link above shows a broken bar and is an example of the screen grab failing every few calls, but thats not important as I need only headless.
The third image at the link above is the correctly rendered chart image produced by doing a programmed screen grab in a non-headless environment. (Note: since I generated these images I switched to the JfreeChart 1.5.0 version which resulted in the new 3D cylinder type bar chart, but the the same colour behaviour is present.)
Here are the relevant POM dependancies in my project. I suspect the jcommon artifact version needs to be bumped, advice welcome:
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.5.0</version> <!-- was 1.0.19 -->
</dependency>
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jcommon</artifactId>
<version>1.0.23</version>
</dependency>
To Test: (after setting CLASSPATH to include jFreeChart jar)
1. Command Line
$ java -Djava.awt.headless=true org.someorg.SkillMatchBar # headless test
$ java org.someorg.SkillMatchBar # non-headless test
Open the generated file testChart.jpg (in current directory) in your browser
OR
2. In Eclipse:
- create two Eclipse run profiles, one with -Djava.awt.headless=true in VM settings, and one with -Djava.awt.headless=false or not present
Open the generated file testChart.jpg (in current directory) in your browser
SkillMatchBar.java
Code: Select all
package org.someorg;
import java.awt.AWTException;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.encoders.ImageFormat;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.ui.RefineryUtilities;
import org.junit.Assert;
import org.slf4j.Logger;
/**
* Originally based on bar chart demo, this adds headless support and adapts
* to my spring microservices demo app
*/
public class SkillMatchBar
{
final static String CLSNAME = SkillMatchBar.class.getSimpleName();
final static Logger logger = org.slf4j.LoggerFactory.getLogger( SkillMatchBar.class );
private JFreeChart chart;
private String title;
private int preferredWidth;
private int preferredHeight;
private String filename;
/**
* Creates a new demo instance.
*/
public SkillMatchBar(String title)
{
this.title = title;
preferredHeight = 340; // hard-coded for test example
preferredWidth = 500; // "
filename = "testChart.jpg"; // "
}
/**
* Returns a sample dataset.
*
* @return The dataset.
*/
private CategoryDataset createDataset() {
// row keys...
final String series1 = "Matching Skills Experience";
// create the dataset...
final DefaultCategoryDataset dataset = new DefaultCategoryDataset();
String[] skills = new String[] { "java", "spring", "sql" }; // test data set
int[] skillMonths= new int[] { 80, 18, 120 };
for ( int i = 0; i < skills.length; i++ )
dataset.addValue( skillMonths[i], series1, skills[i] );
return dataset;
}
/**
* Creates a sample chart.
*
* @param dataset
* the dataset.
*
* @return The chart.
*/
private JFreeChart createChart(final CategoryDataset dataset)
{
final String METHOD = CLSNAME + ".createChart(dataset) ";
boolean headless = GraphicsEnvironment.isHeadless();
System.out.println( METHOD + "Headless: " + headless );
Toolkit tk = Toolkit.getDefaultToolkit();
tk.beep(); // r2d2 lives
// create the chart...
chart = ChartFactory.createBarChart( "Matching Skill Experience", // chart title
"Skill", // domain axis label
"Months", // range axis label
dataset, // data
PlotOrientation.HORIZONTAL, // orientation
true, // include legend
true, // tooltips?
false // URLs?
);
chart.setBorderVisible(false);
// NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
chart.setBackgroundPaint(Color.white);
//chart.setBackgroundImageAlpha(0.15f);
// get a reference to the plot for further customisation...
final CategoryPlot plot = chart.getCategoryPlot();
plot.setBackgroundPaint(Color.lightGray);
plot.setDomainGridlinePaint(Color.white);
plot.setRangeGridlinePaint(Color.white);
// set the range axis to display integers only...
final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
// disable bar outlines...
final BarRenderer renderer = (BarRenderer) plot.getRenderer();
renderer.setDrawBarOutline(false);
// set up gradient paints for series... TODO test if series are used
final GradientPaint gp0 = new GradientPaint(0.0f, 0.0f, Color.blue, 0.0f, 0.0f, Color.lightGray);
renderer.setSeriesPaint(0, gp0);
final CategoryAxis domainAxis = plot.getDomainAxis();
domainAxis.setCategoryLabelPositions(CategoryLabelPositions.createUpRotationLabelPositions(Math.PI / 6.0));
return chart;
}
public BufferedImage generateChartImage()
{
final String METHOD = CLSNAME + ".generateChartImage() ";
BufferedImage image = null;
Rectangle2D rec = new Rectangle( preferredWidth, preferredHeight );
chart = createChart( createDataset() ); // create the JFreeChart chart var
Assert.assertNotNull( chart );
logger.info( METHOD + "size=(" + preferredWidth + "w," + preferredHeight +"h)" );
chart.setBorderPaint( Color.white );
chart.setBorderVisible( false );
chart.setBackgroundPaint( Color.white );
if( ! GraphicsEnvironment.isHeadless() )
{
// STEP 1 - Render chart in GUI
Canvas canvas = null;
JFrame frame = null;
canvas = new Canvas(); // new canvas with white background
canvas.setSize( preferredWidth, preferredHeight );
canvas.setBackground( Color.white );
canvas.setVisible( true );
frame = new JFrame();
frame.setBackground( Color.white ); // new frame with white background
Dimension preferredDim = new Dimension( preferredWidth, preferredHeight );
frame.setSize( preferredDim );
frame.add( canvas ); // canvas added to frame
frame.setVisible( true );
Graphics2D g2 = ( Graphics2D )canvas.getGraphics();
Assert.assertNotNull( g2 );
chart.draw( g2, rec ); // chart draws on canvas perfectly
frame.pack(); // pack frame components layout
RefineryUtilities.centerFrameOnScreen( frame ); // center, correct chart appears in GUI
logger.debug( METHOD + "frame bounds="+frame.getBounds() );
g2.dispose();
// STEP 2 - screen grab to file
BufferedImage screenImage = null;
Robot robot = null;
Exception ex = null;
String msgPrefix = "";
try
{
robot = new Robot();
canvas.setVisible( true );
screenImage = robot.createScreenCapture( frame.getBounds() ); // screen grab, but often has defects
ImageIO.write( screenImage, ImageFormat.JPEG, new File( filename ) );
logger.info( METHOD + "step 2 did write: "+ filename );
}
catch( IOException e ) { ex = e; msgPrefix="ImageIO.write(..) "; }
catch( AWTException awte ) { ex = awte; msgPrefix="new Robot() "; }
finally
{
if( ex != null )
logger.error( METHOD + msgPrefix + "threw Exception: "+ex.getMessage()
+ " cls: "+ex.getClass().getSimpleName()
+ " cause: "+ex.getCause() );
}
}
else // else headless mode create chart
image = headlessCreateChart();
return image;
}
@SuppressWarnings("unused")
private BufferedImage headlessCreateChart()
{
final String METHOD = CLSNAME + ".headlessCreateChart() ";
BufferedImage chartImage = null, backImage = null;
chartImage = chart.createBufferedImage( preferredWidth, preferredHeight ); // chart with very faint
// dark red bars on black entirely black background
/* ****************************************************************************
* A failed attempt to fix the problem
* [The very faint bar artifacts not visible on my MacBook Pro Retina
* display, but are visible on my 23" LCD, only if viewed from 20-90 degrees
* angle vertically above center] Below attempt had no effect to fix the problem.
*/
if( false )
{
backImage = new BufferedImage( preferredWidth, preferredHeight, BufferedImage.TYPE_INT_ARGB ); // 2 - fill a rectangle of white on the whole image
Graphics2D g = ( Graphics2D )backImage.getGraphics();
// 1 - build white backImage
g.setBackground( Color.white );
g.clearRect( 0, 0, preferredWidth, preferredHeight );
g.setPaint( Color.white );
g.setPaintMode();
g.fill( new Rectangle( 0, 0, preferredWidth, preferredHeight ) );
// 2 - set background into chart
chart.setBackgroundImage( backImage );
chart.setBackgroundImageAlpha( 0.15f );
chartImage = chart.createBufferedImage( preferredWidth, preferredHeight ); // create does draw(.)
g.dispose();
}
writeChartImage( chartImage );
return chartImage;
}
private void writeChartImage( BufferedImage chartImage )
{
final String METHOD = CLSNAME + ".writeTestImage(chartImage) ";
Assert.assertNotNull( chartImage );
int numElements = chartImage.getData().getDataBuffer().getSize();
IOException ex = null;
try
{
File imageFile = new File( filename );
boolean exists = imageFile.exists();
logger.info( METHOD + " did the new File( filename ), exists="+exists+" so over-writing" );
OutputStream outStream = new FileOutputStream( filename );
//ChartUtilities.writeBufferedImageAsJPEG( outStream, chartImage );
ImageIO.write( chartImage, ImageFormat.JPEG, outStream );
outStream.close();
logger.info( METHOD + "End wrote filename="+filename+" image.numElements="+numElements
+ " size=("+chartImage.getWidth()+"w," + chartImage.getHeight()+"h)" );
}
catch( IOException ex2 ) { ex = ex2; }
finally
{
if( ex != null )
logger.error( METHOD + "IOException: "+ex.getMessage()
+ " cls: "+ex.getClass().getSimpleName()
+" cause: "+ex.getCause() );
}
}
/**
* Starting point for the demonstration application.
*
* @param args
* ignored.
*/
public static void main(final String[] args)
{
final String METHOD = CLSNAME + ".main(args.length="+args.length+") ";
logger.info( METHOD + "Start" );
final SkillMatchBar demo = new SkillMatchBar( "Match Skills Experience" );
BufferedImage chartImage = demo.generateChartImage();
logger.info( METHOD + "End" );
}
}
JFreeChart ver: 1.5
Java ver: 1.8
Browser: Safari 11.01