How to draw a directed arrow line in Java?
Asked Answered
L

7

29

I want to draw a directed arrow line through Java.

At present, I am using java.awt.Line2D.Double class to draw a line

g2.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); // g2 is an instance of Graphics2D
g2.draw(new Line2D.Double(x1,y1,x2,y2));

But only the line appears and no directed arrow appears. BasicStroke.Join_BEVEL is used to draw a directed arrow. It is applied when two line segments meet.

The line I am drawing meets the border of a rectangle but no directed arrow is drawn. Only a simple line is drawn.

Is there anything I am missing?

Lynxeyed answered 8/1, 2010 at 12:50 Comment(0)
F
32

The bevel is drawn between segments in a polyline if they are at certain angles. It has no bearing if you are drawing a line which happens to be drawn near some other pixels which are of a certain colour - once you've drawn the rectangle, the Graphics object doesn't know about the rectangle, it (in effect) only holds the pixels. ( or rather the image or OS window holds the pixels ).

To draw a simple arrow, draw a line for the stalk as you're doing, then a polyline for the vee. Nicer looking nicer arrows have curved sides and are filled.

You probably don't want to use bevel for the arrow head, as bevels are a flat; instead use the mitre option:

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;

public class BevelArrows
{
    public static void main ( String...args )
    {
        SwingUtilities.invokeLater ( new Runnable () {
            BevelArrows arrows = new BevelArrows();

            @Override
            public void run () {
                JFrame frame = new JFrame ( "Bevel Arrows" );

                frame.add ( new JPanel() {
                    public void paintComponent ( Graphics g ) {
                        arrows.draw ( ( Graphics2D ) g, getWidth(), getHeight() );
                    }
                }
                , BorderLayout.CENTER );

                frame.setSize ( 800, 400 );
                frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
                frame.setVisible ( true );
            }
        } );
    }

    interface Arrow {
        void draw ( Graphics2D g );
    }

    Arrow[] arrows = { new LineArrow(), new CurvedArrow() };

    void draw ( Graphics2D g, int width, int height )
    {
        g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );

        g.setColor ( Color.WHITE );
        g.fillRect ( 0, 0, width, height );

