2019.09.14

Javaプログラミング

数学グラフ

Swingパッケージを使って数式のグラフを表示する方法を解説します。

ここで扱う数式は、

y = f(x)

と表すもので、xを与えてyを求める関数です。以下はその例です。

y = |x| … 絶対値

y = x * x … 放物線

y = √x … 平方根

y = sin(x) … サイン正弦

y = cos(x) … コサイン余弦

y = tan(x) … タンジェント正接

以上が、xからyを求める関数の例です。

それでは、y = f(x)で表される関数をグラフ表示する方法について考えていきます。

まず、数学座標(x,f(x))をそのままグラフィック座標に当てはめることを考えます。下の図は、数学座標系グラフィック座標系(画像座標系)を表したものです。

数学座標系から画像座標系への変換

この図から、以下のような問題があることがわかります。

・上下が反転している。(y軸の方向が逆)

・マイナスの座標が表示できない。

座標の範囲に限界がある。(0,0)-(幅ピクセル- 1,高さピクセル - 1)

・小数が取り扱えない。

このため、数式の値で直接グラフを表示することはできません。

そこで、数学座標で指定した座標の範囲を、グラフィック座標に変換することでグラフを表示することを考えます。これには、数学座標の一部の範囲をグラフィック座標に変換する方法を使います。

数学座標と画像座標の対応

グラフィック座標は小数点が使えないので完全なグラフを表示することはできませんが、表示するグラフィック座標の縦横のピクセル数を多くすることでより正確なグラフを表現することができます。

下の図は、サイン(sin)カーブの表示例で、横軸を角度(単位°)、縦軸をsin(角度)の値としたものです。

y=sin(x)をグラフ表示

サインカーブを表示するJavaソースコード

サイン(sin)カーブの表示を行うソースコードです。

このプログラムは、サインカーブの表示を行う「GR_SinCurve.java」と座標変換を行う「Transformation2d.java」の2つのソースコードから成り立っています。

サインカーブを表示するソース

GR_SinCurve.java

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import java.awt.*;
import javax.swing.*;

public class GR_SinCurve {
	public static void main(String[] args) {
		// フレームの大きさを設定
		int w  = 400;
		int h = 320;

		// フレームを作成
		JFrame frame = new JFrame();

		//  タイトル名を設定
		frame.setTitle( "サインカーブ" );


		// 内側フレームの大きさを設定
		frame.getContentPane().setPreferredSize( new Dimension( w, h ) );
		frame.pack();

		// ”×”ボタンを押した時の処理を設定
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

		// フレームにパネルを追加
		MyPanel panel = new MyPanel();
		frame.getContentPane().add( panel );

		// フレームを表示
		frame.setVisible( true );
	}
}


// JPanelを継承したMyPanelを作成
class MyPanel extends JPanel {
	public MyPanel() {
		// 背景色を白(white)に設定
		setBackground( Color.white );
	}

	// 関数
	private double f( double x )
	{
		// 角度degをラジアンradに変換
		double rad = Math.toRadians( x );
		// サインを計算
		return Math.sin( rad );
	}


	// ラインの開始座標
	private int line_x1 = 0;
	private int line_y1 = 0;

	// ラインの開始座標の設定
	private void moveTo( int x, int y )
	{
		line_x1 = x;
		line_y1 = y;
	}

	// ラインの表示
	private void lineTo(Graphics g, int x, int y )
	{
		g.drawLine( line_x1, line_y1, x, y );
		line_x1 = x;
		line_y1 = y;
	}

	// 描画
	public void paintComponent( Graphics g ) {
		super.paintComponent( g );

		// フレームの大きさを取得
		int frame_w = getWidth();
		int frame_h = getHeight();

		// 座標変換クラスの作成
		Transformation2d trans = new Transformation2d();

		// 座標を設定
		// (-360.0,1.2)-(360.0,-1.2)の範囲がフレーム全体に表示する
		trans.set( -360.0, 1.2, 360, -1.2, 0.0, 0.0,
			(double)( frame_w - 1 ), (double)( frame_h - 1 ) );

		// 原点の画像座標を取得
		double origin_x = trans.getX( 0.0 );
		double origin_y = trans.getY( 0.0 );

		// 座標軸の色を黒
		g.setColor( Color.black );

		// x軸の描画
		g.drawLine( 0, (int)origin_y, frame_w - 1, (int)origin_y );

		// y軸の描画
		g.drawLine( (int)origin_x, 0, (int)origin_x, frame_h - 1 );

		// サイン(sin)カーブの描画
		// サインカーブの色を赤
		g.setColor( Color.red );

		// 角度-360から360度までのサインカーブを表示
		boolean isStart = true;
		for ( int x = -360; x <= 360; x++ ) {
			// 計算結果をyに代入
			double y = f( (double)x );

			// 数学座標( deg, value )を
			// 画像座標( line_x2, line_x2 )に変換
			int px = (int)trans.getX( (double)x );
			int py = (int)trans.getY( y );
			
			// ラインの表示
			if ( true == isStart ) {
				moveTo( px, py );
				isStart = false;
			}
			else
				lineTo( g, px, py );
		}
	}
}

