Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java draw circle and lines on Swing

I'm trying to draw a circle with a random center inside a big bigger circular surface. (I'm actually trying to simulate a human and his eyesight inside a room!) I need to draw a random line (call it line1) passing through its center which will intersect with the surface. line1 does not necessarily pass the center of circular surface. I also need to draw two lines forming 60 degree, facing on one side of line1. Can anyone help me with that?

I created an example of what I need to draw.

enter image description here

import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Point;
import java.util.Random;

import javax.swing.JFrame;

public class ShapeTest extends JFrame{
    int width=500;
    int height=500;

     public ShapeTest(){
          setSize(width,height);
          setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          setResizable(false);
          setLocationRelativeTo(null);
          setVisible(true);
     }

     public static void main(String a[]){
         new ShapeTest();
     }

     public void paint(Graphics g){
         // Circular Surface
         drawCircleByCenter(g, width/2, height/2, width/2);
         Random r = new Random();
         Point center = new Point();
         center.x=r.nextInt(width/2);
         center.y=r.nextInt(width/2);
         drawCircleByCenter(g, center.x, center.y, width/15);
     }

     void drawCircleByCenter(Graphics g, int x, int y, int radius){
         //g.setColor(Color.LIGHT_GRAY);
         g.drawOval(x-radius, y-radius, 2*radius, 2*radius);
     }
}
like image 581
Tina J Avatar asked Mar 22 '14 04:03

Tina J


People also ask

How do you draw a circle in Java Swing?

Draw a Circle Using the drawOval() Function in Java Now we call the drawOval() function and pass four arguments. The first two arguments are the x and y coordinates of the circle, while the last two arguments specify the width and the height of the circle to be drawn.

How do I make a circle in a Jframe?

You can draw a circle and oval using the Graphics. drawOval(int x, int y, int width, int height) method. This function performs both functions. 'x' and 'y' are the starting point on the screen, and 'width' and 'height' are the parameters to set the width and height of the oval or circle.

What does draw () do in Java?

Description. Called directly after setup(), the draw() function continuously executes the lines of code contained inside its block until the program is stopped or noLoop() is called.


2 Answers

Start by changing your method to draw a circle based on its center and radius to a method which returns a Ellipse2D object representing the circle. This will allow us to do some clipping and other things with the shape besides just draw it.

Setting the clip to be the shape of your large circle prevents stray marks from being made where you don't want them (think "color inside the lines"). This is important because when we draw the circles and lines inside the big circle, some of them will be too big and would otherwise mark outside the bounds of the big circle.

Once we set the clip, we use the method Line2D getVector(Point2D, double, length) with an origin at the center of the large circle, a random angle and a random length (capped to keep the small blue circle inside the big circle). Think of this a random polar coordinate with the center of the large circle as the origin. The end point of this vector is used to mark the center of the small circle.

Using the center of the small circle as a starting point, we can generate two vectors in opposite directions (just negate the length of one to get it going the other direction) by using a random direction angle. We use a length equal to the diameter of the big circle to make certain that the lines will always go all the way up to the edge of the big circle (but not past, thanks to our clip).

We simply add 60 and 120 degrees to the angle of our blue dashed line and draw two green lines calculating the vectors the same way we did for the two blue dashed lines, except we don't need to create ones with negated lengths. We can also add a normal vector in for good measure simply by adding 90 degrees to the angle of the blue dashed line.

Lastly, we pick some random polar coordinates (just like we did for the small blue circle) to represent some people, and using the intersection of the people with the areas created by the various lines, we can see where they are at and draw them up with color coded values.

Now that we have all the people, we eliminate the clip and draw the big circle and voila!

Check out Draw a line at a specific angle in Java for details on how I calculated the vectors for the lines.

But enough talk, here's the code:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Random;

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


public class ShapeTest extends JFrame {

