Printing my own ChartPanel

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
brimborium
Posts: 23
Joined: Mon Dec 13, 2010 10:23 am
antibot: No, of course not.

Printing my own ChartPanel

Post by brimborium » Wed Feb 15, 2012 4:30 pm

Hi,

I am using JFreeChart 1.0.13 to display some plots. As I needed some additional stuff on the plots, I extended the ChartPanel to a new class APChartPanel which overwrites the paintComponent(Graphics g) method to also draw some stuff:

Code: Select all

public class APChartPanel extends ChartPanel {
	public void paintComponent(java.awt.Graphics g) {
		super.paintComponent(g);
		drawAdditionalStuff(g, getScreenDataArea());
	}
	
	private void drawAdditionalStuff(Graphics g, Rectangle2D plotArea) {
		// draw lots of stuff here...
	}
	
	// other methods to control what to draw additionally

}
This works all very nice!

Now I want to give the user the possibility to print out the ChartPanel (landscape). I found out, that ChartPanel already provides a method createChartPrintJob(). When I call this method, it (obviously) does only draw the stuff from the original ChartPanel and not my stuff too. I am a little bit lost here on how to do this elegantly (or just do it at all ^^). As there is lots of stuff already there in the ChartPanel, I suspect there is a very easy solution to this.

Best,
Stefan

brimborium
Posts: 23
Joined: Mon Dec 13, 2010 10:23 am
antibot: No, of course not.

Re: Printing my own ChartPanel

Post by brimborium » Thu Feb 16, 2012 10:54 am

Ok, so I created a little example for you to understand better, what my problem is. I found, that there is a createPrintJob() method in ChartPanel. But this method does (of course) not work with my paintComponents, but with the parent one from ChartPanel itself. Therefore the additional data is not shown. I tried to overwrite createPrintJob() and also the print() method (you can find my attempt commented out in the APChartPanel.java at lines 33-56), which works, but has some problems with the drawing area (the plot does not fit the page). But I think, it must be something like this...

So my Demo consists of two classes,
  • PrintPlotDemo.java, which contains the main() and creates the stuff.
  • APChartPanel.java, which extends ChartPanel to draw some additional stuff onto the plot
You also have to add jfreechart-1.0.13.jar and jcommon-1.0.16.jar to the project. (I guess that this is not so much dependent on the version of jfreechart)
And here is the code:

Code: Select all

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

public class PrintPlotDemo {
	private static final int N = 10;
	private static final int Nsamp = 100;
	private static final double xMin = 0, xMax = 2 * Math.PI;

	public static void main(String[] args) {
		XYSeries[] plots = new XYSeries[N];
		XYSeriesCollection data = new XYSeriesCollection();
		for (int i = 0; i < N; i++) {
			plots[i] = new XYSeries("Series " + i);
			for (double t = 0; t <= 2 * Math.PI; t += (xMax - xMin) / (Nsamp - 1)) {
				plots[i].add(t, Math.sin(t * i)*Math.exp(-3*t/xMax), false);
			}
			data.addSeries(plots[i]);
		}

		JFreeChart chart = ChartFactory.createXYLineChart("", "", "", data, PlotOrientation.VERTICAL, false, false, false);
		final APChartPanel chartPanel = new APChartPanel(chart);
		chartPanel.setAdditional(new String[] {"This is some", "additional data", "which consists of", "some silly strings"});

		JButton printButton = new JButton("Print");
		printButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				chartPanel.createChartPrintJob();
			}
		});
		
		JFrame frame = new JFrame("Print Plot Demo");
		frame.setLayout(new BorderLayout());
		frame.add(chartPanel, BorderLayout.CENTER);
		frame.add(printButton, BorderLayout.SOUTH);
		frame.setVisible(true);
		frame.setSize(800, 600);
	}
}

Code: Select all

import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.print.PageFormat;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;

import javax.swing.JOptionPane;

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;

public class APChartPanel extends ChartPanel {
	private static final long serialVersionUID = 1L;
	private String[] additionalStr = {};

	public APChartPanel(JFreeChart chart) {
		super(chart);
	}

	public void paintComponent(java.awt.Graphics g) {
		super.paintComponent(g);
		drawAdditionalStuff(g, getScreenDataArea());
	}

	public void setAdditional(String[] stuff) {
		additionalStr = stuff;
		updateUI();
	}

