'Issues my method to get a 2D circle to move in a circle

OBS! Changed as part of the question has been answered.

My math has been fixed due to your help and input, the same with StackOverflowError but I still can get my head around how to make the circle move from one x,y point to another. Currently I just repeat the drawing multiple places.

public class MyFrame extends JPanel {
        int xc = 300, yc = 300, r = 100, diam = 50;
        double inc = Math.PI / 360, theta = 0;

        public void paintComponent(Graphics g) {

                Timer timer = new Timer(0, new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                                theta = theta + inc;
                                repaint();
                        }
                });
                timer.setDelay(2);
                timer.start();
        }
        @Override
        public void paint(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g;
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); //smooth the border around the circle
                g2d.rotate(theta, xc, yc);
                g2d.setColor(Color.blue);
                g2d.drawOval(xc + r - diam / 2, yc + r - diam / 2, diam, diam);
paintComponent(g);
        }
}


Solution 1:[1]

This should help you get started. You can modify it as you see fit. It simply has an outer circle revolve around an inner red dot at the center of the panel.

  • First, rotate the graphics context, and not the circle location around the center. Thus, no trig is required.
  • Anti-aliasing simply fools the eye into thinking the graphics are smoother.
  • BasicStroke sets the thickness of the line
  • you need to put the panel in a frame.
  • and super.paintComponent(g) should be first statement in paintComponent to clear panel (and do other things).
  • the timer updates the angle by increment and invokes repaint. A larger increment will make a quicker but more "jerky" motion about the center. If you set the angle to Math.PI/4, then you need to increase the timer delay (try about 1000ms).
  • Check out the Java Tutorials for more on painting.
  • Anything else I omitted or forgot should be documented in the JavaDocs.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class DrawCircle extends JPanel {
    int width = 500, height = 500;
    final int xc = width/2, yc = height/2;
    int r = 100; // radius from center of panel to center of outer circle
    int diam = 50; // outer circle diamter
    double inc = Math.PI/360; 
    double theta = 0;
    JFrame f = new JFrame();

    public static void main(String[] args) {
        SwingUtilities.invokeLater(()-> new DrawCircle().start());
    }

    public void start() {
        f.add(this);
        f.pack();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
        Timer timer = new Timer(0, (ae)-> { theta += inc; repaint();});
        timer.setDelay(20);
        timer.start();
    }
    @Override
    public Dimension getPreferredSize() {
        return new Dimension(width, height);
    }
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
        g2d.rotate(theta, xc, yc);
        g2d.setColor(Color.red);
        g2d.fillOval(xc-3, yc-3, 6, 6); // center of panel
        g2d.setStroke(new BasicStroke(3));
        g2d.setColor(Color.blue);
//      g2d.drawLine(xc,yc, xc+r, yc); // tether between centers 
        g2d.drawOval(xc+r-diam/2, yc-diam/2, diam,diam);
    }

}

Updated Answer

OK, there are two fundamental things you are doing wrong.

  • You are not adding super.paintComponent(g) as the first statement in your paintComponent method.
  • you are overridding paint(Graphics g) (whether you intend to or not) because it is also public and is inherited by JPanel from JComponent. Do not use paint() as it isn't necessary here (maybe in some applications but I have never had the need). Move all the code in there to paintComponent

You should also move the timer code outside of paintComponent. It only needs to be defined once and is run in the background. It will continue to call your ActionListener class until you stop it.

Now, after doing the above you might ask "why is only one circle showing up when I draw?" The obvious answer is that I only wanted to draw one. But why doesn't it get repeated each time?

Because super.paintComponent(g) clears the JPanel each time paintComponent is invoked just as it is supposed to. So if you want to draw multiple circles (or other things), you need to put them in a list and draw them from within paintComponent. Since all events including painting and your timer are run in series on a single thread (the Event Dispatch Thread) it is important to keep processing to a minimum. So when possible, most calculations should be done outside of that thread. EDT processing should be as simple and as quick as possible.

My first answer showed a circle orbiting a point. But perhaps that is not what you want. You may just want to position circles uniformly around the center from a fixed distance. I have provided two methods of doing that.

  • Using rotate as before. Imo, it is the simplest. The angle is fixed and each time rotate is called, it is additive. So just call that method nCircle times and draw the circle. And remember to compute the x and y coordinates to correct for the radius.
  • Using trig to calculate the location of the circles. This uses a list of angles based on the nCircles. For each iteration, x and y are computed based on the radius and current angle.

Both of these are shown in different colors to demonstrate their overlay.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class DrawCircle2 extends JPanel {
    int width = 500, height = 500;
    final int xc = width / 2, yc = height / 2;
    int r = 100; // radius from center of panel to center of outer circle
    int diam = 50; // outer circle diamter
    int nCircles = 8; // number of circles
    
    double theta = Math.PI*2/nCircles;
    
    List<Point> coords1 = fillForTrig();
    JFrame f = new JFrame();
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new DrawCircle2().start());
    }
    
    private List<Point> fillForTrig() {
        List<Point> list = new ArrayList<>();
        for (int i = 0; i < nCircles; i++) {
            int x = xc+(int)(r*Math.sin(i*theta));
            int y = yc+(int)(r*Math.cos(i*theta));
            list.add(new Point(x,y));
        }
        return list;
    }
    
        
    public void start() {
        
        f.add(this);
        f.pack();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    
    public Dimension getPreferredSize() {
        return new Dimension(width, height);
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        drawUsingRotate(g2d);
//      drawUsingTrig(g2d);
    }

    private void drawUsingRotate(Graphics2D g2d) {
        g2d = (Graphics2D)g2d.create();
        g2d.setColor(Color.RED);
        //fixed positions for radius as context is rotated
        int xx =  0;
        int yy  = r;
        for (int i = 0; i < nCircles;i++) {
             g2d.rotate(theta, xc, yc);
             // xx-diam/2 just places the center of the orbiting circle at
             // the proper radius from the center of the panel. Same for yy.
             g2d.drawOval(xc + xx - diam / 2, yc + yy - diam / 2, diam,
                        diam);
            
         }
         g2d.dispose();
    }
    private void drawUsingTrig(Graphics2D g2d) {
        g2d = (Graphics2D)g2d.create();
        g2d.setColor(Color.BLUE);
        for (Point p : coords1) {
            int x = (int)p.getX();
            int y = (int)p.getY();
            g2d.drawOval(x-diam/2, y-diam/2, diam, diam);
        }
        g2d.dispose();
    }
}

Solution 2:[2]

Math.sin and Math.cos methods expect a value in radians. You can convert degrees to radians by multiplying with Math.PI/180

Therefore, try changing Math.cos(i * 360 / n) and Math.sin(i * 360 / n) to Math.cos((i * 360 / n)*(Math.PI/180)) and Math.sin((i * 360 / n)*(Math.PI/180)).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 MWiesner