    private static final long serialVersionUID = 1L;
    private int width = 500;
    private int height = 500;
    private int padding = 50;
    private BufferedImage graphicsContext;
    private JPanel contentPanel = new JPanel();
    private JLabel contextRender;
    private Stroke dashedStroke = new BasicStroke(3.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 2f, new float[] {3f, 3f}, 0f);
    private Stroke solidStroke = new BasicStroke(3.0f);
    private RenderingHints antialiasing;
    private Random random = new Random();

    public static void main(String[] args) {
        //you should always use the SwingUtilities.invodeLater() method
        //to perform actions on swing elements to make certain everything
        //is happening on the correct swing thread
        Runnable swingStarter = new Runnable()
        {
            @Override
            public void run(){
                new ShapeTest();
            }
        };

        SwingUtilities.invokeLater(swingStarter);
    }

    public ShapeTest(){
        antialiasing = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphicsContext = new BufferedImage(width + (2 * padding), width + (2 * padding), BufferedImage.TYPE_INT_RGB);
        contextRender = new JLabel(new ImageIcon(graphicsContext));

        contentPanel.add(contextRender);
        contentPanel.setSize(width + padding * 2, height + padding * 2);

        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setResizable(false);
        this.setContentPane(contentPanel);
        //take advantage of auto-sizing the window based on the size of its contents
        this.pack();
        this.setLocationRelativeTo(null);
        this.paint();
        setVisible(true);
    }

    public void paint() {

        Graphics2D g2d = graphicsContext.createGraphics();
        g2d.setRenderingHints(antialiasing);

        //Set up the font to print on the circles
        Font font = g2d.getFont();
        font = font.deriveFont(Font.BOLD, 14f);
        g2d.setFont(font);

        FontMetrics fontMetrics = g2d.getFontMetrics();

        //clear the background
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, graphicsContext.getWidth(), graphicsContext.getHeight());

        //set up the large circle
        Point2D largeCircleCenter = new Point2D.Double((double)width / 2 + padding, (double)height / 2 + padding);
        double largeCircleRadius = (double)width / 2;
        Ellipse2D largeCircle = getCircleByCenter(largeCircleCenter, largeCircleRadius);

        //here we build the small circle
        Point2D smallCircleCenter = new Point2D.Double();
        double smallCircleRadius = 15;
        //we need to make certain it is confined inside the larger circle
        //so we choose the following values carefully

        //we want to go a random direction from the circle, so chose an
        //angle randomly in any direction
        double smallCenterVectorAngle = random.nextDouble() * 360.0d;
        //and we want to be a random distance from the center of the large circle, but
        //we limit the distance based on the radius of the small circle to prevent it
        //from appearing outside the large circle
        double smallCenterVectorLength = random.nextDouble() * (largeCircleRadius - smallCircleRadius);
        Line2D vectorToSmallCenter = getVector(largeCircleCenter, smallCenterVectorAngle, smallCenterVectorLength);
        //the resulting end point of the vector is a random distance from the center of the large circle
        //in a random direction, and guaranteed to not place the small circle outside the large
        smallCircleCenter.setLocation(vectorToSmallCenter.getP2());
        Ellipse2D smallCircle = getCircleByCenter(smallCircleCenter, smallCircleRadius);

        //before we draw any of the circles or lines, set the clip to the large circle
        //to prevent drawing outside our boundaries
        g2d.setClip(largeCircle);



        //chose a random angle for the line through the center of the small circle
        double angle = random.nextDouble() * 360.0d;
        //we create two lines that start at the center and go out at the angle in
        //opposite directions. We use 2*largeCircleRadius to make certain they
        //will be large enough to fill the circle, and the clip we set prevent stray
        //marks outside the big circle
        Line2D centerLine1 = getVector(smallCircleCenter, angle, largeCircleRadius * 2);
        Line2D centerLine2 = getVector(smallCircleCenter, angle, -largeCircleRadius * 2);

