San Mor
San Mor

Reputation: 133

Graphics and SwingWorker

I'm trying to paint a dynamic graph that has a few thousand points. I'm using SwingWorker class that gets the Graphics object as an argument. I initialize the graphics object in the constructor of the SwingWorker class and then call the main graphing function doGraph() in the done() method i.e, after all the calculations are done. I'm having issues with painting, not sure what is going on. The code works if I call doGraph() from the constructor of the SwingWorker class but not when I place it in done().

Any help is greatly appreciated. I have simplified the code for ease of understanding. I`m just trying to paint a rectangle in the doGraph() for simplicity.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.RenderingHints.Key;

import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

@SuppressWarnings("serial")
public class UIGraph extends JPanel {

private double minX, maxX;
private double minY, maxY;
private SWPaint swPaint; //Swing Worker object
private Graphics2D g2D;
public Key key = RenderingHints.KEY_ANTIALIASING;
public Object obj = RenderingHints.VALUE_ANTIALIAS_ON;
private int labelPadding = 25;
private int padding = 25;
private int padConst = (2 * padding) - labelPadding;
private double xScale;
private double yScale;

/**
 * Crate Panel
 */
private void createUI() {
    setBackground(new Color(245, 255, 250));
    setLayout(new BorderLayout(0, 0));
}

/**
 * Constructor
 * 
 */
public UIGraph() {
    createUI();
    getMinMax();
}

/**
 * Paint Component. Do all the calculations and graphing
 */
@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);

    if (swPaint != null) {
        if (!swPaint.isDone()) { // The swing worker is busy with tasks. Set Visibility to false and return.
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    setVisible(false);
                }
            });
            return;
        }
    }

    swPaint = new SWPaint(g);  //Create a swing worker class, pass graphics object as argument
    swPaint.execute();
    }
}

/**
 * Get the Min and Max values of Data
 */
private void getMinMax() {
  //obtain min and max values
}



/**
 * Main method for graphing
 * 
 * @param g2D 
 */
private void doGraph() {
    xScale = (double) (getWidth() - padConst) / (maxX - minX);
    yScale = (double) (getHeight() - padConst) / (maxY - minY);

    g2D.setColor(Color.WHITE);
    g2D.fillRect(padding + labelPadding, padding, getWidth() - (2 * padding) - labelPadding, getHeight() - 2 * padding
            - labelPadding);
    g2D.setColor(Color.BLACK);
}

/**
 * Swing Worker to handle Paint
 *
 */
public class SWPaint extends SwingWorker<Void, Void> {

    /**
     * Constructor
     * 
     * @param g
     */
    public SWPaint(Graphics g) {
        g2D = (Graphics2D) g;
        g2D.setRenderingHint(key, obj);

       //doGraph() //This works
    }

    /**
     * Do graphing calculations here
     */
    @Override
    protected Void doInBackground() throws Exception {
        // do all calculations here
        return null;
    }

    /*
     * The SW is done. Paint the graph. Set Visibility to true
     */
    protected void done() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                doGraph();  //This doesn`t work here.
                setVisible(true);
            }
        });
    }
  }
}

Upvotes: 0

Views: 169

Answers (1)

Emmanuel Bourg
Emmanuel Bourg

Reputation: 10998

A few things:

  • SwingWorker.done() is executed on the EDT, using SwingUtilities.invokeLater there isn't necessary
  • Triggering the SwingWorker from the paintComponent() method isn't a good idea as it may do more computations than necessary. You should rather execute the SwingWorker when the state changes, and just let paintComponent() draw the data that were last computed.
  • Holding a reference to the component Graphics2D object at the class level is dubious, there is no garantee the instance will be the same between two paintComponent() calls, so you may end up drawing on the wrong graphic context.
  • If the rendering time is important, even once the computations are done, you should consider drawing to an off screen image first.

Upvotes: 0

Related Questions