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

}

Reply via email to