• 検索結果がありません。

のようにする 上の例では GeneralPath を new するときに コンストラクタに何も指定していないが 直線を表す Line, 四角形を表す Rectangle などを引数に与えてもよい 矢印を作成するメソッドの引数矢印を表す GeneralPath を生成するために getarrowpat

N/A
N/A
Protected

Academic year: 2021

シェア "のようにする 上の例では GeneralPath を new するときに コンストラクタに何も指定していないが 直線を表す Line, 四角形を表す Rectangle などを引数に与えてもよい 矢印を作成するメソッドの引数矢印を表す GeneralPath を生成するために getarrowpat"

Copied!
7
0
0

読み込み中.... (全文を見る)

全文

(1)

-1-

【手書認識・グラフ描画

Step3】認識した数式をもとに関数グラフを描画する

<数式の構造解析> 一般に、”1+3”などと文字列で数式をうけとっても、コンピュータはそれをそのままで式とは認識しな い。あくまで、文字列は文字の並びであり、そこに数学的な意味は含まれない。数式として計算するた めには、プログラムによって数式の構造を解析し、コンピュータが計算できる形式に変換する必要があ る。 今回のプログラムでは、通常の記法でかかれた数式の文字列を、逆ポーランド記法(Reverse Polish Notation, RPN)という数式の書き方に変換して計算している。これは、数字などを演算子の後に記述す る書き方で、コンピュータとの親和性が高い。 本演習では、数式解析してRPN に変換し、計算をする部分はソースごと提供するので、使い方のみ説 明する。たとえば x2+ 1 x + 3 という式を計算したい場合、関数に渡すために数式を次のように文字列で表現する。 String str = “x^2+1/(x+3)”; この式で、x=5 の値を求めたい場合は

Term term = RPNUtil.RPN(str); double y = term.func(5.0);

とすると、y に解 25.125 が代入される。ただし、Term クラスと RPNUtil クラスは Java が提供するも

のではなく、rpn パッケージに含まれる独自のクラスである。

<GeneralPath について>

関数をグラフ描画するにあたって、矢印を描画する必要がある。今回は、一般的な直線や曲線の集合 をあらわすGeneralPath というクラスを使って描画する。GeneralPath は線の集合であり、それらの線 が必ずしもつながっている必要はない。一般に、点(x1, y1)から点(x2, y2)に直線を引くには

GeneralPath path = new GeneralPath(); path.moveTo(x1, y1); // x1, y1 に移動

path.lineTo(x2, y2); // x2, y2 まで直線をひく g2d.draw(path); // 描画

(2)

-2- のようにする。

上の例では、GeneralPath を new するときに、コンストラクタに何も指定していないが、直線を表す

Line, 四角形を表す Rectangle などを引数に与えてもよい。 ※矢印を作成するメソッドの引数

矢印を表すGeneralPath を生成するために、getArrowPath(point1, point2, barb, degree)というメソ ッドを定義して使っている。引数の意味は、次の図にあるとおりである。

※Point2D.Float クラス

getArrowPath メソッドの引数 point1, point2 は ava.awt.geom パッケージの Point2D.Float クラスで ある。このクラスは、Point2D というクラスの中に宣言されたサブクラスで、他に Point2D.Double クラ スもある。宣言方法は次のようになっている。

public abstract class Point2D implements Cloneable {

public static class Float extends Point2D implements Serializable { public float x;

public float y; ・・・(中略)・・・ }

public static class Double extends Point2D implements Serializable { public double x; public double y; ・・・(中略)・・・ } ・・・(中略)・・・ } このように入れ子になってるクラスのことを内部クラス、またはインナークラスと呼ぶ。今まで使って いたjava.awt パッケージの Point クラスは、x 座標と y 座標が int 型であったが、Point2D.Float クラス はfloat 型、Point2D.Double クラスは double 型である。

barb

point1

point2 degree

(3)

-3- <作成手順> 1.rpn.zip を適当な場所で解凍し、rpn フォルダをコピーしてプロジェクトの src フォルダ (D:¥workspace¥Graph¥src)の中に貼り付ける。 2.パッケージ・エクスプローラでGraph プロジェクトを選択し、右クリック→リフレッシュ rpn パッケージがあることを確認 3.GraphPanel.java のソースを編集 4.GraphFrame.java でインポート文を2つ追加し、グラフ描画メソッドを編集 import javax.swing.JOptionPane; import rpn.RPNException;

public class GraphFrame extends javax.swing.JFrame { ・・・(中略)・・・

private void graphButtonActionPerformed(ActionEvent evt) { try { // 範囲取得 graphPanel.setxMin(Double.parseDouble(xMinField.getText())); graphPanel.setxMax(Double.parseDouble(xMaxField.getText())); graphPanel.setyMin(Double.parseDouble(yMinField.getText())); graphPanel.setyMax(Double.parseDouble(yMaxField.getText())); // 数式設定 graphPanel.setFormula(paintPanel.getFormula()); graphPanel.repaint(); } catch (RPNException e) { JOptionPane.showMessageDialog(this, "正しい数式を設定してください\n"+e.getMessage()); } catch (NumberFormatException fe) {

JOptionPane.showMessageDialog(this, "範囲を正しく入力してください"); } } } 5.実行し、適当な式を 1 文字ずつ入力・認識し、グラフ描画ボタンを押す。グラフが正しく描画され ることを確認する。

