How to draw simple diagrams the easy way (with Java2D)

When you need to produce lots of fairly straightforward graphs, Java2D is your friend. In this blog, I show you how you can render simple images as crisp-looking PDF or EPS files, provided you can draw them on a Graphics2D object.

Recently, I had to draw a bunch of simple images, such as this one

or this PDF

Of course, you can do these drawings in Illustrator or Inkscape. But if you are a coder like me, a graphics program may not be your cup of tea. It takes me about 15 seconds to code

   for (int i = 0; i < 6; i++)
      for (int j = 0; j < 6; j++) {
         g2.setColor(2 <= i && i <= 3 && 2 <= j && j <= 3 ? Color.GREEN : Color.RED);
         g2.fillRect(i * 30, j * 30, 29, 29);
      }

(Or at least it feels like 15 seconds :-))

To do the same in Inkscape would take me a seriously long time, and I would be bored to tears.

What about that maze? I had to set it up anyway for a CS1 lab, using Alice, and it wasn't much extra trouble to add a draw(Graphics2D g2) method to each Robot, Wall, and Beeper object. I really enjoyed having an image with perfect lines and circles, with only a few minutes of work.

Once you can draw your image on a Graphics2D, it is a routine matter to turn it into a PostScript or image file, using standard Java 2D operations. You can then process the PostScript output into PDF or EPS using ps2pdf or ps2eps. Java 2D seems to have fallen out of fashion, and there doesn't seem to be a lot of useful information (outside Core Java, Filthy Rich Clients, and the Sun Java tutorial). That's too bad—it is a very powerful framework and it isn't hard to learn. So, here is the program outline that I use. Feel free to use it for your 2D rendering needs.

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.print.*;
import java.io.*;
import javax.imageio.*;
import javax.print.*;
import javax.print.attribute.*;
import javax.swing.*;

public class Image {
   // Set your image dimensions here
   private static int IMAGE_WIDTH = ...;
   private static int IMAGE_HEIGHT = ...;

   public static void draw(Graphics2D g2) {
      // Your drawing instructions go here
      ...
   }

   // No need to touch anything below this line
   public static void main(String[] args)
   {
      final JFrame frame = new JFrame();
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      final JComponent component = new JComponent() {
            public void paintComponent(Graphics g) {
               draw((Graphics2D) g);
            }
            public Dimension getPreferredSize() {
               return new Dimension(IMAGE_WIDTH, IMAGE_HEIGHT);
            }
         };
      frame.add(component);
      JMenuBar menuBar = new JMenuBar();
      frame.setJMenuBar(menuBar);
      JMenu menu = new JMenu("File");
      menu.setMnemonic('F');
      menuBar.add(menu);
      JMenuItem item = new JMenuItem("Save", 'S');
      menu.add(item);
      item.setAccelerator(KeyStroke.getKeyStroke("ctrl S"));
      item.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
               JFileChooser chooser = new JFileChooser();
               if (chooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION)
                  try {
                     saveImage(component, chooser.getSelectedFile().getPath());
                  } catch (Exception ex) {
                     ex.printStackTrace();
                  }
            }
         });      
      frame.pack();
      frame.setVisible(true);
   }

   private static void saveImage(final JComponent comp, String fileName) throws IOException, PrintException {
      if (fileName.endsWith(".ps")) {
         DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
         String mimeType = "application/postscript";
         StreamPrintServiceFactory[] factories = StreamPrintServiceFactory.lookupStreamPrintServiceFactories(flavor, mimeType);
         FileOutputStream out = new FileOutputStream(fileName);
         if (factories.length > 0) {
            PrintService service = factories[0].getPrintService(out);            
            SimpleDoc doc = new SimpleDoc(new Printable() {
                  public int print(Graphics g, PageFormat pf, int page) {
                     if (page >= 1) return Printable.NO_SUCH_PAGE;
                     else {
                        double sf1 = pf.getImageableWidth() / (comp.getWidth() + 1);
                        double sf2 = pf.getImageableHeight() / (comp.getHeight() + 1);
                        double s = Math.min(sf1, sf2);
                        Graphics2D g2 = (Graphics2D) g;
                        g2.translate((pf.getWidth() - pf.getImageableWidth()) / 2, (pf.getHeight() - pf.getImageableHeight()) / 2);
                        g2.scale(s, s);
                        
                        comp.paint(g);
                        return Printable.PAGE_EXISTS;
                     }
                  }
               }, flavor, null);
            DocPrintJob job = service.createPrintJob();
            PrintRequestAttributeSet attributes = new HashPrintRequestAttributeSet();
            job.print(doc, attributes);
         }
      } else {
         Rectangle rect = comp.getBounds();
         BufferedImage image = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_RGB);
         Graphics2D g = (Graphics2D) image.getGraphics();
         g.setColor(Color.WHITE);
         g.fill(rect);
         g.setColor(Color.BLACK);
         comp.paint(g);
         String extension = fileName.substring(fileName.lastIndexOf('.') + 1);
         ImageIO.write(image, extension, new File(fileName));
         g.dispose();
      }
   }   
}