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();
      }
   }   
}