座標変換を行うソース

Transformation2d.java

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
public class Transformation2d {
	// 変換パラメータ a,b,c,d
	private double a = 0.0;
	private double b = 0.0;
	private double c = 0.0;
	private double d = 0.0;

	// パラメータを計算
	boolean set( double x1, double y1, double x2, double y2,
				double px1, double py1, double px2, double py2 )
	{
		// ゼロとみなす値
		double e = 0.0000000001;

		// 初期値を代入
		a = b = c = d = 0.0;

		// x1とx2が同じ値であればエラー
		if ( e > Math.abs( x1 - x2 ) ) return false;

		// y1とy2が同じ値であればエラー
		if ( e > Math.abs( y1 - y2 ) ) return false;

		// パラメータの計算
		a = ( px1 - px2 ) / ( x1 - x2 );
		b = ( px1 * x2 - px2 * x1 ) / ( x2 - x1 );
		c = ( py1 - py2 ) / ( y1 - y2 );
		d = ( py1 * y2 - py2 * y1 ) / ( y2 - y1 );

		return true;
	}


	// 変換後のx座標を取得
	public double getX( double x )
	{
		return a * x + b;
	}

	// 変換後のy座標を取得
	public double getY( double y )
	{
		return c * y + d;
	}
}

ソースコードの解説

ここから、「GR_SinCurve.java」を解説していきます。

001
002
import java.awt.*;
import javax.swing.*;

Javaクラスライブラリの中から「java.awt」と 「javax.swing」というパッケージにあるクラスを、このプログラム内で使うために記述します。