(4)

GraphPanel.java -4- package graph; 1 2 import java.awt.Color; 3 import java.awt.Graphics; 4 import java.awt.Graphics2D; 5 import java.awt.Point; 6 import java.awt.font.TextAttribute; 7 import java.awt.geom.GeneralPath; 8 import java.awt.geom.Line2D; 9 import java.awt.geom.Point2D; 10 import java.text.AttributedCharacterIterator; 11 import java.text.AttributedString; 12 import java.text.CharacterIterator; 13 14 import javax.swing.JPanel; 15 16 import rpn.RPNException; 17 import rpn.RPNUtil; 18 import rpn.Term; 19 20

public class GraphPanel extends JPanel { 21

22

private Term term; // 数式オブジェクト 23

private double xMin=-10, xMax=10; // x の表示範囲(関数の座標) 24

private double yMin=-10, yMax=10; // y の表示範囲(関数の座標) 25

private int divNum = 200; // グラフ線の刻み幅 26 27 /** 28 * コンストラクタ(何もしない) 29 */ 30 public GraphPanel() { 31 } 32 33 /** 34 * 描画メソッド 35 */ 36 @Override 37

public void paintComponent(Graphics g) { 38 super.paintComponent(g); 39 Graphics2D g2d = (Graphics2D)g; 40 // 白色背景 41 g2d.setColor(Color.WHITE); 42

g2d.fillRect(0, 0, getWidth(), getHeight()); 43 // グラフ描画 44 if (term != null) { 45 // 座標軸 --- 46 // 黒色設定 47 g2d.setColor(Color.BLACK); 48 // 原点を取得 49

Point origin = getGraphicalCoordinate(0, 0); 50 // x 軸の矢印の始点と終点を取得 51 /* getArrowPath で使うので Point2D.Float にする。 52 * これは座標を float で保持する点のクラス */ 53

Point2D.Float pf1 = new Point2D.Float(0, origin.y); 54

Point2D.Float pf2 = new Point2D.Float(getWidth(), origin.y); 55

// x 軸の矢印を取得 56

GeneralPath arrow = getArrowPath(pf1, pf2, 10, 20); 57 // x 軸の矢印を描画 58 g2d.draw(arrow); 59 // y 軸の矢印の始点と終点を取得 60

pf1 = new Point2D.Float(origin.x, getHeight()); 61 pf2 = new Point2D.Float(origin.x, 0); 62 // y 軸の矢印を取得 63 arrow = getArrowPath(pf1, pf2, 10, 20); 64

(5)

GraphPanel.java -5- // y 軸の矢印を描画 65 g2d.draw(arrow); 66 67 // 関数線 --- 68 // 赤色設定 69 g2d.setColor(Color.RED); 70 double x1,y1,x2,y2; 71 Point p1, p2; 72 // divNum 個の刻みで関数線を描画 73

for (int i=0; i<divNum; i++) { 74 // i 番目の点を計算 75 // ※数式オブジェクト term.func(x 座標)で y 座標を取得可能 76 x1 = xMin + (xMax-xMin)*i/(double)divNum; 77 y1 = term.func(x1); 78 // i+1 番目の点を計算 79 x2 = xMin + (xMax-xMin)*(i+1)/(double)divNum; 80 y2 = term.func(x2); 81 // 2 つの点の描画すべき点を取得 82 p1 = getGraphicalCoordinate(x1, y1); 83 p2 = getGraphicalCoordinate(x2, y2); 84 // パネルの中だったら描画する 85

if (p1.y >= 0 && p1.y <= getHeight() 86

&& p2.y >= 0 && p2.y <= getHeight()) 87

// 直線を描画 88

g2d.drawLine(p1.x, p1.y, p2.x, p2.y); 89 } 90 } 91 } 92 93 /** 94 * 関数の座標系からパネルの座標を取得 95 * @param x 96 * @param y 97 * @return パネル上の点 98 */ 99

private Point getGraphicalCoordinate(double x, double y) { 100

return new Point((int)(getWidth()*(x-xMin)/(xMax-xMin)), 101 (int)(getHeight() - getHeight()*(y-yMin)/(yMax-yMin))); 102 } 103 104 /** 105 * パスによって矢印線を表すオブジェクトを取得<br> 106 * point2 に矢印をつける 107 * @param point1 始点 108 * @param point2 終点 109 * @param barb 矢印部分の長さ 110

* @param degree 矢印部分の角度(degree) 111

* @return 矢印を表すパス 112

*/ 113

