矢印の描画自体は、矢印を構成する各頂点を線で結んでいくだけなので、単純です。 (頂点の計算はちょっと面倒かもしれませんが)
とはいえ、何本の矢印を描画する必要がある場合、都度描画するのは面倒なので、始点(p0)と終点(p1)だけ指定したら、矢印を描画するShapeクラスをつくりました。
矢印を描画するコードをArrowShapeクラスに集約しているので、以下のように、始点(p0)と終点(p1)を指定して、描画します。
ArrowShape a=new ArrowShape(p0,p1);
g2.fill(a);
実体は、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);
}
}
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);
}
}