004
public class GR_SinCurve {

クラス名を、GR_SinCurveとしています。

005
	public static void main(String[] args) {

このmainメソッドからプログラムを実行します。

006
007
008
		// フレームの大きさを設定
		int w  = 400;
		int h = 320;

フレームの大きさを設定するint型変数wとhにそれぞれ400と320を代入しています。wは幅で、hが高さです。

010
011
		// フレームを作成
		JFrame frame = new JFrame();

JFrameオブジェクトを作成しています。

013
014
		//  タイトル名を設定
		frame.setTitle( "サインカーブ" );

setTitleメソッドで、タイトルに”サインカーブ”を設定しています。

017
018
019
		// 内側フレームの大きさを設定
		frame.getContentPane().setPreferredSize( new Dimension( w, h ) );
		frame.pack();

setPreferredSizeメソッドとpackメソッドで内側のサイズを指定したフレームを作成しています。変数wとhを使って、サイズを(400,320)にしています。

021
022
		// ”×”ボタンを押した時の処理を設定
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

"×"ボタンをクリックしたときに、フレームを閉じる処理を設定しています。

024
025
026
		// フレームにパネルを追加
		MyPanel panel = new MyPanel();
		frame.getContentPane().add( panel );

フレームにパネルに追加しています。

このパネルで、サインカーブのグラフを表示いています。34行目以降を参考にしてください。

028
029
		// フレームを表示
		frame.setVisible( true );

setVisibleメソッドtrueを渡して、フレームを表示しています。

034
035
// JPanelを継承したMyPanelを作成
class MyPanel extends JPanel {

JPanelを継承したMyPanelクラスを作成しています。

036
037
038
	public MyPanel() {
		// 背景色を白(white)に設定
		setBackground( Color.white );

クラス名と同じ名前のメソッドMyPanel()はコンストラクタと呼ばれ、クラス作成時に呼ばれます。そのメソッドの中に初期化の処理を記述します。

ここでは、setBackgroundメソッドにColor.whiteを渡して、背景色を白にする処理を記述しています。

041
042
043
044
045
046
047
048
	// 関数
	private double f( double x )
	{
		// 角度degをラジアンradに変換
		double rad = Math.toRadians( x );
		// サインを計算
		return Math.sin( rad );
	}

変数xを渡してyを戻すメソッドです。ここでは、角度xからsin(x)を計算するメソッドにしています。xの単位が度です。

単位が度の変数xの値をMath.toRadiansメソッドラジアンに変換した値を変数radに代入し、Math.sinメソッドにradを渡してsinの値を計算しています。

このメソッドの中を変えることで、いろいろな種類のグラフを表示することができます。

051
052
053
	// ラインの開始座標
	private int line_x1 = 0;
	private int line_y1 = 0;

ラインの開始座標を格納するメンバ変数を宣言しています。line_x1が開始座標x、line_y1が開始座標yです。

055
056
057
058
059
060
	// ラインの開始座標の設定
	private void moveTo( int x, int y )
	{
		line_x1 = x;
		line_y1 = y;
	}

ラインの開始座標を設定するメソッドです。メンバ変数のline_x1にx、line_y1にyを代入しています。

062
063
064
065
066
067
068
	// ラインの表示
	private void lineTo(Graphics g, int x, int y )
	{
		g.drawLine( line_x1, line_y1, x, y );
		line_x1 = x;
		line_y1 = y;
	}

ラインを表示するメソッドです。メンバ変数の開始座標( line_x1, line_y1 )と引数( x, y )を結ぶラインを表示しています。

表示した後、line_x1にx、line_y1にyを代入しています。

070
071
	// 描画
	public void paintComponent( Graphics g ) {

paintComponentメソッドは、画面の表示が必要な時に呼ばれるメソッドです。

072
		super.paintComponent( g );

スーパークラスのpaintComponenメソッドを呼び出しています。これで、コンポーネントに必要な表示などが行われます。

074
075
076
		// フレームの大きさを取得
		int frame_w = getWidth();
		int frame_h = getHeight();

フレームの大きさを取得しています。

getWidthメソッドで、現在のコンポーネントの幅を取得し、整数型の変数frame_wに代入しています。

getHeightメソッドで、現在のコンポーネントの高さを取得し、整数型の変数frame_hに代入しています。

078
079
080
081
082
083
084
		// 座標変換クラスの作成
		Transformation2d trans = new Transformation2d();

		// 座標を設定
		// (-360.0,1.2)-(360.0,-1.2)の範囲がフレーム全体に表示する
		trans.set( -360.0, 1.2, 360, -1.2, 0.0, 0.0,
			(double)( frame_w - 1 ), (double)( frame_h - 1 ) );

座標を変換するクラスtransを作成しています。このクラスは、数学座標からグラフィック座標に変換するためのものです。

予め設定した変換前の数学座標の範囲と、変換後のグラフィック座標の範囲から、任意の数学座標(x,y)に対応するグラフィック座標を求めることができます。

ここでは、trans.setメソッドで、変換前の数学座標を(-360.0,1.2)-(360.0,-1.2)、変換後のグラフィック座標の範囲を(0,0)-(frame_w-1,frame_h-1)としています。

x座標y座標を別々にみていくと、数学座標xの範囲は-360.0~360.0、グラフィック座標xの範囲は0~frame_w-1です。このソースのx座標は単位が度の角度を表しており、角度が-360度は画面の左端、角度は360度は画面の右端の値が取得できるようにしています。

数学座標yの範囲は1.2~-1.2、グラフィック座標yの範囲は0~frame_h-1です。y座標sin(x)の値を表しており、sin(x)最小値最大値が画面内に収まるように余裕をもって設定しています。sin(x)の計算値の最小値は-1.0、最大値が1.0なので、ここで設定している1.2~-1.2の範囲に収まります。

数学座標yの範囲を1.0~-1.0にすると、sin(x)最大値が画面の上端、最小値が下端になります。

Transformation2dクラスソースコードは、以下の記事で詳しく解説しています。

数学座標(x,y)を画像座標(px,py)に変換する方法をソースコードを使って詳しく説明しています。

2020.03.23
086
087
088
		// 原点の画像座標を取得
		double origin_x = trans.getX( 0.0 );
		double origin_y = trans.getY( 0.0 );

数学座標(0.0,0.0)のグラフィック座標を取得しています。

090
091
		// 座標軸の色を黒
		g.setColor( Color.black );

g.SetColorメソッドで、これから表示する図形の色を黒に設定しています。

093
094
		// x軸の描画
		g.drawLine( 0, (int)origin_y, frame_w - 1, (int)origin_y );

g.drawLineメソッドで、グラフィックの原点y座標origin_yを通る水平線を表示しています。これがx軸です。

096
097
		// y軸の描画
		g.drawLine( (int)origin_x, 0, (int)origin_x, frame_h - 1 );

g.drawLineメソッドで、グラフィックの原点x座標origin_xを通る垂直線を表示しています。これがy軸です。

099
100
101
		// サイン(sin)カーブの描画
		// サインカーブの色を赤
		g.setColor( Color.red );

ここから、サインカーブの表示です。

g.SetColorメソッドで、これから表示する図形の色を赤に設定しています。

103
104
		// 角度-360から360度までのサインカーブを表示
		boolean isStart = true;

表示の開始を表すboolean型変数isStartを宣言して、trueを代入しています。

105
		for ( int x = -360; x <= 360; x++ ) {

xを-360から360まで1ずつ変化されるfor文を作成しています。

106
107
			// 計算結果をyに代入
			double y = f( (double)x );

fメソッドにxを与えてyを求めています。fメソッド引数の型はdoule型なので、xの前に(double)を付けて型キャストしています。

このソースでは、fメソッドの中身はy=sin(x)です。

109
110
111
112
			// 数学座標( deg, value )を
			// 画像座標( line_x2, line_x2 )に変換
			int px = (int)trans.getX( (double)x );
			int py = (int)trans.getY( y );

数学座標(x,y)からグラフィック座標(px,py)を求めています。

114
115
116
117
118
			// ラインの表示
			if ( true == isStart ) {
				moveTo( px, py );
				isStart = false;
			}

表示の開始を表す変数isStartがtrueの場合、moveToメソッドでライン表示開始位置に(px,py)を設定しています。その後、変数isStartにfalseを代入しています。

119
120
			else
				lineTo( g, px, py );

表示の開始を表す変数isStartがfalseの場合、lineToメソッドに(px,py)を与えてラインを表示します。表示するラインは(px,py)とライン表示開始位置の座標を結ぶラインが表示されます。ラインを表示した後に、(px,py)がライン表示開始位置に設定されます。

このように、moveToを呼んだ後、lineToを連続して呼ぶと折れ線が表示できるようになっています。

GR_SinCurveのコンパイル

コンパイルするときは、javacコマンドに"GR_SinCurve.java"と"Transformation2d.java"の2つのソースコードを指定してください。

コンパイル方法

javac GR_SinCurve.java Transformation2d.java

ソースファイル文字コードを指定してコンパイルする場合は、以下の方法をお試しください。

コンパイル方法(ソースの文字コードが"ANSI"の場合)

javac -encoding sjis GR_SinCurve.java Transformation2d.java

コンパイル方法(ソースの文字コードが"utf-8"の場合)

javac -encoding utf-8 GR_SinCurve.java Transformation2d.java

他の数式のグラフ表示はこちら

Swingパッケージを使って絶対値のグラフを表示するJavaソースコードを紹介しています。

2019.09.21

Swingパッケージを使って放物線のグラフを表示するJavaソースコードを紹介しています。

2019.09.21

Swingパッケージを使って平方根のグラフを表示するJavaソースコードを紹介しています。

2019.09.21

Swingパッケージを使ってコサイン(cos)のグラフを表示するJavaソースコードを紹介しています。

2019.09.21

Swingパッケージを使ってタンジェント(tan)のグラフを表示するJavaソースコードを紹介しています。

2019.09.21

次に読んでほしいコンテンツ

sin(サイン)の意味と、Math.sinメソッドの使い方をソースコードを使って詳しく解説しています。

2020.03.23

cos(コサイン)の意味と、Math.cosメソッドの使い方をソースコードを使って詳しく解説しています。

2020.03.23

tan(タンジェント)の意味と、Math.tanメソッドの使い方をソースコードを使って詳しく解説しています。

2020.03.23

絶対値の意味と、Math.absメソッドの使い方をソースコードを使って詳しく解説しています。

2020.03.23

平方根の意味と、Math.sqrtメソッドの使い方をソースコードを使って詳しく解説しています。

2020.03.23

広告