Search code examples
javaswinggraphgraphicsgraphics2d

Problem when drawing arrows. How do I set the coordinates correctly?


    Polygon arrow = new Polygon();
    arrow.addPoint(0, 5);
    arrow.addPoint(-5, -5);
    arrow.addPoint(5, -5);

    AffineTransform tx = new AffineTransform();
    double angle = Math.atan2(y2 - y1, x2 - x1);
    tx.translate(x2 + Config.VERTEX_RADIUS / 2, y2 + Config.VERTEX_RADIUS / 2);
    tx.rotate((angle - Math.PI / 2));

    graphics.setColor(Color.BLACK);
    graphics.draw(new Line2D.Double(x1, y1, x2, y2));
    graphics.setTransform(tx);
    graphics.fill(arrow);

screen: http://imglink.ru/show-image.php?id=254ac13712ad825a1a1b99181170f747

EDIT So, I changed the code on the advice of MadProgrammer, but there is such an unpleasant shift of the arrow. screen -> http://www.imglink.ru/show-image.php?id=f1d6d3dfdb52972d731690e031af7d71

private void drawEdgeLine(Graphics2D graphics, double x1, double y1, double x2, double y2) {

    graphics.setColor(Color.BLACK);
    graphics.draw(new Line2D.Double(x1, y1, x2, y2));

    ArrowHead arrowHead = new ArrowHead();
    double length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
    double t1 = Config.VERTEX_RADIUS / length;
    double t2 = (length - Config.VERTEX_RADIUS) / length;
    double arrowX, arrowY;
    if (t1 > t2) {
        arrowX = x1 + t1 * (x2 - x1);
        arrowY = y1 + t1 * (y2 - y1);
    } else {
        arrowX = x1 + t2 * (x2 - x1);
        arrowY = y1 + t2 * (y2 - y1);
    }

    double angle = Math.atan2(y2 - y1, x2 - x1);
    /*double angleDegrees = Math.toDegrees(angle + Math.PI);
    System.out.println(angleDegrees);
    if (angleDegrees > 90 && angleDegrees < 270) {
        arrowY += Config.ARROW_HEAD_SIZE / 2;
    } else {
        arrowX -= Config.ARROW_HEAD_SIZE / 2;
    }*/

    AffineTransform transform = AffineTransform.getTranslateInstance(arrowX, arrowY);
    transform.rotate(angle + Math.PI / 2);
    arrowHead.transform(transform);
    graphics.draw(arrowHead);
}

}

class ArrowHead extends Path2D.Double {

public ArrowHead() {
    double size = Config.ARROW_HEAD_SIZE;
    moveTo(0, size);
    lineTo(size / 2, 0);
    lineTo(size, size);
}

}


Solution

  • So, rotation will occur at the top/left corner (0x0 or "origin point"). For a component, this has already been translated to the top/left corner for your convince.

    You also need to take into consideration that transformations are compounding, so you need to make sure you undo them when you're done - especially because the Graphics context in Swing is shared ... and that could really screw things up.

    So. The basic idea is:

    • Translate the content to where you want the shape drawn
    • Rotate the context around the centre of the shape, this will "generally" produce a desired result
    • Reset the transformation

    The following example demonstrates the basic idea and uses a Timer to spin the shape

    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.awt.geom.Path2D;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.Timer;
    
    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 ArrowHead extends Path2D.Double {
    
            public ArrowHead() {
                int size = 10;
                moveTo(0, size);
                lineTo(size / 2, 0);
                lineTo(size, size);
                closePath();
            }
    
        }
    
        public class TestPane extends JPanel {
    
            private ArrowHead arrow = new ArrowHead();
            private double angleOfAttack = 0;
    
            public TestPane() {
                Timer timer = new Timer(5, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        angleOfAttack += 0.5;
                        repaint();
                    }
                });
                timer.start();
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(200, 200);
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                // Snap shot the current context
                Graphics2D g2d = (Graphics2D) g.create();
    
                // Translate to somewhere you want the arrow painted ...
                g2d.translate(50, 50);
                // The bounding box of the arrow...
                Rectangle bounds = arrow.getBounds();
    
                // A guide, showing where the arrow is been painted without the rotation
                g2d.setColor(Color.RED);
                g2d.draw(bounds);
    
                // Rotate abount the middle of the arrow...
                g2d.rotate(Math.toRadians(angleOfAttack), bounds.width / 2, bounds.height / 2);
                g2d.setColor(Color.BLACK);
                // Draw the arrow
                g2d.fill(arrow);
    
                // Discard the transformations so we don't effect anyone else
                g2d.dispose();
            }
    
        }
    
    }
    

    You might also want to take a look at Connect two circles with a line, which is a more complicated example of this concept, but which seems to be more inline with what you are trying to achieve