        //now we just add 20 and 120 to our angle for the center-line, start at the center
        //and again, use largeCircleRadius*2 to make certain the lines are big enough
        Line2D sightVector1 = getVector(smallCircleCenter, angle + 60, largeCircleRadius * 2);
        Line2D sightVector2 = getVector(smallCircleCenter, angle + 120, largeCircleRadius * 2);



        Path2D visible = new Path2D.Double();
        visible.moveTo(sightVector1.getX2(), sightVector1.getY2());
        visible.lineTo(smallCircleCenter.getX(), smallCircleCenter.getY());
        visible.lineTo(sightVector2.getX2(), sightVector2.getY2());
        visible.closePath();

        Path2D greenSide = new Path2D.Double();
        greenSide.moveTo(centerLine1.getX2(), centerLine1.getY2());
        greenSide.lineTo(smallCircleCenter.getX(), smallCircleCenter.getY());
        greenSide.lineTo(centerLine2.getX2(), centerLine2.getY2());
        greenSide.lineTo(sightVector1.getX2(), sightVector1.getY2());
        greenSide.closePath();

        int personCount = 5;
        Area visibleArea = new Area(visible);
        visibleArea.intersect(new Area(largeCircle));

        Area greenSideArea = new Area(greenSide);
        greenSideArea.intersect(new Area(largeCircle));

        //we create a list of the people in the circle to
        //prevent overlap
        ArrayList<Shape> people = new ArrayList<Shape>();
        people.add(smallCircle);

        int i = 0;
        personLoop: while (i < personCount){
            double personCenterVectorAngle = random.nextDouble() * 360.0d;
            double personCenterVectorLength = random.nextDouble() * (largeCircleRadius - smallCircleRadius);
            Line2D vectorToPersonCenter = getVector(largeCircleCenter, personCenterVectorAngle, personCenterVectorLength);
            Point2D personCircleCenter = vectorToPersonCenter.getP2();
            Ellipse2D personCircle = getCircleByCenter(personCircleCenter, smallCircleRadius);

            //this little loop lets us skip a person if they have overlap
            //with another person, since people don't generally overlap
            Area personArea = new Area(personCircle);
            for (Shape person : people)
            {
                Area overlapArea = new Area(person);
                overlapArea.intersect(personArea);
                //this means that we have found a conflicting
                //person, so should skip them
                if (!overlapArea.isEmpty()){
                    continue personLoop;
                }
            }
            people.add(personCircle);

            personArea.intersect(visibleArea);

            Area greenSideAreaTest = new Area(personCircle);
            greenSideAreaTest.intersect(greenSideArea);
            if (personArea.isEmpty()){
                if (greenSideAreaTest.isEmpty()){
                    g2d.setColor(Color.orange);
                    System.out.println("Person " + i + " is behind the blue line");
                }
                else {
                    System.out.println("Person " + i + " is in front of the blue line");
                    g2d.setColor(Color.cyan);
                }
            }
            else
            {
                System.out.println("Person " + i + " is between the green lines");
                g2d.setColor(Color.magenta);
            }

            //alternatively to circles intersecting the area of interest, we can check whether the center
            //is in the area of interest which may make more intuitive sense visually
//          if (visibleArea.contains(personCircleCenter)){
//              System.out.println("Person " + i + " is between the green lines");
//              g2d.setColor(Color.magenta);
//          }
//          else {
//              if (greenSideArea.contains(personCircleCenter)) {
//                  System.out.println("Person " + i + " is in front of the blue line");
//                  g2d.setColor(Color.cyan);
//              }
//              else{
//                  g2d.setColor(Color.orange);
//                  System.out.println("Person " + i + " is behind the blue line");
//              }
//          }

            g2d.fill(personCircle);
            g2d.setColor(Color.black);
            String itemString = "" + i;
            Rectangle2D itemStringBounds = fontMetrics.getStringBounds(itemString, g2d);
            double textX = personCircleCenter.getX() - (itemStringBounds.getWidth() / 2);
            double textY = personCircleCenter.getY() + (itemStringBounds.getHeight()/ 2);
            g2d.drawString("" + i, (float)textX, (float)textY);
            i++;
        }