        for ( Arrow arrow : arrows ) {
            g.setColor ( Color.ORANGE );
            g.fillRect ( 350, 20, 20, 280 );

            g.setStroke ( new BasicStroke ( 20.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL ) );
            g.translate ( 0, 60 );
            arrow.draw ( g );

            g.setStroke ( new BasicStroke ( 20.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER ) );
            g.translate ( 0, 100 );
            arrow.draw ( g );

            g.setStroke ( new BasicStroke ( 20.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND ) );
            g.translate ( 0, 100 );
            arrow.draw ( g );

            g.translate ( 400, -260 );
        }
    }

    static class LineArrow  implements Arrow
    {
        public void draw ( Graphics2D g )
        {
            // where the control point for the intersection of the V needs calculating
            // by projecting where the ends meet

            float arrowRatio = 0.5f;
            float arrowLength = 80.0f;

            BasicStroke stroke = ( BasicStroke ) g.getStroke();

            float endX = 350.0f;

            float veeX;

            switch ( stroke.getLineJoin() ) {
                case BasicStroke.JOIN_BEVEL:
                    // IIRC, bevel varies system to system, this is approximate
                    veeX = endX - stroke.getLineWidth() * 0.25f;
                    break;
                default:
                case BasicStroke.JOIN_MITER:
                    veeX = endX - stroke.getLineWidth() * 0.5f / arrowRatio;
                    break;
                case BasicStroke.JOIN_ROUND:
                    veeX = endX - stroke.getLineWidth() * 0.5f;
                    break;
            }

            // vee
            Path2D.Float path = new Path2D.Float();

            path.moveTo ( veeX - arrowLength, -arrowRatio*arrowLength );
            path.lineTo ( veeX, 0.0f );
            path.lineTo ( veeX - arrowLength, arrowRatio*arrowLength );

            g.setColor ( Color.BLUE );
            g.draw ( path );

            // stem for exposition only
            g.setColor ( Color.YELLOW );
            g.draw ( new Line2D.Float ( 50.0f, 0.0f, veeX, 0.0f ) );

            // in practice, move stem back a bit as rounding errors
            // can make it poke through the sides of the Vee
            g.setColor ( Color.RED );
            g.draw ( new Line2D.Float ( 50.0f, 0.0f, veeX - stroke.getLineWidth() * 0.25f, 0.0f ) );
        }
    }

    static class CurvedArrow  implements Arrow
    {
        // to draw a nice curved arrow, fill a V shape rather than stroking it with lines
        public void draw ( Graphics2D g )
        {
            // as we're filling rather than stroking, control point is at the apex,

            float arrowRatio = 0.5f;
            float arrowLength = 80.0f;

            BasicStroke stroke = ( BasicStroke ) g.getStroke();

            float endX = 350.0f;

            float veeX = endX - stroke.getLineWidth() * 0.5f / arrowRatio;

            // vee
            Path2D.Float path = new Path2D.Float();

            float waisting = 0.5f;

            float waistX = endX - arrowLength * 0.5f;
            float waistY = arrowRatio * arrowLength * 0.5f * waisting;
            float arrowWidth = arrowRatio * arrowLength;

            path.moveTo ( veeX - arrowLength, -arrowWidth );
            path.quadTo ( waistX, -waistY, endX, 0.0f );
            path.quadTo ( waistX, waistY, veeX - arrowLength, arrowWidth );

            // end of arrow is pinched in
            path.lineTo ( veeX - arrowLength * 0.75f, 0.0f );
            path.lineTo ( veeX - arrowLength, -arrowWidth );

            g.setColor ( Color.BLUE );
            g.fill ( path );

            // move stem back a bit
            g.setColor ( Color.RED );
            g.draw ( new Line2D.Float ( 50.0f, 0.0f, veeX - arrowLength * 0.5f, 0.0f ) );
        }
    }
}
Fabozzi answered 8/1, 2010 at 12:56 Comment(6)
I don't see how you use this.Andvari
Shouldn't the draw method take x1, y1, x2, y2 parameters?Saddleback
@CardinalSystem depends whether you want an arrow or a 'arrow drawer'. But yes, the object should be parameterised. It used to be that SO answers were expected to give you the principles rather than people expecting drop- in components.Fabozzi
This is way too complicated. Downvoting. My first downvote ever.Haematozoon
@DmitryKamenetsky the less complicated answers answer a different question 'can you give me a code snippet which draws an arrow'; the question being asked also wanted to know how the JOIN_BEVEL option affected it, so obviously demonstrating that in a stand-alone program is going to be more complicated than a partial answer to a different questionFabozzi
@PeteKirkham ok fair enough. I missed the JOIN_BEVEL part. Sorry. Anyway I ended up using phibao37 code for my application.Haematozoon
L
37

Although Pete's post is awesomely comprehensive, I'm using this method to draw a very simple line with a little triangle at its end.

// create an AffineTransform 
// and a triangle centered on (0,0) and pointing downward
// somewhere outside Swing's paint loop
AffineTransform tx = new AffineTransform();
Line2D.Double line = new Line2D.Double(0,0,100,100);

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

// [...]
private void drawArrowHead(Graphics2D g2d) {  
    tx.setToIdentity();
    double angle = Math.atan2(line.y2-line.y1, line.x2-line.x1);
    tx.translate(line.x2, line.y2);
    tx.rotate((angle-Math.PI/2d));  

    Graphics2D g = (Graphics2D) g2d.create();
    g.setTransform(tx);   
    g.fill(arrowHead);
    g.dispose();
}
Lectureship answered 22/6, 2010 at 16:9 Comment(1)
This worked great for me. Many thanks for the more simpler solution!Rumen
F
32

The bevel is drawn between segments in a polyline if they are at certain angles. It has no bearing if you are drawing a line which happens to be drawn near some other pixels which are of a certain colour - once you've drawn the rectangle, the Graphics object doesn't know about the rectangle, it (in effect) only holds the pixels. ( or rather the image or OS window holds the pixels ).

