Skip to content
Advertisement

Center shape defined in Cartesian coordinates in JPanel

I have a shape (triangle for example) defined in Cartesian coordinates and I’d like to translate the shape to the center of a JPanel. I’ve been able to translate about the JPanel’s 0,0 orientation, but I can’t figure out how to center based on the JPanel’s width and height. I’d like to be able to center any shape defined by a bounding rectangle.

int tPanelWidth = 200;
int tPanelHeight = 200;

// A triangle in lower right quadrant
Path2D tPath = new Path2D.Double();
tPath.moveTo(25,-25);
tPath.lineTo(50,-75);
tPath.lineTo(75,-25);
tPath.closePath();

// Determine translation distance to 0,0
double tX = Math.abs(Math.min(0,tPath.getBounds().getX()));
double tY = Math.abs(Math.min(0,tPath.getBounds().getY()));

AffineTransform tAt = new AffineTransform();
tAt.translate(tX,tY);
tPath.transform(tAt);

This puts the triangle upside down (not desired) in the JPanel’s upper left corner, somewhat expected. So in order to translate the triangle to the center, it should be around half of the panels width and height? I then change the above to:

// tAt.translate(tX,tY);
tAt.translate(tX+(tPanelWidth/2),tY+(tPanelHeight/2));

Now the triangle is in the lower right of the JPanel, so half of the width and height is too much. I then randomly tried /4 and it is close, but not exact and not mathematically correct or at least doesn’t make sense. It also doesn’t work when the JPanel isn’t square.

Advertisement

Answer

So, problems…

  • Some coordinates are negative (you’ve compensated for those already)
  • The shape’s origin is not 0x0, so it’s offset

So, in addition to the “negative coordinates” transformation, you also need to apply a “offset” translation, so that the origin point of the shape becomes 0x0, something like…

int midX = getWidth() / 2;
int midY = getHeight() / 2;

Rectangle pathBounds = tPath.getBounds();
// Because the x/y position may not be 0/0
int xDelta = pathBounds.x * -1;
int yDelta = pathBounds.y * -1;

int xPos = (midX - (pathBounds.width / 2)) + xDelta;
int yPos = (midY - (pathBounds.height / 2)) + yDelta;

Which can produce something like…

enter image description here

(nb: I also rendered the shape’s bounds as a secondary check)

Now, obviously, this is just focused on centring around the shape bounds and you may need to do some additional compensations, but this should give you a starting point.

Runnable example…

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.geom.AffineTransform;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;

public final class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private Path2D tPath;

        public TestPane() {
            // A triangle in lower right quadrant
            tPath = new Path2D.Double();
            tPath.moveTo(25, -25);
            tPath.lineTo(50, -75);
            tPath.lineTo(75, -25);
            tPath.closePath();

            // Determine translation distance to 0,0
            double tX = Math.abs(Math.min(0, tPath.getBounds().getX()));
            double tY = Math.abs(Math.min(0, tPath.getBounds().getY()));

            AffineTransform tAt = new AffineTransform();
            tAt.translate(tX, tY);
            tPath.transform(tAt);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            int midX = getWidth() / 2;
            int midY = getHeight() / 2;

            g2d.setColor(Color.RED);
            g2d.drawLine(0, midY, getWidth(), midY);
            g2d.drawLine(midX, 0, midX, getHeight());

            g2d.setColor(Color.BLACK);

            Rectangle pathBounds = tPath.getBounds();
            // Because the x/y position may not be 0/0
            int xDelta = pathBounds.x * -1;
            int yDelta = pathBounds.y * -1;

            int xPos = (midX - (pathBounds.width / 2)) + xDelta;
            int yPos = (midY - (pathBounds.height / 2)) + yDelta;

            g2d.translate(xPos, yPos);
            g2d.draw(tPath);
            g2d.draw(tPath.getBounds());
            g2d.dispose();
        }

    }
}

nb: I choose to modify the shape within the context of the paint path, you could apply the transformations to the shape itself, but that would depend on your requirements, ie: does anything else depend on the original shapes position or not

Advertisement