        //fill the small circle with blue
        g2d.setColor(Color.BLUE);
        g2d.fill(smallCircle);

        //draw the two center lines lines
        g2d.setStroke(dashedStroke);
        g2d.draw(centerLine1);
        g2d.draw(centerLine2);

        //create and draw the black offset vector
        Line2D normalVector = getVector(smallCircleCenter, angle + 90, largeCircleRadius * 2);
        g2d.setColor(Color.black);
        g2d.draw(normalVector);

        //draw the offset vectors
        g2d.setColor(new Color(0, 200, 0));
        g2d.draw(sightVector1);
        g2d.draw(sightVector2);


        //we save the big circle for last, to cover up any stray marks under the stroke
        //of its perimeter. We also set the clip back to null to prevent the large circle
        //itselft from accidentally getting clipped
        g2d.setClip(null);
        g2d.setStroke(solidStroke);
        g2d.setColor(Color.BLACK);
        g2d.draw(largeCircle);

        g2d.dispose();
        //force the container for the context to re-paint itself
        contextRender.repaint();

    }

    private static Line2D getVector(Point2D start, double degrees, double length){
        //we just multiply the unit vector in the direction we want by the length
        //we want to get a vector of correct direction and magnitute
        double endX = start.getX() + (length * Math.sin(Math.PI * degrees/ 180.0d));
        double endY = start.getY() + (length * Math.cos(Math.PI * degrees/ 180.0d));
        Point2D end = new Point2D.Double(endX, endY);
        Line2D vector = new Line2D.Double(start, end);
        return vector;
    }

    private static Ellipse2D getCircleByCenter(Point2D center, double radius)
    {
        Ellipse2D.Double myCircle = new Ellipse2D.Double(center.getX() - radius, center.getY() - radius, 2 * radius, 2 * radius);
        return myCircle;
    }

}
like image 97
Sam Avatar answered Oct 14 '22 00:10

Sam


The logic of the geometry turned out to be more tricky than I'd presumed, but this is what I think you are after.

enter image description hereenter image description here

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.*;

class HumanEyesightLines {

    int rad = 150;
    int radSmall = 15;
    int pad = 10;
    JPanel gui = new JPanel(new BorderLayout());
    BufferedImage img = new BufferedImage(
            2 * (rad + pad),
            2 * (rad + pad),
            BufferedImage.TYPE_INT_RGB);
    Timer timer;
    JLabel imgDisplay;
    Random rnd = new Random();
    RenderingHints rh = new RenderingHints(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);

