Search code examples
javaswingrandompaintcomponent

How to paint a circle with a random size on a random coordinate such that the circle is fully visible?


Code:

Random rand = new Random();
JPanel mainPanel;

int randomSize = 0; 
int randomPositionX = 0;
int randomPositionY = 0;

final static int FRAME_HEIGHT = 500;
final static int FRAME_WIDTH  = 500;

final static int TITLE_BAR    = 30 ;

final static int MAX_SIZE     = 100;
final static int MIN_SIZE     = 10 ;

/* All the below code is put into a method */

mainPanel = new JPanel(){   
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.RED);
        g.fillOval(randomPositionY, randomPositionX, randomSize, randomSize);
    }
};

do{
    randomSize = rand.nextInt(MAX_SIZE) + 1;
}while(randomSize < MIN_SIZE);

do{
    randomPositionX = rand.nextInt(FRAME_WIDTH);
    randomPositionY = rand.nextInt(FRAME_HEIGHT);
}while((randomPositionX + randomSize > FRAME_WIDTH) || (randomPositionY + randomSize > FRAME_HEIGHT - TITLE_BAR));

repaint();

What I want is the circle to have a random size such that it should have a minimum size of 10 and a maximum size of 100. The circle should also be painted in a random coordinate such that the circle is fully visible inside the JPanel mainPanel.

Note that mainPanel will be added to a JFrame whose size is set using setSize(FRAME_WIDTH, FRAME_HEIGHT);.

But the problem is that sometimes, a part of the circle is half outside and half inside the JPanel:

SCREENSHOT

Where did I go wrong?


Solution

    1. You're trying to use the frame size when calculating the position of the circle, when the frame includes a variable high title bar AND frame insets
    2. You're trying to use the frame size when it's irrelevant, it's the component's size you should be using, as it's within the mainPanel's coordinate context which you want to paint

    So, how do I fix the issues?

    Discard ALL the frame "padding"/"offsets". The mainPanel has it's own coordinate context (it's top left corner will be 0x0) and it will be within it's parent's coordinate context, so you don't need to care about the frame or it's insets.

    Simplify your calculations, for example...

    int randomSize = MIN_SIZE + (rand.nextInt(MAX_SIZE) + 1);
    
    int randomPositionX = rand.nextInt(getWidth() - randomSize);
    int randomPositionY = rand.nextInt(getHeight() - randomSize);
    

    Should allow you to produce results which won't fall outside of the viewable area, as the maximum range of the dimensions is the current size of the container minus the desired size, just beware, if the available size is smaller then MAX_SIZE, you will have issues ;)

    The example generates a new circle every second

    Random Circles

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Rectangle;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.Random;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.Timer;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        public Test() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public static class TestPane extends JPanel {
    
            public final static int MAX_SIZE = 100;
            public final static int MIN_SIZE = 10;
            private Rectangle bounds;
            private Random rand;
    
            public TestPane() {
                rand = new Random();
                Timer timer = new Timer(1000, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
    
                        int randomSize = MIN_SIZE + (rand.nextInt(MAX_SIZE) + 1);
    
                        int randomPositionX = rand.nextInt(getWidth() - randomSize);
                        int randomPositionY = rand.nextInt(getHeight() - randomSize);
    
                        bounds = new Rectangle(randomPositionX, randomPositionY, randomSize, randomSize);
                        repaint();
                    }
                });
                timer.start();
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(200, 200);
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
                if (bounds != null) {
                    g2d.setColor(Color.RED);
                    g2d.fillOval(bounds.x, bounds.y, bounds.width, bounds.height);
                }
                g2d.dispose();
            }
    
        }
    
    }