To draw a simple arrow, draw a line for the stalk as you're doing, then a polyline for the vee. Nicer looking nicer arrows have curved sides and are filled.

You probably don't want to use bevel for the arrow head, as bevels are a flat; instead use the mitre option:

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;

public class BevelArrows
{
    public static void main ( String...args )
    {
        SwingUtilities.invokeLater ( new Runnable () {
            BevelArrows arrows = new BevelArrows();

            @Override
            public void run () {
                JFrame frame = new JFrame ( "Bevel Arrows" );

                frame.add ( new JPanel() {
                    public void paintComponent ( Graphics g ) {
                        arrows.draw ( ( Graphics2D ) g, getWidth(), getHeight() );
                    }
                }
                , BorderLayout.CENTER );

                frame.setSize ( 800, 400 );
                frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
                frame.setVisible ( true );
            }
        } );
    }

    interface Arrow {
        void draw ( Graphics2D g );
    }

    Arrow[] arrows = { new LineArrow(), new CurvedArrow() };

    void draw ( Graphics2D g, int width, int height )
    {
        g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );

        g.setColor ( Color.WHITE );
        g.fillRect ( 0, 0, width, height );

        for ( Arrow arrow : arrows ) {
            g.setColor ( Color.ORANGE );
            g.fillRect ( 350, 20, 20, 280 );

            g.setStroke ( new BasicStroke ( 20.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL ) );
            g.translate ( 0, 60 );
            arrow.draw ( g );

            g.setStroke ( new BasicStroke ( 20.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER ) );
            g.translate ( 0, 100 );
            arrow.draw ( g );

            g.setStroke ( new BasicStroke ( 20.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND ) );
            g.translate ( 0, 100 );
            arrow.draw ( g );

            g.translate ( 400, -260 );
        }
    }

    static class LineArrow  implements Arrow
    {
        public void draw ( Graphics2D g )
        {
            // where the control point for the intersection of the V needs calculating
            // by projecting where the ends meet

            float arrowRatio = 0.5f;
            float arrowLength = 80.0f;

            BasicStroke stroke = ( BasicStroke ) g.getStroke();

            float endX = 350.0f;

            float veeX;

            switch ( stroke.getLineJoin() ) {
                case BasicStroke.JOIN_BEVEL:
                    // IIRC, bevel varies system to system, this is approximate
                    veeX = endX - stroke.getLineWidth() * 0.25f;
                    break;
                default:
                case BasicStroke.JOIN_MITER:
                    veeX = endX - stroke.getLineWidth() * 0.5f / arrowRatio;
                    break;
                case BasicStroke.JOIN_ROUND:
                    veeX = endX - stroke.getLineWidth() * 0.5f;
                    break;
            }

            // vee
            Path2D.Float path = new Path2D.Float();

            path.moveTo ( veeX - arrowLength, -arrowRatio*arrowLength );
            path.lineTo ( veeX, 0.0f );
            path.lineTo ( veeX - arrowLength, arrowRatio*arrowLength );

            g.setColor ( Color.BLUE );
            g.draw ( path );

            // stem for exposition only
            g.setColor ( Color.YELLOW );
            g.draw ( new Line2D.Float ( 50.0f, 0.0f, veeX, 0.0f ) );

            // in practice, move stem back a bit as rounding errors
            // can make it poke through the sides of the Vee
            g.setColor ( Color.RED );
            g.draw ( new Line2D.Float ( 50.0f, 0.0f, veeX - stroke.getLineWidth() * 0.25f, 0.0f ) );
        }
    }

    static class CurvedArrow  implements Arrow
    {
        // to draw a nice curved arrow, fill a V shape rather than stroking it with lines
        public void draw ( Graphics2D g )
        {
            // as we're filling rather than stroking, control point is at the apex,

            float arrowRatio = 0.5f;
            float arrowLength = 80.0f;

            BasicStroke stroke = ( BasicStroke ) g.getStroke();

            float endX = 350.0f;

            float veeX = endX - stroke.getLineWidth() * 0.5f / arrowRatio;

            // vee
            Path2D.Float path = new Path2D.Float();

            float waisting = 0.5f;

            float waistX = endX - arrowLength * 0.5f;
            float waistY = arrowRatio * arrowLength * 0.5f * waisting;
            float arrowWidth = arrowRatio * arrowLength;

            path.moveTo ( veeX - arrowLength, -arrowWidth );
            path.quadTo ( waistX, -waistY, endX, 0.0f );
            path.quadTo ( waistX, waistY, veeX - arrowLength, arrowWidth );

            // end of arrow is pinched in
            path.lineTo ( veeX - arrowLength * 0.75f, 0.0f );
            path.lineTo ( veeX - arrowLength, -arrowWidth );

            g.setColor ( Color.BLUE );
            g.fill ( path );

            // move stem back a bit
            g.setColor ( Color.RED );
            g.draw ( new Line2D.Float ( 50.0f, 0.0f, veeX - arrowLength * 0.5f, 0.0f ) );
        }
    }
}
Fabozzi answered 8/1, 2010 at 12:56 Comment(6)
I don't see how you use this.Andvari
Shouldn't the draw method take x1, y1, x2, y2 parameters?Saddleback
@CardinalSystem depends whether you want an arrow or a 'arrow drawer'. But yes, the object should be parameterised. It used to be that SO answers were expected to give you the principles rather than people expecting drop- in components.Fabozzi
This is way too complicated. Downvoting. My first downvote ever.Haematozoon
@DmitryKamenetsky the less complicated answers answer a different question 'can you give me a code snippet which draws an arrow'; the question being asked also wanted to know how the JOIN_BEVEL option affected it, so obviously demonstrating that in a stand-alone program is going to be more complicated than a partial answer to a different questionFabozzi
@PeteKirkham ok fair enough. I missed the JOIN_BEVEL part. Sorry. Anyway I ended up using phibao37 code for my application.Haematozoon
R
22

