Attached is a toy program that draws a thousand colored
rectangles, and allows the user to drag them around
with the mouse.
Initially, I didn't pass any arguments to repaint(), so
that all the rectangles were wastefully re-rendered.
This made dragging very slow -- the dragged rectangle
would lag the mouse, and only catch up at the end.
By calculating a sensible clip bound and re-rendering
only the rectangles in that region, the performance
was much better.
Now I've added two rectangles at the bottom of the window that
are connected by a line. As a linked rectangle is dragged
such that the line intersects the field of rectangles,
the repaint region becomes larger and the performance
is poor again -- the rectangle lags behind the mouse
(try dragging the blue rectangle up the right side
of the window).
This isn't a real-world program, but I'm wondering how one
would improve the performance -- with the hope that any such
improvements might be applicable elsewhere.
Thanks.
-David
------------------------+--------------------------+
David Eisner | E-mail: [EMAIL PROTECTED] |
CALCE EPSC | Phone: 301-405-5341 |
University of Maryland | Fax: 301-314-9269 |
------------------------+--------------------------+
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* Toy app to demonstrate performance issue.
*/
public class RectangleTest extends JFrame {
GraphPanel main_panel;
public RectangleTest() {
super("RectangleTest v0.01");
addWindowListener( new WindowAdapter() {
public void windowClosing( WindowEvent e) {
System.exit(0);
}
});
main_panel = new GraphPanel();
main_panel.setPreferredSize( new Dimension( Constants.WIN_WIDTH,
Constants.WIN_HEIGHT ));
main_panel.setBackground( Color.white );
getContentPane().add( main_panel, BorderLayout.CENTER );
}
public static void
main( String args[] ) {
RectangleTest app = new RectangleTest();
app.pack();
app.setVisible(true);
}
}
class Constants {
final static int WIN_WIDTH = 650;
final static int WIN_HEIGHT = 450;
}
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
/**
* JPanel that can display a bunch of colored rectangles.
*/
public class GraphPanel extends JPanel {
Random rg = new Random();
Vector rects = new Vector(); // list of ColoredRectangles
Link link;
Rectangle2D selectedRect = null;
Point dragStartPt = null;
Point rectStartPt = new Point();
final static int NUM_RECTS = 1000;
/**
* Rectangle with a color.
*/
class ColoredRectangle extends Rectangle2D.Double {
private Color color;
ColoredRectangle( int x, int y, int w, int h ) {
this(x, y, w, h, Color.white );
}
ColoredRectangle( int x, int y, int w, int h, Color c ) {
super( x, y, w, h );
color = c;
}
public void
setColor( Color c ) {
color = c;
}
public Color
getColor() {
return color;
}
}
/**
* A simple link between two rectangles.
*/
class Link {
Rectangle2D r1;
Rectangle2D r2;
Link( Rectangle2D rect1, Rectangle2D rect2 ) {
r1 = rect1;
r2 = rect2;
}
}
public GraphPanel() {
super();
Rectangle2D rect;
rect = new ColoredRectangle( 20, Constants.WIN_HEIGHT - 50,
25, 25, Color.red );
rects.add( rect );
rect = new ColoredRectangle( Constants.WIN_WIDTH - 50,
Constants.WIN_HEIGHT - 50,
25, 25, Color.blue );
rects.add( rect );
for (int i= rects.size(); i < NUM_RECTS; i++) {
rect = new ColoredRectangle( rg.nextInt( Constants.WIN_WIDTH - 100),
rg.nextInt( Constants.WIN_HEIGHT -100),
5 + rg.nextInt( 40 ),
5 + rg.nextInt( 40 ),
new Color( rg.nextInt( 256 ),
rg.nextInt( 256 ),
rg.nextInt( 256 ) ));
rects.add( rect );
}
link = new Link( (Rectangle2D) rects.get(0),
(Rectangle2D) rects.get(1) );
MouseInputAdapter adapter = new MouseInputAdapter() {
public void mousePressed( MouseEvent e ) {
Point pt = e.getPoint();
selectedRect = pickRectangle( pt );
if (selectedRect != null) {
dragStartPt = pt;
rectStartPt.setLocation( selectedRect.getX(),
selectedRect.getY() );
}
}
public void mouseDragged( MouseEvent e ) {
if (selectedRect != null) {
double deltaX = e.getX() - dragStartPt.getX();
double deltaY = e.getY() - dragStartPt.getY();
// Calculate bounds for repaint
Rectangle bounds = selectedRect.getBounds();
selectedRect.setRect( rectStartPt.getX() + deltaX,
rectStartPt.getY() + deltaY,
selectedRect.getWidth(),
selectedRect.getHeight() );
bounds.add( selectedRect );
// Make sure bounds includes Link line
if (link.r1 == selectedRect)
bounds.add( link.r2 );
else if (link.r2 == selectedRect)
bounds.add( link.r1 );
bounds.width += 1;
bounds.height += 1;
repaint( bounds );
}
}
public void mouseReleased( MouseEvent e ) {
selectedRect = null;
dragStartPt = null;
}
};
addMouseListener( adapter );
addMouseMotionListener( adapter );
}
public void
paintComponent( Graphics g ) {
Graphics2D g2 = (Graphics2D) g;
Rectangle cb = g2.getClipBounds();
cb.x -= 1;
cb.y -= 1;
cb.width += 1;
cb.height += 1;
super.paintComponent( g2 );
Line2D.Double line = new Line2D.Double( link.r1.getCenterX(),
link.r1.getCenterY(),
link.r2.getCenterX(),
link.r2.getCenterY() );
g2.setColor( Color.black );
g2.draw( line );
for (int i=0; i < rects.size(); i++) {
ColoredRectangle rect = (ColoredRectangle) rects.get(i);
if ( rect.intersects( cb )) {
g2.setColor( rect.getColor() );
g2.fill( rect );
g2.setColor( Color.black );
g2.draw( rect );
}
}
}
/**
* Return rectangle that contains pt, if any, or null otherwise.
*/
private Rectangle2D
pickRectangle( Point pt ) {
double x = pt.getX();
double y = pt.getY();
for (int i=rects.size() - 1; i >= 0; i--) {
Rectangle2D rect = (Rectangle2D) rects.get(i);
if (rect.contains( x, y))
return rect;
}
return null;
}
}