private GeneralPath getArrowPath(Point2D.Float point1, Point2D.Float point2, int barb, int 114

degree) { 115

// 角度をラジアンにする 116

double phi = Math.toRadians(degree); 117

// 始点と終点によって決まる線分の角度を求める 118

double theta = Math.atan2(point2.y - point1.y, point2.x - point1.x); 119

// 始点から終点へのパスを取得 120

GeneralPath path = new GeneralPath(new Line2D.Float(point1, point2)); 121

// 矢印部分の先端の座標を取得 122

double x = point2.x + barb*Math.cos(theta+Math.PI-phi); 123

double y = point2.y + barb*Math.sin(theta+Math.PI-phi); 124 // 矢印部分の先端へ移動 125 path.moveTo((float)x, (float)y); 126 // 終点へ向かってラインを引く 127 path.lineTo((float)point2.x, (float)point2.y); 128

(6)

GraphPanel.java -6- // もうひとつの矢印部分の先端を取得 129 x = point2.x + barb*Math.cos(theta+Math.PI+phi); 130 y = point2.y + barb*Math.sin(theta+Math.PI+phi); 131 // 終点から矢印部分の先端へラインを引く 132 path.lineTo((float)x, (float)y); 133 // 矢印線を表すパスを返す 134 return path; 135 } 136 137 /** 138 * 属性付き文字列から数式を表すオブジェクトを作成<br> 139 * RPNUtil.RPN(数式文字列)で Term オブジェクトを生成できる 140 * @param formula 数式を表す属性付き文字列 141 * @throws RPNException 142 */ 143

public void setFormula(AttributedString formula) throws RPNException { 144 // null だったら何もしない --- 145 if (formula == null) 146 return; 147 // 数式文字列生成 例:x の 2 乗→x^2 など --- 148

StringBuffer sb = new StringBuffer(); // 数式文字列生成用 149

StringBuffer sbSup = new StringBuffer(); // 上付き文字保存用 150

// 属性付き文字列のループのまわし方は以下のようにする 151

// char 変数 ch に文字が順に入り、属性はイテレータ ite が持っている 152

AttributedCharacterIterator ite = formula.getIterator(); 153

for (char ch=ite.first(); ch!=CharacterIterator.DONE; ch=ite.next()) { 154 // 上付き文字属性だったら ch を sbSup に追加 155 if (ite.getAttribute(TextAttribute.SUPERSCRIPT) == TextAttribute.SUPERSCRIPT_SUPER) { 156 if (sbSup.length() == 0) { 157 sb.append('^'); 158 } 159 sbSup.append(ch); 160 } 161 // 上付き文字属性でなければ ch を sb に追加 162 else { 163 // sbSup に何か文字が入ってれば sb に sbSup の内容を追加 164 if (sbSup.length() != 0) { 165 sb.append(sbSup); 166

sbSup.delete(0, sbSup.length()); // sbSup を空にする 167 } 168 sb.append(ch); 169 } 170 } 171 // 最後に sbSup に残っている分を sb に追加 172 if (sbSup.length() != 0) { 173 sb.append(sbSup); 174 sbSup.delete(0, sbSup.length()); 175 } 176 // πは pi に、×は*にする 177

for (int i=0; i<sb.length(); i++) { 178

// if (sb.charAt(i) == 'π') と等価 179

if (sb.charAt(i) == 0x03C0) { 180

sb = sb.replace(i, i+1, "pi"); 181 i++; 182 } 183 // else if (sb.charAt(i) == '×') と等価 184 else if (sb.charAt(i) == 0xD7) { 185 sb = sb.replace(i, i+1, "*"); 186 } 187 } 188 // 数式文字列を解析して数式オブジェクトを作る --- 189 term = RPNUtil.RPN(sb.toString()); 190 } 191 192

(7)

GraphPanel.java

-7-

/* 範囲 setter **********************************************************/ 193

194

public void setxMin(double xMin) { 195 this.xMin = xMin; 196 } 197 198

public void setxMax(double xMax) { 199 this.xMax = xMax; 200 } 201 202

public void setyMin(double yMin) { 203 this.yMin = yMin; 204 } 205 206

public void setyMax(double yMax) { 207 this.yMax = yMax; 208 } 209 210 } 211

参照

関連したドキュメント

回転に対応したアプリを表示中に本機の向きを変えると、 が表 示されます。 をタップすると、縦画面/横画面に切り替わりま

これはつまり十進法ではなく、一進法を用いて自然数を表記するということである。とは いえ数が大きくなると見にくくなるので、.. 0, 1,

このように、このWの姿を捉えることを通して、「子どもが生き、自ら願いを形成し実現しよう

子どもが、例えば、あるものを作りたい、という願いを形成し実現しようとする。子どもは、そ

点から見たときに、 債務者に、 複数債権者の有する債権額を考慮することなく弁済することを可能にしているものとしては、

本検討で距離 900m を取った位置関係は下図のようになり、2点を結ぶ両矢印線に垂直な破線の波面

(注)本報告書に掲載している数値は端数を四捨五入しているため、表中の数値の合計が表に示されている合計

貸借若しくは贈与に関する取引(第四項に規定するものを除く。)(以下「役務取引等」という。)が何らの