	// uncomment to get the additional stuff printed, but then, the printed "image" does not fit the page...
    //	@Override
    //	public int print(Graphics g, PageFormat pf, int pageIndex) {
    //		if (pageIndex > 0) {
    //			return NO_SUCH_PAGE;
    //		} else {
    //			Graphics2D g2d = (Graphics2D) g;
    //			g2d.translate(pf.getImageableX(), pf.getImageableY());
    //			this.paint(g2d);
    //			return PAGE_EXISTS;
    //		}
    //	}
    //
    //	public void createChartPrintJob() {
    //		PrinterJob job = PrinterJob.getPrinterJob();
    //		job.setPrintable(this);
    //		if (job.printDialog()) {
    //			try {
    //				job.print();
    //			} catch (PrinterException e) {
    //				JOptionPane.showMessageDialog(this, e);
    //			}
    //		}
    //	}

	private void drawAdditionalStuff(Graphics g, Rectangle2D plotArea) {
		// Rectangle2D plotArea = getScreenDataArea();
		if (additionalStr.length > 0) {
			// g.setFont(new Font(null, Font.PLAIN, 10));
			FontMetrics met = g.getFontMetrics();
			int border = 10, margin = 5;
			int x = (int) plotArea.getX(), y = (int) plotArea.getY();
			int w = (int) plotArea.getWidth(), h = (int) plotArea.getHeight();
			x += w * 3 / 4 - border;
			y += border;
			w = w / 4;
			h = 2 * margin + (additionalStr.length - 1) * (met.getHeight() + 3) + met.getAscent() + 3;
			g.setColor(new Color(0.3f, 0.5f, 0.7f, 0.3f));
			g.fillRect(x, y, w, h);
			g.setColor(new Color(0f, 0f, 0f, 0.6f));
			g.drawRect(x, y, w, h);
			g.setColor(new Color(0f, 0f, 0f, 0.8f));
			int asc = met.getAscent();
			int height = met.getHeight();
			int xS = x + margin, yS = y + margin + asc, tJ = 2 * margin, hJ = height + 3;
			for (int i = 0; i < additionalStr.length; i++)
				g.drawString(additionalStr[i], xS + tJ, yS + i * hJ);
		}
	}
}
I would be really glad, if you could help me. I always dodged around this printing stuff so far, now I want to understand it... ^^

brimborium
Posts: 23
Joined: Mon Dec 13, 2010 10:23 am
antibot: No, of course not.

Re: Printing my own ChartPanel

Post by brimborium » Thu Feb 16, 2012 2:55 pm

Ok, I got a little further now. It is not so elegant anymore, but it nearly works. :D
So what I did, is overwriting the print() and createPrintJob() method of the ChartPanel like so:

Code: Select all

public int print(Graphics g, PageFormat pf, int pageIndex) {
	if (pageIndex != 0) return NO_SUCH_PAGE;
	Graphics2D g2 = (Graphics2D) g;
	double x = pf.getImageableX();
	double y = pf.getImageableY();
	double w = pf.getImageableWidth();
	double h = pf.getImageableHeight();
	this.getChart().draw(g2, new Rectangle2D.Double(x, y, w, h), this.getAnchor(), null);
	this.drawAdditionalStuff(g2, new Rectangle2D.Double(x, y, w, h));
	return PAGE_EXISTS;
}

public void createChartPrintJob() {
	PrinterJob job = PrinterJob.getPrinterJob();
	PageFormat pf = new PageFormat();
	pf.setOrientation(PageFormat.LANDSCAPE);
	job.setPrintable(this, pf);
	if (job.printDialog()) {
		try { job.print(); } catch (PrinterException e) { JOptionPane.showMessageDialog(this, e); }
	}
}
Basically, this draws the stuff manually, that I do in paintComponents, so I don't use my paintComponents method, which is uncool because thats basically copy and paste. The problem I have is the following:
To draw the additional stuff, I need to have access to ChartPanel.getScreenDataArea(), which gives me the possibility to convert between pixel coordinates and axis coordinates. Obviously, the plot should be fitted to the page while printing. If I use the normal getScreenDataArea(), I get the data area given by the displayed ChartPanel. Is there a way to somehow get this area for a different size of the component (without setting it, as I don't want to disturb the displayed ChartPanel?

brimborium
Posts: 23
Joined: Mon Dec 13, 2010 10:23 am
antibot: No, of course not.

Re: Printing my own ChartPanel

Post by brimborium » Thu Feb 16, 2012 3:14 pm

Ok, finally I figured a way of doing it. Yay! Although I am setting the size of the plot (right before I call paintComponent() and reset it to the original size right after). I don't know if this is a good way to do it, but it seems to work. If someone know a better way, I would be interested, but for now, I leave it like it is.

So for everyone else who had this problem, here is the full demo from before, but with the solution:
PlotPrintDemo.java:

Code: Select all

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

public class PrintPlotDemo {
	private static final int N = 10;
	private static final int Nsamp = 100;
	private static final double xMin = 0, xMax = 2 * Math.PI;

	public static void main(String[] args) {
		XYSeries[] plots = new XYSeries[N];
		XYSeriesCollection data = new XYSeriesCollection();
		for (int i = 0; i < N; i++) {
			plots[i] = new XYSeries("Series " + i);
			for (double t = 0; t <= 2 * Math.PI; t += (xMax - xMin) / (Nsamp - 1)) {
				plots[i].add(t, Math.sin(t * i)*Math.exp(-3*t/xMax), false);
			}
			data.addSeries(plots[i]);
		}

		JFreeChart chart = ChartFactory.createXYLineChart("", "", "", data, PlotOrientation.VERTICAL, false, false, false);
		final APChartPanel chartPanel = new APChartPanel(chart);
		chartPanel.setAdditional(new String[] {"This is some", "additional data", "which consists of", "some silly strings"});

		JButton printButton = new JButton("Print");
		printButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				chartPanel.createChartPrintJob();
			}
		});
		
		JFrame frame = new JFrame("Print Plot Demo");
		frame.setLayout(new BorderLayout());
		frame.add(chartPanel, BorderLayout.CENTER);
		frame.add(printButton, BorderLayout.SOUTH);
		frame.setVisible(true);
		frame.setSize(800, 600);
	}
}
APChartPanel.java:

