My HTML-formatted annotations look great when they are displayed in a Java window, but do not appear in my chart's PDF output.
I have stepped through a lot of code to try to understand the difference between these situtations, and wrote a small example that illustrates my problem.
The program below is based on org.jfree.chart.demo.TimeSeriesChartDemo1. I've added some annotations, and print the chart to a PDF file (in your home directory) after displaying it in a Java window.
I would appreciate it if you could try this program and see if you can reproduce my results. I see 4 annotations in the Java window (yellow rectangle, black line, a green circle, and an HTML table), but only 3 annotations in the PDF file (the HTML table is missing).
Furthermore(!), if you click-and-drag (while viewing the PDF file in Acrobat) where the HTML annotation is supposed to be, you can select the invisible text! (See screenshot.) You can copy the "Hello" text into the cut buffer, and paste it into notepad. This tells me that the text is getting into the PDF file, but it is not visible.
I am rendering my HTML annotation in a JEditorPane, and adding it to the chart with an XYDrawableAnnotation. I implemented a method that creates an annotation for any JComponent, which seems to work fine for a JPanel with a custom paint() method. It does not seem to work for JEditorPane.
In writeChartAsPDF(), I have some commented-out code which will successfully draw a JEditorPane directly into the PDF file, bypassing the annotation system.
The fact that I can write a JEditorPane directly into the PDF, but not via the annotation system, suggests that there is more that I need to take into account when dealing with the annotation system.
Thanks for your advice.
XYDrawableAnnotation with JEditorPane to render HTML
XYDrawableAnnotation with JEditorPane to render HTML
Last edited by mickish on Tue Mar 25, 2008 9:21 pm, edited 1 time in total.
Code: Select all
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JPanel;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.Drawable;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.annotations.XYDrawableAnnotation;
import org.jfree.chart.annotations.XYLineAnnotation;
import org.jfree.chart.annotations.XYShapeAnnotation;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.util.ApplicationFrame;
import org.jfree.chart.util.RectangleInsets;
import org.jfree.chart.util.RefineryUtilities;
import org.jfree.data.time.Month;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.XYDataset;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.DefaultFontMapper;
import com.lowagie.text.pdf.FontMapper;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfTemplate;
import com.lowagie.text.pdf.PdfWriter;
/**
* An example of a time series chart. For the most part, default settings are
* used, except that the renderer is modified to show filled shapes (as well as
* lines) at each data point.
*/
public class TimeSeriesChartDemoX extends ApplicationFrame {
/**
* The chart displayed in this frame
*/
final JFreeChart chart;
/**
* Some sample HTML to display in an annotation
*/
final static String defaultHTML =
"<body bgcolor='FFAAEE'>\n" +
" <table border='1' valign='center' align='center'>\n" +
" <tr><td><b>Hello</b></td><td>There</td></tr>\n" +
" <tr><td><font size='+3'>Hello</font></td><td><i>again</i></td></tr>\n" +
" </table>\n" +
"</body>\n";
/**
* A demonstration application showing how to create a simple time series
* chart. This example uses monthly data.
*
* @param title the frame title.
*/
public TimeSeriesChartDemoX(String title) {
super(title);
ChartPanel chartPanel = (ChartPanel) createDemoPanel();
chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
chartPanel.setMouseZoomable(true, false);
setContentPane(chartPanel);
this.chart = chartPanel.getChart();
}
/**
* Creates a chart.
*
* @param dataset a dataset.
*
* @return A chart.
*/
private static JFreeChart createChart(XYDataset dataset) {
JFreeChart chart = ChartFactory.createTimeSeriesChart(
"Legal & General Unit Trust Prices", // title
"Date", // x-axis label
"Price Per Unit", // y-axis label
dataset, // data
true, // create legend?
true, // generate tooltips?
false // generate URLs?
);
chart.setBackgroundPaint(Color.white);
XYPlot plot = (XYPlot) chart.getPlot();
plot.setBackgroundPaint(Color.lightGray);
plot.setDomainGridlinePaint(Color.white);
plot.setRangeGridlinePaint(Color.white);
plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
plot.setDomainCrosshairVisible(true);
plot.setRangeCrosshairVisible(true);
XYItemRenderer r = plot.getRenderer();
if (r instanceof XYLineAndShapeRenderer) {
XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) r;
renderer.setBaseShapesVisible(true);
renderer.setBaseShapesFilled(true);
}
DateAxis axis = (DateAxis) plot.getDomainAxis();
axis.setDateFormatOverride(new SimpleDateFormat("MMM-yyyy"));
addAnnotations( chart );
return chart;
}
//--------------------------------------------------
private static void addAnnotations(JFreeChart chart) {
long t1 = (new GregorianCalendar(2001, Calendar.MARCH, 17)).getTimeInMillis();
long t2 = (new GregorianCalendar(2001, Calendar.JUNE, 17)).getTimeInMillis();
long t3 = (new GregorianCalendar(2001, Calendar.AUGUST, 17)).getTimeInMillis();
long t4 = (new GregorianCalendar(2002, Calendar.MARCH, 17)).getTimeInMillis();
// yellow rectangle
chart.getXYPlot().addAnnotation(
new XYShapeAnnotation(
new Rectangle2D.Double(t1, 175, (t2-t1), 8),
new BasicStroke(1.0f), Color.blue, Color.yellow ) );
// black line
chart.getXYPlot().addAnnotation(
new XYLineAnnotation(t1, 130, t2, 160) );
// JPanel that paints a circle
chart.getXYPlot().addAnnotation(
createXYDrawableAnnotation(
new JPanel() {
@Override
public void paint(Graphics g) {
g.setColor( java.awt.Color.green );
g.fillOval(10, 10, 20, 20);
}
},
t3, 170 ) );
// XYDrawableAnnotation for a JEditorPane
chart.getXYPlot().addAnnotation(
createXYDrawableAnnotation(
createHTMLEditorPane( defaultHTML, 150 ),
t4, 140 ) );
}
//--------------------------------------------------
/**
* Convenience method for creating XYDrawableAnnotations from JComponents.
*/
public static XYDrawableAnnotation createXYDrawableAnnotation(final JComponent c,
final double x, final double y) {
final Drawable d = new Drawable() {
public void draw(final Graphics2D g2, final Rectangle2D area) {
final AffineTransform atPrev = g2.getTransform();
try {
g2.translate( area.getX(), area.getY() );
c.paint( g2 );
} finally {
g2.setTransform( atPrev );
}
}
};
return new XYDrawableAnnotation(x, y, c.getWidth(), c.getHeight(), d);
}
//--------------------------------------------------
static public JEditorPane createHTMLEditorPane(final String text, int width) {
final JEditorPane pane = new JEditorPane();
pane.setEditable(false);
pane.setContentType("text/html");
pane.setText(text);
pane.setSize(new Dimension(width,100));
pane.validate();
return pane;
}
//--------------------------------------------------
/**
* Creates a dataset, consisting of two series of monthly data.
*
* @return The dataset.
*/
private static XYDataset createDataset() {
TimeSeries s1 = new TimeSeries("L&G European Index Trust", Month.class);
s1.add(new Month(2, 2001), 181.8);
s1.add(new Month(3, 2001), 167.3);
s1.add(new Month(4, 2001), 153.8);
s1.add(new Month(5, 2001), 167.6);
s1.add(new Month(6, 2001), 158.8);
s1.add(new Month(7, 2001), 148.3);
s1.add(new Month(8, 2001), 153.9);
s1.add(new Month(9, 2001), 142.7);
s1.add(new Month(10, 2001), 123.2);
s1.add(new Month(11, 2001), 131.8);
s1.add(new Month(12, 2001), 139.6);
s1.add(new Month(1, 2002), 142.9);
s1.add(new Month(2, 2002), 138.7);
s1.add(new Month(3, 2002), 137.3);
s1.add(new Month(4, 2002), 143.9);
s1.add(new Month(5, 2002), 139.8);
s1.add(new Month(6, 2002), 137.0);
s1.add(new Month(7, 2002), 132.8);
TimeSeries s2 = new TimeSeries("L&G UK Index Trust", Month.class);
s2.add(new Month(2, 2001), 129.6);
s2.add(new Month(3, 2001), 123.2);
s2.add(new Month(4, 2001), 117.2);
s2.add(new Month(5, 2001), 124.1);
s2.add(new Month(6, 2001), 122.6);
s2.add(new Month(7, 2001), 119.2);
s2.add(new Month(8, 2001), 116.5);
s2.add(new Month(9, 2001), 112.7);
s2.add(new Month(10, 2001), 101.5);
s2.add(new Month(11, 2001), 106.1);
s2.add(new Month(12, 2001), 110.3);
s2.add(new Month(1, 2002), 111.7);
s2.add(new Month(2, 2002), 111.0);
s2.add(new Month(3, 2002), 109.6);
s2.add(new Month(4, 2002), 113.2);
s2.add(new Month(5, 2002), 111.6);
s2.add(new Month(6, 2002), 108.8);
s2.add(new Month(7, 2002), 101.6);
// ******************************************************************
// More than 150 demo applications are included with the JFreeChart
// Developer Guide...for more information, see:
//
// > http://www.object-refinery.com/jfreechart/guide.html
//
// ******************************************************************
TimeSeriesCollection dataset = new TimeSeriesCollection();
dataset.addSeries(s1);
dataset.addSeries(s2);
return dataset;
}
/**
* Saves a chart to a PDF file.
*
* @param file the file.
* @param chart the chart.
* @param width the chart width.
* @param height the chart height.
* @param mapper the font mapper.
*
* @throws IOException if there is an I/O problem.
*/
public static void saveChartAsPDF(File file,
JFreeChart chart,
int width,
int height,
FontMapper mapper) throws IOException {
OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
writeChartAsPDF(out, chart, width, height, mapper);
out.close();
}
/**
* Writes a chart to an output stream in PDF format.
*
* @param out the output stream.
* @param chart the chart.
* @param width the chart width.
* @param height the chart height.
* @param mapper the font mapper.
*
* @throws IOException if there is an I/O problem.
*/
public static void writeChartAsPDF(OutputStream out,
JFreeChart chart,
int width,
int height,
FontMapper mapper) throws IOException {
Rectangle pagesize = new Rectangle(width, height);
Document document = new Document(pagesize, 50, 50, 50, 50);
try {
PdfWriter writer = PdfWriter.getInstance(document, out);
document.addAuthor("JFreeChart");
document.addSubject("Demonstration");
document.open();
PdfContentByte cb = writer.getDirectContent();
PdfTemplate tp = cb.createTemplate(width, height);
Graphics2D g2 = tp.createGraphics(width, height, mapper);
Rectangle2D r2D = new Rectangle2D.Double(0, 0, width, height);
chart.draw(g2, r2D);
// Uncomment this to draw the JEditorPane directly into the PDF
// final AffineTransform atPrev = g2.getTransform();
// try {
// g2.translate( 225, 50 );
// createHTMLEditorPane( defaultHTML, 150 ).paint(g2);
// } finally {
// g2.setTransform( atPrev );
// }
g2.dispose();
cb.addTemplate(tp, 0, 0);
}
catch (DocumentException de) {
System.err.println(de.getMessage());
}
document.close();
}
/**
* Creates a panel for the demo (used by SuperDemo.java).
*
* @return A panel.
*/
public static JPanel createDemoPanel() {
JFreeChart chart = createChart(createDataset());
return new ChartPanel(chart);
}
/**
* Starting point for the demonstration application.
*
* @param args ignored.
* @throws IOException
*/
public static void main(String[] args) throws IOException {
TimeSeriesChartDemoX demo = new TimeSeriesChartDemoX(
"Time Series Chart Demo X");
demo.pack();
RefineryUtilities.centerFrameOnScreen(demo);
demo.setVisible(true);
// write the chart to a PDF file...
File fileName = new File(System.getProperty("user.home")
+ "/TimeSeriesChartDemoX.pdf");
saveChartAsPDF(fileName, demo.chart, 400, 300, new DefaultFontMapper());
}
}
-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
Re: XYDrawableAnnotation with JEditorPane to render HTML
There shouldn't be anything else that you need to take account of. I'm able to reproduce the bug, but I don't know the cause of it. I'll try a few things out...mickish wrote:The fact that I can write a JEditorPane directly into the PDF, but not via the annotation system, suggests that there is more that I need to take into account when dealing with the annotation system.
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
It seems that iText's PdfGraphics2D class doesn't like our alpha composite setting. Try inserting the following line right before your commented out code that draws the editor pane to the PDF:
If you do this, you'll see that the HTML output is also invisible (transparent?) in the PDF, just like with the annotation. I suspect this is an iText bug, but I can't yet figure out exactly how their implementation of PdfGraphics2D works, so I can't be 100% sure.
Code: Select all
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
1.0f));
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


Thanks, that is very helpful!
As a workaround, I now set AlphaComposite.SRC in my Drawable's draw() method, which means "overwrite the destination with the source, regardless of alpha":
As a workaround, I now set AlphaComposite.SRC in my Drawable's draw() method, which means "overwrite the destination with the source, regardless of alpha":
Code: Select all
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 1.0f));