    HumanEyesightLines() {
        imgDisplay = new JLabel(new ImageIcon(img));
        gui.add(imgDisplay);
        File f = new File(System.getProperty("user.home"));
        final File f0 = new File("HumanEyesiteLines");
        f0.mkdirs();
        try {
            Desktop.getDesktop().open(f0);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        ActionListener animationListener = new ActionListener() {

            int ii = 0;

            @Override
            public void actionPerformed(ActionEvent e) {
                paintImage();
                ii++;
                if (ii < 100) {
                    System.out.println(ii);
                    File f1 = new File(f0, "eg" + ii + ".png");
                    try {
                        ImageIO.write(img, "png", f1);
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        };
        timer = new Timer(100, animationListener);
        paintImage();
    }
    float[] dash = {3f, 3f};
    float phase = 0f;

    private final void paintImage() {
        Graphics2D g = img.createGraphics();
        g.setRenderingHints(rh);
        g.setStroke(new BasicStroke(2f));

        // fill the BG
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, 2 * (rad + pad), 2 * (rad + pad));

        // draw the big circle
        Point center = new Point(rad + pad, rad + pad);
        Shape bigCircle = new Ellipse2D.Double(pad, pad, 2 * rad, 2 * rad);
        g.setColor(Color.MAGENTA.darker());
        g.fill(bigCircle);

        // set the clip to that of the big circle
        g.setClip(bigCircle);

        // draw the small circle
        int xOff = rnd.nextInt(rad) - rad / 2;
        int yOff = rnd.nextInt(rad) - rad / 2;
        int x = center.x - xOff;
        int y = center.y - yOff;
        Shape smallCircle = new Ellipse2D.Double(
                x - radSmall, y - radSmall,
                2 * radSmall, 2 * radSmall);
        g.setColor(Color.YELLOW);
        g.fill(smallCircle);
        g.setColor(Color.ORANGE);
        g.draw(smallCircle);

        g.setStroke(new BasicStroke(
                1.5f,
                BasicStroke.CAP_BUTT,
                BasicStroke.JOIN_ROUND,
                2f,
                dash,
                phase));

        // I don't know what the rule is for where the blue line goes, so
        // will use the top left corner of the image as a 2nd anchor point.
        int x0 = 0;
        int y0 = 0;
        double grad = (double) (y - y0) / (double) (x - x0);

        // now calculate the RHS point from y = mx + b 
        // where b = 0 and m is the gradient
        int x1 = 2 * (pad + rad);
        int y1 = (int) (grad * x1);
        Line2D.Double line1 = new Line2D.Double(x0, y0, x1, y1);
        g.setColor(Color.BLUE);
        g.draw(line1);

        //find the perpendicular gradient.
        double perpGrad = -1d / grad;
        double perpTheta = Math.atan(perpGrad);
        // angle from perp
        double diffTheta = Math.PI / 6d;

        g.setColor(Color.GREEN);
        double viewLine1Theta = perpTheta + diffTheta;
        Line2D.Double viewLine1 = getLine(x, y, viewLine1Theta);
        double viewLine2Theta = perpTheta - diffTheta;
        Line2D.Double viewLine2 = getLine(x, y, viewLine2Theta);
        g.draw(viewLine1);
        g.draw(viewLine2);

        g.setColor(Color.BLACK);
        Line2D.Double viewPerp = getLine(x, y, perpTheta);
        g.draw(viewPerp);

        g.setColor(Color.RED);
        g.draw(bigCircle);

        g.dispose();
        imgDisplay.repaint();
    }

    /**
     * Returns a Line2D starting at the point x1,y1 at angle theta.
     */
    private final Line2D.Double getLine(double x1, double y1, double theta) {
        double m;
        double b;
        double x2;
        double y2;
        if (theta < (-Math.PI / 2d)) {
            System.out.println("CHANGE IT! " + theta);
            m = Math.tan(theta);
            b = y1 - (m * x1);
            x2 = 0;
            y2 = (m * x2) + b;
        } else {
            m = Math.tan(theta);
            b = y1 - (m * x1);
            x2 = 2 * (rad + pad);
            y2 = (m * x2) + b;
        }
        /*
         * System.out.println("Perp theta: " + theta); System.out.println("Line
         * grad: " + m); System.out.println("Line off: " + b);
         * System.out.println("x1,y1: " + x1 + "," + y1);
         * System.out.println("x2,y2: " + x2 + "," + y2);
         *
         */

        return new Line2D.Double(x1, y1, x2, y2);
    }

    public JComponent getGui() {
        return gui;
    }

    public void start() {
        timer.start();
    }

    public void stop() {
        timer.stop();
    }

    public static void main(String[] args) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                HumanEyesightLines hel = new HumanEyesightLines();

                hel.start();
                JOptionPane.showMessageDialog(null, hel.getGui());
                hel.stop();
            }
        };
        // Swing GUIs should be created and updated on the EDT
        // http://docs.oracle.com/javase/tutorial/uiswing/concurrency
        SwingUtilities.invokeLater(r);
    }
}
like image 43
Andrew Thompson Avatar answered Oct 13 '22 23:10

Andrew Thompson