Code: Select all

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.print.PageFormat;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;

import javax.swing.JOptionPane;

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;

public class APChartPanel extends ChartPanel {
	private static final long serialVersionUID = 1L;
	private String[] additionalStr = {};

	public APChartPanel(JFreeChart chart) {
		super(chart);
	}

	public void paintComponent(java.awt.Graphics g) {
		super.paintComponent(g);
		 drawAdditionalStuff(g, getScreenDataArea());
	}

	public void setAdditional(String[] stuff) {
		additionalStr = stuff;
		updateUI();
	}

	/**
	 * Prints the chart on a single page.
	 * @param g the graphics context.
	 * @param pf the page format to use.
	 * @param pageIndex the index of the page. If not <code>0</code>, nothing gets print.
	 * @return The result of printing.
	 */
	public int print(Graphics g, PageFormat pf, int pageIndex) {
		if (pageIndex != 0) return NO_SUCH_PAGE;
		Graphics2D g2 = (Graphics2D) g;
		double x = pf.getImageableX();
		double y = pf.getImageableY();
		double w = pf.getImageableWidth();
		double h = pf.getImageableHeight();
		Dimension beforeSize = this.getSize();
		this.setSize((int)w, (int)h);
		g2.translate(x, y);
		paintComponent(g2);
		this.setSize(beforeSize);
		return PAGE_EXISTS;
	}

	public void createChartPrintJob() {
		PrinterJob job = PrinterJob.getPrinterJob();
		PageFormat pf = new PageFormat();
		pf.setOrientation(PageFormat.LANDSCAPE);
		job.setPrintable(this, pf);
		if (job.printDialog()) {
			try {
				job.print();
			} catch (PrinterException e) {
				JOptionPane.showMessageDialog(this, e);
			}
		}
	}

	private void drawAdditionalStuff(Graphics g, Rectangle2D plotArea) {
		if (additionalStr.length > 0) {
			FontMetrics met = g.getFontMetrics();
			int border = 10, margin = 5;
			int x = (int) plotArea.getX(), y = (int) plotArea.getY();
			int w = (int) plotArea.getWidth(), h = (int) plotArea.getHeight();
			x += w * 3 / 4 - border;
			y += border;
			w = w / 4;
			h = 2 * margin + (additionalStr.length - 1) * (met.getHeight() + 3) + met.getAscent() + 3;
			g.setColor(new Color(0.3f, 0.5f, 0.7f, 0.3f));
			g.fillRect(x, y, w, h);
			g.setColor(new Color(0f, 0f, 0f, 0.6f));
			g.drawRect(x, y, w, h);
			g.setColor(new Color(0f, 0f, 0f, 0.8f));
			int asc = met.getAscent();
			int height = met.getHeight();
			int xS = x + margin, yS = y + margin + asc, tJ = 2 * margin, hJ = height + 3;
			for (int i = 0; i < additionalStr.length; i++)
				g.drawString(additionalStr[i], xS + tJ, yS + i * hJ);
		}
	}
}
Sorry for the fuzz, but maybe, someone can use this code too. I certainly learned a lot about printing with java in general today. :D

Locked