I'm trying to draw some rotatable arrows with Swing 2d, there are some sample code online, so I copied and combined them into one app, but there is something wrong with each of the 3 methods : 1st one doesn't rotate from it's center, the other 2 don't look correctly in the arrow head, can someone show me how to fix them ?
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.event.*;
public class Arrow_Test extends JPanel implements ChangeListener {
Path2D.Double arrow = createArrow();
double theta = 0;
public void stateChanged(ChangeEvent e) {
int value = ((JSlider) e.getSource()).getValue();
theta = Math.toRadians(value);
repaint();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(6));
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int cx = getWidth() / 2;
int cy = getHeight() / 2;
AffineTransform at = AffineTransform.getTranslateInstance(cx, cy);
at.rotate(theta);
at.scale(2.0, 2.0);
Shape shape = at.createTransformedShape(arrow);
g2.setPaint(Color.blue);
g2.draw(shape);
GeneralPath a = drawArrow(20, 20, 30, 20, 50, 13);
AffineTransform at1 = AffineTransform.getTranslateInstance(cx, cy);
at1.rotate(theta);
at1.scale(2.0, 2.0);
Shape shape1 = at1.createTransformedShape(a);
g2.setPaint(Color.blue);
g2.draw(shape1);
drawArrow(100, 100, 50, 0, g2);
}
private Path2D.Double createArrow() {
int length = 80;
int barb = 15;
double angle = Math.toRadians(20);
Path2D.Double path = new Path2D.Double();
path.moveTo(-length / 2, 0);
path.lineTo(length / 2, 0);
double x = length / 2 - barb * Math.cos(angle);
double y = barb * Math.sin(angle);
path.lineTo(x, y);
x = length / 2 - barb * Math.cos(-angle);
y = barb * Math.sin(-angle);
path.moveTo(length / 2, 0);
path.lineTo(x, y);
return path;
}
GeneralPath drawArrow(int x1, int y1, int x2, int y2, double length,
double width) {
double x, y;
length = 50;
width = 5;
Point2D start = new Point2D.Double(x1, y1);
Point2D end = new Point2D.Double(x2, y2);
Point2D base = new Point2D.Double();
Point2D back1 = new Point2D.Double();
Point2D back2 = new Point2D.Double();
length = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
// Compute normalized line vector
x = (x2 - x1) / length;
y = (y2 - y1) / length;
// Compute points for arrow head
base.setLocation(x2 - x * length, y2 - y * length);
back1.setLocation(base.getX() - width * y, base.getY() + width * x);
back2.setLocation(base.getX() + length * y, base.getY() - width * x);
Line2D.Double l1 = new Line2D.Double(start, end);
Line2D.Double l2 = new Line2D.Double(end, back2);
Line2D.Double l3 = new Line2D.Double(end, back1);
GeneralPath c = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
c.append(l1, true);
c.append(l2, true);
c.append(l3, true);
return c;
}
private void drawArrow(int x1, int y1, int x2, int y2, Graphics2D g2d) {
int x[] = { 0, 36, 0 };
int y[] = { -10, 0, 10 };
g2d.rotate(theta);
g2d.drawLine(x1 - 20, y1, x1 + 20, y1);
// will move the orgin
g2d.translate(x1, y1);
double angle = findLineAngle(x1 - 20, y1, x1 + 20, y1);
System.out.println("angle is===>" + angle);
g2d.rotate(angle);
g2d.fillPolygon(new Polygon(x, y, 3));
// /will restore orgin
g2d.translate(-x2, -y2);
g2d.rotate(-angle);
}
private double findLineAngle(int x1, int y1, int x2, int y2) {
if ((x2 - x1) == 0)
return Math.PI / 2;
return Math.atan((y2 - y1) / (x2 - x1));
}
private JSlider getSlider() {
JSlider slider = new JSlider(-180, 180, 0);
slider.addChangeListener(this);
return slider;
}
public static void main(String[] args) {
Arrow_Test test = new Arrow_Test();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(test);
f.add(test.getSlider(), "Last");
f.setSize(400, 400);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
I really don't want to get into "why", as your code is hard enough to read.
When rotating an object, you should specify a anchor (x/y) around which the rotation should take place. By default, this is the
0x0
position of the current context.Why your "path" based arrows look...interesting, could have to do with the way they are created, but I didn't really play around with them.
The other thing you need to be careful is, transformations are compounding, this is a good and bad thing, you just need to be careful with them ;)
Lets start with a basic shape...
Okay, nothing impressive, you could add width/height parameters to make the arrow appear the way you want, but this gets a basic start. I prefer to use
Shape
based objects, they are just simpler to work with then the oldPolygon
style API.The
Arrow
is basically three lines which meet at the middle of the vertical and the end of the horizontal. You might get a better result if the arrow head was a single line, but I'll leave that for you to play withNext, we need to position and rotate the object (
arrow
is an instance ofArrow
BTW)We apply a translation first, this makes it so that the
Graphics
context's0x0
position is now thex/y
position we specify. This makes it MUCH easier to calculate the anchor position around which the arrow should rotate...And a runnable example to bind it all together