Es ist wieder soweit: Ich habe ein Problem beim Drucken mit JFreeChart v0.9.11. Nachdem ich ja schon einmal darüber geschrieben hatte, tritt jetzt ein neues Problem auf:
Ich möchte ein stinknormales ChartPanel drucken. Der Chart ist ein ScatterPlot, den ich mit den Factory-Methoden aus ChartFactory erzeugt habe. Es werden nur 50k Datenpunkte angezeigt. Nach dem Aufruf von ChartPanel#createChartPrintJob() vergeht nun geraume Zeit, in der keinerlei Reaktion des Programms erfolgt. Ausserdem wird ein 38MB groÃer Druckauftrag erzeugt. Wenn ich bedenke, dass ich eigentlich vorhabe, einen Chart mit 2,5M Datenpunkten zu drucken, wird mir jetzt schon schwindelig.
Gut, also muss ich das Drucken ein wenig aufmöbeln. Der einfachste Ansatz ist das Erstellen eines eigenen PrintJobs:
PrinterJob job = PrinterJob.getPrinterJob();
PageFormat format = job.defaultPage();
format.setOrientation(PageFormat.LANDSCAPE);
format = job.pageDialog(format);
job.setPrintable(cp, format);
if (job.printDialog()) {
try {
job.print();
} catch (PrinterException e) {}
} |
Dadurch wird das Verhalten auch nicht anders, also verwendet das ChartPanel genau diesen Mechanismus zum Drucken – allerdings kann ich jetzt direkt Querformat vorgeben. Hilft aber immer noch nicht. Also erstmal in einen Thread auslagern:
Thread t = new Thread(new Runnable() {
public void run() {
// allein: sehr schlecht - lange, zu groÃer Speicherbedarf
PrinterJob job = PrinterJob.getPrinterJob();
PageFormat format = job.defaultPage();
format.setOrientation(PageFormat.LANDSCAPE);
format = job.pageDialog(format);
job.setPrintable(cp, format);
if (job.printDialog()) {
try {
job.print();
} catch (PrinterException e) {}
}
}
});
t.start(); |
Danach zeigt zumindest das GUI wieder eine Reaktion, nachdem der Einstellungsdialog für die Druckoptionen geschlossen wurde. Allerdings kann man nun nicht mehr erkennen, dass überhaupt etwas passiert, denn die Erstellung des PrintJob läuft ja im Hintergrund. Also auch noch keine Lösung. Tatsächlich scheint hier wieder nur eine eigenen Implementierung eines ChartPanels zu helfen, die die print()-Methode überschreibt und das ChartPanel auf geeignete Weise neu zeichnet.
Ich bin einen etwas anderen Weg gegangen und habe mir zuerst ein Bild des ChartPanels in einer fest eingestellten Auflösung gezeichnet und anschliessend dieses Bild ausgegeben. Dazu habe ich ein paar Methoden definiert. Als erstes eine Methode, die aus dem ChartPanel ein Bild in fester Auflösung macht:
// Kopiert aus CustomChartPanel#getHighResChartImage
private BufferedImage getHighResChartImage(ChartPanel chartpanel, int resolution) {
int screenResolution = Toolkit.getDefaultToolkit().getScreenResolution();
int scaleRatio = resolution / screenResolution;
// get width and height for image files
//int width = ResourceContainer.getInstance().getWidthForFiles();//getWidth();
//int height = ResourceContainer.getInstance().getHeightForFiles();//getHeight();
// width, height for resolution
int width = chartpanel.getWidth() * scaleRatio;
int height = chartpanel.getHeight() * scaleRatio;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = image.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
/*
g2.transform(AffineTransform.getScaleInstance(scaleRatio,
scaleRatio));
*/
chartpanel.getChart().draw(g2, new Rectangle2D.Double(0,
0, width, height), null);
g2.dispose();
return image;
} |
Jetzt fehlt noch eine druckbare Variante des BufferedImage, um das Bild zu Papier zu bringen:
class PrintImage implements Printable {
private BufferedImage myImage;
public PrintImage(BufferedImage image) {
myImage = image;
}
public int print(Graphics gr, PageFormat pageFormat, int pageIndex) {
if(pageIndex!=0) return NO_SUCH_PAGE;
Graphics2D g = (Graphics2D)gr;
double x = pageFormat.getImageableX();
double y = pageFormat.getImageableY();
double w = pageFormat.getImageableWidth();
double h = pageFormat.getImageableHeight();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
double sx = w / myImage.getWidth();
double sy = h / myImage.getHeight();
g.translate(x,y);
g.scale(sx, sy);
gr.drawImage(myImage, 0, 0, Color.WHITE, null);
return PAGE_EXISTS;
}
} |
Anschliessend wird beim Druck folgender Aufruf verwendet:
PrintImage pi = new PrintImage(getHighResChartImage(cp, 300));
PrinterJob job = PrinterJob.getPrinterJob();
PageFormat format = job.defaultPage();
format.setOrientation(PageFormat.LANDSCAPE);
format = job.pageDialog(format);
job.setPrintable(pi, format);
if (job.printDialog()) {
try {
job.print();
} catch (PrinterException e) {}
} |
Es wird ein BufferedImage mit 300dpi erstellt, dass das ChartPanel cp entsprechend seiner Bildschirmdarstellung enthält. Dieses Bild ist deutlich schneller erstellt, als der PrintJob von Original-ChartPanel. Anschliessend wird das PrintImage gedruckt, was wiederum schneller beendet ist, als ein ChartPanel zu drucken. Nachteil dieser Methode ist aber, dass die Auflösung fest mit 300dpi angegeben wurde – wenn der Drucker es schafft, ok. Ansonsten wird skaliert, was wieder zu schlechten Ergebnissen führt…
Alles noch nicht sehr ausgereift, und ich hoffe, das aktuelle JFreeChart-Versionen dieses Manko behoben haben…