Search code examples
javaswinggraphicspaintcomponent

How can I paint a custom circular JComponent without an "invisible box" around it?


I'm writing a JApplet that displays a diagram with numbers in circles and lines connecting the circles. I've created a class that extends JComponent to act as the circles. I overrode the paintComponent() method to draw a circle with the numbers inside, and I placed these circles on my applet, on which I drew the lines in the paint() method.

However, if the lines hit the circle at an angle, they cut out early, before the circle, at the square that outlines the entire JComponent, even though the background should be transparent. this forms an "invisible box" around the circle.

I've prepared a smaller program to demonstrate the issue: Example of lines stopping at the "invisible square"

And here is the code for that example:

import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;

import javax.swing.*;

public class Circle extends JApplet {
    private static final long serialVersionUID = 1L;

    myCircle myC = new myCircle(10, 215, 215); // custom circle
    Container c;

    public void init() {
        setSize(500, 500);
        c = getContentPane();
        c.setLayout(null);
        c.setBackground(Color.lightGray);
        c.add(myC);
    }

    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2d = (Graphics2D)g;
        g2d.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setColor(Color.black);
        g2d.setStroke(new BasicStroke(2f, BasicStroke.CAP_BUTT,     BasicStroke.JOIN_MITER));

        //draw lines
        g2d.draw(new Line2D.Double(0f, 0f, 500f, 500f));
        g2d.draw(new Line2D.Double(0f, 500f, 500f, 0f));
        g2d.draw(new Line2D.Double(0f, 250f, 500f, 250f));
        g2d.draw(new Line2D.Double(250f, 0f, 250f, 500f));
        g2d.draw(new Line2D.Double(150f, 0f, 350f, 500f));
        myC.repaint(); // put the circle on top
    }

    public class myCircle extends JComponent {
        private static final long serialVersionUID = 1L;

        int number; // number to display

        public myCircle(int num, int x, int y) {
            this.number = num;
            this.setLocation(x, y);
            this.setSize(75, 75);
        }

        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D)g;
            g2d.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(
                    RenderingHints.KEY_TEXT_ANTIALIASING,
                    RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            g2d.setStroke(new BasicStroke(2f, BasicStroke.CAP_BUTT,     BasicStroke.JOIN_MITER));
            g2d.setColor(Color.white);
            g2d.fill(new Ellipse2D.Float(1f, 1f, 70f, 70f));
            g2d.setColor(Color.black);
            g2d.draw(new Ellipse2D.Float(1f, 1f, 70f, 70f));
            g2d.drawString(Integer.toString(number), 15f, 20f);
        }
    }
}

Solution

  • I wouldn't use a component to represent the circle shape alone. Instead, just paint it all on one JPanel (I'm using a JFrame instead of an applet):

    enter image description here

    public class Circle extends JPanel {
    
        int number = 10;
        float size = 500f;
        float rad = 70f;
        float stringLocX = 15f, stringLocy = 20f;
    
        public void paintComponent(Graphics g) {
    
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;
            g2d.setColor(Color.LIGHT_GRAY);
            g2d.fillRect(0, 0, getWidth(), getHeight());
    
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            g2d.setStroke(new BasicStroke(2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));
    
            g2d.setColor(Color.BLACK);
            g2d.draw(new Line2D.Double(0f, 0f, size, size));
            g2d.draw(new Line2D.Double(0f, size, size, 0f));
            g2d.draw(new Line2D.Double(0f, size / 2, size, size / 2));
            g2d.draw(new Line2D.Double(size / 2, 0f, size / 2, size));
            g2d.draw(new Line2D.Double(150f, 0f, 350f, 500f));
    
            Ellipse2D.Float circle = new Ellipse2D.Float((size - rad) / 2, (size - rad) / 2, rad, rad);
            g2d.setColor(Color.WHITE);
            g2d.fill(circle);
            g2d.setColor(Color.BLACK);
            g2d.draw(circle);
            g2d.drawString(Integer.toString(number), (size - rad) / 2 + stringLocX,
                            (size - rad) / 2 + stringLocy);
        }
    
        @Override
        public Dimension getPreferredSize() {
    
            return new Dimension((int) size, (int) size);
        }
    
        public static void main(String[] args) {
    
            SwingUtilities.invokeLater(() -> {
                JFrame frame = new JFrame();
                frame.add(new Circle());
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.pack();
                frame.setVisible(true);
            });
        }
    }
    

    Depending on how many circles you have and what data they must contain, you can just keep a list of those numbers, then iterate over the list in painComponent and draw them.

    Notes:

    • Don't use null layout.
    • Better to keep the hard coded numbers as fields so you can change them more easily, possibly make them final.