This is my approach, absolute Math only:

/**
 * Draw an arrow line between two points.
 * @param g the graphics component.
 * @param x1 x-position of first point.
 * @param y1 y-position of first point.
 * @param x2 x-position of second point.
 * @param y2 y-position of second point.
 * @param d  the width of the arrow.
 * @param h  the height of the arrow.
 */
private void drawArrowLine(Graphics g, int x1, int y1, int x2, int y2, int d, int h) {
    int dx = x2 - x1, dy = y2 - y1;
    double D = Math.sqrt(dx*dx + dy*dy);
    double xm = D - d, xn = xm, ym = h, yn = -h, x;
    double sin = dy / D, cos = dx / D;

    x = xm*cos - ym*sin + x1;
    ym = xm*sin + ym*cos + y1;
    xm = x;

    x = xn*cos - yn*sin + x1;
    yn = xn*sin + yn*cos + y1;
    xn = x;

    int[] xpoints = {x2, (int) xm, (int) xn};
    int[] ypoints = {y2, (int) ym, (int) yn};

    g.drawLine(x1, y1, x2, y2);
    g.fillPolygon(xpoints, ypoints, 3);
}
Riel answered 13/12, 2014 at 16:59 Comment(0)
B
15

In the past, I've written the following method to create an an arrow shape, which I can then fill with ((Graphics2D) g).fill(shape);

public static Shape createArrowShape(Point fromPt, Point toPt) {
    Polygon arrowPolygon = new Polygon();
    arrowPolygon.addPoint(-6,1);
    arrowPolygon.addPoint(3,1);
    arrowPolygon.addPoint(3,3);
    arrowPolygon.addPoint(6,0);
    arrowPolygon.addPoint(3,-3);
    arrowPolygon.addPoint(3,-1);
    arrowPolygon.addPoint(-6,-1);


    Point midPoint = midpoint(fromPt, toPt);

    double rotate = Math.atan2(toPt.y - fromPt.y, toPt.x - fromPt.x);

    AffineTransform transform = new AffineTransform();
    transform.translate(midPoint.x, midPoint.y);
    double ptDistance = fromPt.distance(toPt);
    double scale = ptDistance / 12.0; // 12 because it's the length of the arrow polygon.
    transform.scale(scale, scale);
    transform.rotate(rotate);

    return transform.createTransformedShape(arrowPolygon);
}

