Friday, February 15, 2008 / Java2D

矢印を簡単に描画するシェイプの自作(Java2D)

arrowShape

矢印の描画自体は、矢印を構成する各頂点を線で結んでいくだけなので、単純です。 (頂点の計算はちょっと面倒かもしれませんが)

とはいえ、何本の矢印を描画する必要がある場合、都度描画するのは面倒なので、始点(p0)と終点(p1)だけ指定したら、矢印を描画するShapeクラスをつくりました。

使い方

矢印を描画するコードをArrowShapeクラスに集約しているので、以下のように、始点(p0)と終点(p1)を指定して、描画します。

ArrowShape a=new ArrowShape(p0,p1);
g2.fill(a);

code

ArrowShape

実体は、GeneralPath そのものですが、GeneralPathはfinalクラスであり、サブクラスをつくれない(extendsできない)ため、 AbstractShape.java というShapeインタフェースを実装したスーパークラスを用意しています。

ArrowShape.java

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

public class ArrowShape extends AbstractShape {
    private Point p0;
    private Point p1;
    public ArrowShape(Point p0,Point p1){
        this.p0=p0;
        this.p1=p1;
    }

    private GeneralPath shape;
    protected Shape getMyShape(){
        if(shape==null){
            shape=new GeneralPath();

            ArrowPointCalcUtil util=new ArrowPointCalcUtil( this.p0,this.p1 );

            Point2D p2=util.getPoint2();
            Point2D p3=util.getPoint3();

            Line2D line0=new Line2D.Float(this.p0,this.p1);
            Line2D line1=new Line2D.Float(this.p1,p2);
            Line2D line2=new Line2D.Float(this.p1,p3);

            shape.append(line0,false);
            shape.append(line1,false);
            shape.append(line2,false);
        }
        return shape;
    }
}

矢印の各頂点の計算は、 ArrowPointCalcUtil.java で行っています。

import java.awt.Point;
import java.awt.geom.Point2D;

public class ArrowPointCalcUtil {
    private Point2D p0;
    private Point2D p1;

    public ArrowPointCalcUtil(Point p0,Point p1){
        this(new Point2D.Float(p0.x,p0.y),new Point2D.Float(p1.x,p1.y));
    }

    public ArrowPointCalcUtil(Point2D p0,Point2D p1){
        this.p0=p0;
        this.p1=p1;

        double diffX=this.p0.getX()-this.p1.getX();
        double diffY=this.p0.getY()-this.p1.getY();
        double length=Math.sqrt( diffX*diffX+diffY*diffY );

        setLength( (int)(length*0.1f) );
    }

    private int angle=45;
    public void setAngle(int angle){
        this.angle=angle;
    }
    public int getAngle(){
        return this.angle;
    }

    private int length;
    public void setLength(int length){
        this.length=length;
    }
    public int getLength(){
        return this.length;
    }

    public Point2D.Double getPoint2(){
        return getPoint2or3(getAngle());
    }
    public Point2D.Double getPoint3(){
        return getPoint2or3(getAngle()*(-1));
    }
    private Point2D.Double getPoint2or3(int hosei){
        if( p0.getX()<p1.getX() ){
            double radian=calcRadian(p0,p1);
            double kakudo=radian/(Math.PI/180);
            kakudo=kakudo+180;
            return getArrowTerminalPoint(p1,kakudo+hosei,getLength());
        }
        if( p0.getX()>p1.getX() ){
            double radian=calcRadian(p0,p1);
            double kakudo=radian/(Math.PI/180);
            return getArrowTerminalPoint(p1,kakudo+hosei,getLength());
        }
        if( p0.getX()==p1.getX() ){
            double radian=0;
            if( p0.getY()>p1.getY() ){
                radian=90*Math.PI/180;
            }
            if( p0.getY()<p1.getY() ){
                radian=270*Math.PI/180;
            }
            double kakudo=radian/(Math.PI/180);

            return getArrowTerminalPoint(p1,kakudo+hosei,getLength());
        }

        return null;
    }

    private static Point2D.Double getArrowTerminalPoint(Point2D topPoint,double kakudo,int length){
        double radian=kakudo*Math.PI/180;

        double x=Math.cos( radian )*length;
        double y=Math.sin( radian )*length;

        return new Point2D.Double((float)(x+topPoint.getX()),(float)(y+topPoint.getY()));
    }

    /**
     * 2点を通る直線と水平線との角度を求める(ただし、返すのはラジアン)
     */
    private static double calcRadian(Point2D startPoint ,Point2D endPoint){
        Point2D.Double p0=new Point2D.Double(startPoint.getX(),startPoint.getY());
        Point2D.Double p1=new Point2D.Double(endPoint.getX(),endPoint.getY());

        p1.setLocation(p1.getX()-p0.getX(),p1.getY()-p0.getY());

        if( p1.getX()==0 ){
            double radian=90*Math.PI/180;
            return radian;
        }

        double v=p1.getY()/p1.getX();
        double radian=Math.atan( v );

        return radian;
    }
}

スーパークラス AbstractShape.java

import java.awt.*;
import java.awt.geom.*;
import java.awt.font.*;

abstract class AbstractShape implements Shape {

    abstract protected Shape getMyShape();

    public boolean contains(double x, double y) {
        return getMyShape().contains(x,y);
    }

    public boolean contains(double x, double y, double w, double h) {
        return getMyShape().contains(x,y,w,h);
    }

    public boolean intersects(double x, double y, double w, double h) {
        return getMyShape().intersects(x,y,w,h);
    }

    public Rectangle getBounds() {
        return getMyShape().getBounds();
    }

    public boolean contains(Point2D p) {
        return getMyShape().contains(p);
    }

    public Rectangle2D getBounds2D() {
        return getMyShape().getBounds2D();
    }

    public boolean contains(Rectangle2D r) {
        return getMyShape().contains(r);
    }

    public boolean intersects(Rectangle2D r) {
        return intersects(r);
    }

    public PathIterator getPathIterator(AffineTransform at) {
        return getMyShape().getPathIterator(at); 
    }

    public PathIterator getPathIterator(AffineTransform at, double flatness) {
        return getMyShape().getPathIterator(at,flatness);
    }
}

TestFrame

ArrowShapeのテスト用のコード TestFrame.java 。

import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;

import javax.swing.JComponent;
import javax.swing.JFrame;

public class TestFrame extends JFrame {
    public TestFrame(){
        super();
        getContentPane().add(new ArrowCanvas(),BorderLayout.CENTER);
    }

    public static void main(String[] args){
        TestFrame f=new TestFrame();
        f.setSize(160,160);
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.setVisible(true);
    }
}

class ArrowCanvas extends JComponent {
    private ArrowShape as0,as1,as2;
    public ArrowCanvas(){
        super();

        Point a0=new Point(10,10);
        Point b0=new Point(100,100);
        as0=new ArrowShape(a0,b0);

        Point a1=new Point(10+30,10);
        Point b1=new Point(100+30,100);
        as1=new ArrowShape(a1,b1);

        Point a2=new Point(100,10);
        Point b2=new Point(10,100);
        as2=new ArrowShape(a2,b2);
    }

    protected void paintComponent(Graphics g) {
        Graphics2D g2=(Graphics2D)g;
        g2.draw(as0);
        g2.draw(as1);
        g2.draw(as2);
    }
}