private static Point midpoint(Point p1, Point p2) {
    return new Point((int)((p1.x + p2.x)/2.0), 
                     (int)((p1.y + p2.y)/2.0));
}
Balmoral answered 26/3, 2011 at 23:59 Comment(0)
C
3

Just in case if you want an non-programmatic arrow (I.e. for text purpose) in Fast way, you can use <html> code for making arrow as text, just put your HTML code inside .setText() method for a component. I have java 1.8u202 it works fine.

myLabel.setText("<html><body>&#8592;</body></html>");

this code &#8592; is for left-pointing arrow

other arrow directions HTML code from This Website

Countersign answered 19/2, 2019 at 18:3 Comment(1)
neat idea! and you can style them etc:<div style="float:left;font-size: 60px;font-weight: 600;margin:20px;">&#8594;</div> <div style="float:left;font-size: 60px;font-weight: 600;margin:20px;">&#8592;</div>Pulsation
R
1
    void drawArrow(Graphics g1, double x1, double y1, double x2, double y2 ) {
    Graphics2D ga = (Graphics2D) g1.create();
    ga.drawLine((int)x1, (int)y1, (int)x2, (int)y2);

    double l = Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2));//  line length
    double d = l / 10; // arrowhead distance from end of line. you can use your own value.
    
    double newX = ((x2 + (((x1 - x2) / (l) * d)))); // new x of arrowhead position on the line with d distance from end of the line.
    double newY = ((y2 + (((y1 - y2) / (l) * d)))); // new y of arrowhead position on the line with d distance from end of the line.

    double dx = x2 - x1, dy = y2 - y1;
    double angle = (Math.atan2(dy, dx)); //get angle (Radians) between ours line and x vectors line. (counter clockwise)
    angle = (-1) * Math.toDegrees(angle);// cconvert to degree and reverse it to round clock for better understand what we need to do.
    if (angle < 0) {
        angle = 360 + angle; // convert negative degrees to posative degree
    }
    angle = (-1) * angle; // convert to counter clockwise mode
    angle = Math.toRadians(angle);//  convert Degree to Radians
    AffineTransform at = new AffineTransform();
    at.translate(newX, newY);// transport cursor to draw arrowhead position.
    at.rotate(angle);
    ga.transform(at);

    Polygon arrowHead = new Polygon();
    arrowHead.addPoint(5, 0);
    arrowHead.addPoint(-5, 5);
    arrowHead.addPoint(-2, -0);
    arrowHead.addPoint(-5, -5);
    ga.fill(arrowHead);
    ga.drawPolygon(arrowHead);
}

enter image description here

Rickets answered 21/3, 2022 at 17:16 Comment(0)
C
1

This is the code from Vicente Reig's great answer, simplified a little and packaged as a nice utility class.

import java.awt.*;
import java.awt.geom.AffineTransform;

public class Arrow
{
    private final Polygon arrowHead = new Polygon ();

    /**
     * Create an arrow.
     *
     * @see https://mcmap.net/q/478080/-how-to-draw-a-directed-arrow-line-in-java
     *
     * @param size Size of the arrow to draw.
     */
    public Arrow (int size)
    {
        // create a triangle centered on (0,0) and pointing right
        arrowHead.addPoint (size, 0);
        arrowHead.addPoint (-size, -size);
        arrowHead.addPoint (-size, size);
        //arrowHead.addPoint (0, 0); // Another style
    }

    /**
     * Draw the arrow at the end of a line segment. Drawing the line segment must be done by the caller, using whatever
     * stroke and color is required.
     */
    public void drawArrowHead (Graphics2D g, double x0, double y0, double x1, double y1)
    {
        final AffineTransform tx = AffineTransform.getTranslateInstance (x1, y1);
        tx.rotate (Math.atan2 (y1 - y0, x1 - x0));
        g.fill (tx.createTransformedShape (arrowHead));
    }
}
Certiorari answered 10/4, 2022 at 8:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.