Java言語による制御・自動処理

リニアングラフ2Dは、VCSSLに加えて、Java言語での制御もサポートしています。

- 目次 -

開発の準備とコンパイル・実行

Java言語でリニアングラフ2Dを制御するには、別途Java言語の開発環境(JDK)が必要です。JDKの入手やインストールについては、Java言語の解説書や解説サイトなどをご参照ください。 なお、ここでは解説の単純化のためIDEは使用せず、コンパイルと実行はコマンドラインで行います。

はじめに、環境が揃っている事の確認として、リニアングラフ2Dを起動するための簡単なサンプルを作成し、コンパイル・実行してみましょう。 「Sample0.java」という名前で、以下のコードを記述したテキストファイルを作成してください:


import com.rinearn.graph2d.RinearnGraph2D;

public class Sample0 {
	public static void main(String[] args) {
		
		// グラフを起動
		RinearnGraph2D graph = new RinearnGraph2D();
	}
}
code/Sample0.java

続いて、リニアングラフ2Dの配布パッケージ内にある「 RinearnGraph2D.jar (JARファイル) 」を、上のコードと同じ場所に置いてください。 そして、コマンドライン端末上でその場所にcdなどで移動し、以下のように入力してコンパイルしてください:

javac -classpath ".;RinearnGraph2D.jar" Sample0.java     (Windowsの場合)
javac -classpath ".:RinearnGraph2D.jar" Sample0.java     (Linux等の場合)
※ 上の二つは、 -classpath の後の部分での区切り文字が、 「 ; 」 と 「 : 」で異なっています。

エラー無くコンパイルできた事を確認した上で、そのまま以下のように入力して実行してください:

java -classpath ".;RinearnGraph2D.jar" Sample0     (Windowsの場合)
java -classpath ".:RinearnGraph2D.jar" Sample0     (Linux等の場合)

実行の結果、グラフ画面が起動すれば成功です。以降のサンプルコードでは、上のコマンドのSample0の部分をSample1〜Sample6に置き換えて、同様にコンパイル・実行できます。 その際、ファイル名は必ず「 クラス名.java 」とするようにご注意ください。

このページで使用するリニアングラフ2DのJava言語用APIライブラリは、以下のURLで仕様書を参照できます。 このページでは触れきれない細かい機能の一覧や詳細などは、そちらをご参照ください:

リニアングラフ2D API仕様書 ( Java言語用 )
https://www.rinearn.com/ja-jp/graph2d/api/

APIライブラリの中でも特に使用するRinearnGraph2Dクラスの仕様書は、以下で参照できます:

RinearnGraph2Dクラス API仕様書
https://www.rinearn.com/ja-jp/graph2d/api/com/rinearn/graph2d/RinearnGraph2D

以下では、いくつかの基本となる使い方について、サンプルコードを例示しながら解説していきます。 なお、以下で掲載するサンプルコードは、配布パッケージ内にも同梱されています。

ファイルのプロット

まずは、実用における最も単純な例として、CSVファイルに記載されたデータをプロットしてみましょう。以下のサンプルデータファイルを使用します:

- SampleDataFile1.csv -
0.0,0.0
1.0,1.0
2.0,4.0
3.0,9.0
4.0,16.0
5.0,25.0
6.0,36.0
7.0,49.0
8.0,64.0
9.0,81.0
10.0,100.0

この通り、2カラム書式(左から x y )で、0 <= x <= 10の範囲で関数y = x2の座標値が書き出されています。このファイルを開いてグラフにプロットするプログラムは以下の通りです:


import com.rinearn.graph2d.RinearnGraph2D;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Sample1 {
	public static void main(String[] args) {
		
		// グラフを起動
		RinearnGraph2D graph = new RinearnGraph2D();
		
		try {
			// データファイルを読み込んでプロット
			graph.openDataFile(new File("SampleDataFile1.csv"));
			
		// 異常時の例外処理
		} catch (FileNotFoundException fnfe) {
			System.err.println("ファイルが見つかりませんでした。");
		} catch (IOException ioe) {
			System.err.println("ファイルが開けませんでした。");
		}
	}
}
code/Sample1.java
実行結果

上のコードでは、まずRinearnGraph2Dクラスのインスタンスを生成し(この時点でグラフ画面が起動します)、 そしてopenDataFile メソッドを呼び出して、開きたいファイルを引数に渡しています。 ファイルを開く過程での例外については、必要に応じて適切に処理してください。

配列データのプロット

ファイルを介さず、配列に格納された座標値データを、直接渡してプロットする事もできます:


import com.rinearn.graph2d.RinearnGraph2D;

public class Sample2 {
	public static void main(String[] args) {
		
		// 座標値データの用意
		int n = 101;
		double[] x = new double[n];
		double[] y = new double[n];
		for(int i=0; i<n; i++) {
			x[i] = i*0.1;
			y[i] = Math.sin(x[i]);
		}
		
		// グラフにプロット
		RinearnGraph2D graph = new RinearnGraph2D();
		graph.setData(x, y);
	}
}
code/Sample2.java
実行結果

※ Y軸の中心が0になっていないのは、上下の端が僅かに±1からずれているため、後で扱うようにプロット範囲を設定すると、綺麗に揃えられます。

このように、先ほどファイルを開いていた openDataFile メソッドの代わりに、setDataメソッドで配列データを渡します。

配列データは、ここでは単系列なので x[ 座標点インデックス ] の形ですが、 系列を分けたい場合は x[ 小系列インデックス ][ 座標点インデックス ] の形の2次元配列にします(yも同様)。 プロット結果では系列の区切りで色が変わり、また、線プロット時では系列の区切りで線が切れます。

なお、上のコードで、座標値データの点数 n を 101 と半端な値にしているのは、単純に端の点でのX値をきりのいい値にするためで、重要な意味はありません (※ Xの値を単純に配列イデックス×0.1としているので、インデックスの範囲が0〜100ならちょうどきりがいいのですが、そのために必要な配列要素数は101個です)。

画像の出力

グラフを画像ファイルに出力するには、RinearnGraph2DクラスのexportImageFile メソッドを使用します。 例えば、これまでのサンプルコードにおいて、mainメソッドの末尾に以下の一行を追加すれば、画像ファイル「graph.png」が出力されます:


		try {
			graph.exportImageFile(new java.io.File("graph.png"), 1.0);
		} catch (java.io.IOException ioe) {
			System.err.println("ファイル出力に失敗しました。");
		}
code/Export.java

画像形式は拡張子から自動で判断されますが、現時点ではJPEGとPNG形式にのみ対応しています。2番目の引数は画質で、1.0で最高、0.0で最低となります(JPEG形式のみで有効です)。

なお、getImageメソッドにより、グラフ画像をjava.awt.Imageクラスのインスタンスとして取得する事もできます。 これにより、グラフ画像を加工したり、別のGUI画面上に埋め込んだりする事も(それなりの処理を書く必要がありますが)可能です。

範囲やオプションなどの詳細設定

setXRange / setYRange メソッドや setOptionSelected メソッドなどを用いて、プロット範囲やオプションなどの設定を行う事ができます。 例として、先ほどと同様の配列データをプロットし、点プロットオプションを無効化しつつ線プロットオプションは有効である状態にして、範囲も設定してみましょう:


import com.rinearn.graph2d.RinearnGraph2D;
import com.rinearn.graph2d.RinearnGraph2DOptionItem;

public class Sample3 {
	public static void main(String[] args) {
		
		// 座標値データを用意し、グラフに渡してプロット
		int n = 101;
		double[] x = new double[n];
		double[] y = new double[n];
		
		for(int i=0; i<n; i++) {
			x[i] = i*0.1;
			y[i] = Math.sin(x[i]);
		}
		
		RinearnGraph2D graph = new RinearnGraph2D();
		graph.setData(x, y);
		
		// プロット範囲を設定
		graph.setXRange(0.0, 8.0);
		graph.setYRange(-1.25, 1.25);
		
		// グラフスクリーンのサイズを設定
		graph.setScreenSize(680, 380);
		
		// タイトルとラベルを設定
		graph.setTitle("sin 関数のグラフ");
		graph.setXLabel("x");
		graph.setYLabel("sin (x)");
		
		// プロットオプションを選択(点プロット無効化と線プロット有効化)
		graph.setOptionSelected(RinearnGraph2DOptionItem.POINT, false);
		graph.setOptionSelected(RinearnGraph2DOptionItem.LINE, true);
		
		// プロットオプションのパラメータを設定(線プロットの線幅を指定)
		RinearnGraph2DOptionParameter parameter 
				= new RinearnGraph2DOptionParameter();
		parameter.setLineWidth(3.0);
		graph.setOptionParameter(parameter);
	}
}
code/Sample3.java
実行結果

上のコードでは、グラフに座標値データを渡してプロットさせた後、まず setXRange / setYRangeメソッドでそれぞれX/Y軸方向のプロット範囲を設定しています。

続いて、setScreenSize メソッドでグラフスクリーンの幅と高さを設定しています。グラフスクリーンは、グラフが描画されているスクリーンの事で、 グラフを画像ファイルに出力する際などには、ここで指定したサイズが出力画像のサイズになります。

その後は、setOptionSelectedメソッドでプロットオプションの有効/無効状態を操作しています。 操作するオプションの項目は RinearnGraph2DOptionItem 列挙子の要素で指定します。 上では点プロットオプションを無効化し、線プロットオプションを(こちらはデフォルトでも有効ですが)有効化しています。

また、プロットオプションでの線の太さなどの詳細設定を、setOptionParameterメソッドで行っています。 このメソッドには、まずRinearnGraph2DOptionParameter クラスのインスタンスを生成して設定内容を保持させた上で、それを引数に渡します。 線の太さの他に、点プロットでの点の大きさなども設定できます。

設定系のメソッドは他にも存在します。詳細については、RinearnGraph2DクラスのAPI仕様書をご参照ください。 なお、細かい設定を一括で行うには、リニアングラフ2Dの設定ファイルを loadConfigurationFile メソッドで読み込む方法もあります。 設定周りの処理があまり多くなってくると、コードをシンプルにする上でも、設定ファイル経由の方が便利です。 また、APIでは細かすぎて未サポートになっているような設定も、設定ファイル経由なら行えます。 一方で、設定ファイルの記載内容やその有効性は、(できるだけ互換は保たれますが)リニアングラフ2Dのバージョンに依存する事に留意する必要があります。

アニメーションプロット

アニメーションプロットを行うには、スレッドを生成して、その中で8-2や8-3で扱ったファイルや配列データのプロットを繰り返し、少しずつ異なる内容を連続で描かせ続ければOKです。

ただし標準では、setDataメソッドで配列データを渡した際、データの描画処理が完了するまで、呼び出し元に処理が戻らない事に留意する必要があります。 これは、アニメーションの各画面をコマ落ちなく画像に出力したい場合などには便利です。 しかし、例えば外部の装置などから入力されるデータをリアルタイムでプロットしたい場合などには、次々と入ってくるデータに対して描画速度が追い付かない、つまり描画がボトルネックになってしまう可能性もあります。

そのような場合には、setAsynchronousPlottingEnabledメソッドの引数にtrueを指定すると、setDataメソッド呼び出し時はすぐに処理が戻り、描画処理は別スレッドで非同期に、適当なタイミングで行われるようになります。 これは、一般にアニメーション速度を描画所要時間に依存させたくない場合にも有効です。

実際に、毎時刻の配列データを計算で生成し、非同期描画でアニメーションさせてみましょう:


import com.rinearn.graph2d.RinearnGraph2D;
import com.rinearn.graph2d.RinearnGraph2DOptionItem;
import java.awt.event.WindowListener;
import java.awt.event.WindowEvent;

public class Sample4 implements Runnable, WindowListener {
	
	Thread thread = null;         // アニメーション用のスレッド
	RinearnGraph2D graph = null;  // グラフ
	volatile boolean continuesLoop = true;     // ループの継続/終了を制御する
	
	public static void main(String[] args) {
		Sample4 sample = new Sample4();
	}
	
	// 初期化・実行開始処理
	public Sample4() {
		
		// グラフを起動し、描画範囲の設定と自動調整機能の無効化を行う
		this.graph = new RinearnGraph2D();
		graph.setXRange(0.0, 10.0);
		graph.setYRange(-2.5, 2.5);
		this.graph.setXAutoRangingEnabled(false);
		this.graph.setYAutoRangingEnabled(false);
		
		// データ更新と描画処理の関係を非同期にする(リアルタイムアニメーション用)
		//(※ 連番で画像出力する用途などでは行わない方がコマ落ちや欠けを防げる)
		this.graph.setAsynchronousPlottingEnabled(true);
		
		// グラフを閉じたら独自終了処理を行うリスナーを登録、デフォルト処理は無効化
		this.graph.addWindowListener(this);
		this.graph.setAutoDisposingEnabled(false);
		
		// アニメーションスレッドを生成して実行開始
		this.thread = new Thread(this);
		this.thread.start();
	}
	
	// アニメーションスレッドの処理
	@Override
	public void run() {
		
		int n = 101;
		double[] x = new double[n];
		double[] y = new double[n];
		
		// アニメーションループ(continuesLoopがtrueの間継続)
		for(int frame=0; this.continuesLoop; frame++) {
			double t = frame * 0.05; // 時刻変数
			
			// 座標値データを更新してグラフに転送(非同期)
			for(int i=0; i<n; i++) {
				x[i] = i * 0.1;
				y[i] = Math.sin(3.0*x[i]+1.5*t)+Math.cos(3.5*x[i]+t);
			}
			this.graph.setData(x, y);
			
			// 50ミリ秒だけ停止(時間は適時調整)
			try {
				Thread.sleep(30);
			} catch(InterruptedException e) {
				// 割り込み例外の処理
			}
		}
		// アニメーションループが終了したらグラフを破棄
		this.graph.dispose();
		// スレッド処理終端: 他にスレッド・リソースが残っていなければ自然に実行終了
	}
	
	
	// ※ 以下のようなイベント処理の実装が面倒な場合は、多少強引でよければ、
	// this.graph.setAutoExittingEnabled(true); により、グラフを閉じたら
	// アプリケーションの実行を即終了するよう設定可能です(即席の場合向けです)。
	
	
	// グラフのウィンドウが閉じられた際に行うイベント処理
	@Override
	public void windowClosing(WindowEvent e) {
		// アニメーションループを脱出させ、スレッドを終了させる(結果、実行も終了)
		this.continuesLoop = false;
	}
	
	// その他のウィンドウイベント処理(ここでは何もしない)
	@Override
	public void windowDeactivated(WindowEvent e) { }
	@Override
	public void windowActivated(WindowEvent e) { }
	@Override
	public void windowDeiconified(WindowEvent e) { }
	@Override
	public void windowIconified(WindowEvent e) { }
	@Override
	public void windowOpened(WindowEvent e) { }
	@Override
	public void windowClosed(WindowEvent e) { }
}
code/Sample4.java
実行結果 (干渉するsin波のグラフがプロットされ、形状がアニメーションで時間変化します。)

描画エンジンの直接操作による描画

これまでの内容では、リニアングラフに渡した配列データやファイルから、オプション設定などに応じて、ある程度「おまかせ」でグラフを描画してもらっていました。 これは手短で簡単なのですが、その代わり自由度には限界があります。

例えば、「ここに、こういう大きさと色の点を描きたい」 や 「こことここを、こういう色と太さの線で結びたい」 といったように、 もっと細かく描画内容を制御したい場合があるかもしれません。 そのような用途のために、グラフの描画エンジンを直接操作する事もできます。 そのための API はRinearnGraph2DRenderer クラスとして提供されており、以下で仕様書を参照できます:

RinearnGraph2DRendererクラス API仕様書
https://www.rinearn.com/ja-jp/graph2d/api/com/rinearn/graph2d/renderer/RinearnGraph2DRenderer

簡単な例

描画エンジンの処理は、もちろんリニアングラフ 2D 側からも呼び出されます。 そのため、ここで扱う描画エンジンの直接操作と、リニアングラフ2Dの他の機能 (ファイルを開いてプロットしたり、メニューから手動でグラフ範囲やプロットオプションを変更したり、等々) とを併用したい場合には、少し工夫が必要です。

詳しくは次の項で説明しますが、まずはそのような事は一旦置いておいて、とりあえず一番簡単な方法で、点や線を描いてみましょう。


import com.rinearn.graph2d.RinearnGraph2D;
import com.rinearn.graph2d.RinearnGraph2DOptionItem;
import com.rinearn.graph2d.renderer.RinearnGraph2DRenderer;
import java.awt.Color;

public class Sample5 {
	public static void main(String[] args) {

		// グラフを起動してレンダラー(描画エンジン)を取得
		RinearnGraph2D graph = new RinearnGraph2D();
		RinearnGraph2DRenderer renderer = graph.getRenderer();

		// グラフ領域内の(0.3, 0.5)の位置に、半径8ピクセルで赤色の点を描画
		renderer.drawPoint(0.3, 0.5, 8.0, Color.RED);

		// グラフ領域内の(-0.2,-0.3)と(0.8,0.5)を結ぶ、太さ3ピクセルで青色の線を描画
		renderer.drawLine(-0.2, -0.3, 0.8, 0.5, 3.0, Color.BLUE);

		// スクリーンの再描画(レンダリング)
		renderer.render();
	}
}
code/Sample5.java

▼実行結果

上のコードでは、まずグラフを生成して getRenderer メソッドで描画エンジンを取得し、そしてその描画エンジンの各描画メソッドを呼び出して、点や線などを描いています。

最後に、render メソッドでスクリーンの再描画を行わせていますが、これを忘れるとリニアングラフ 2D の画面が更新されず、 従ってせっかく描いた点や線も表示されないのでご注意ください。

スクリーンの再描画は、マウスカーソルがグラフ画面上で動いた場合など、必要に応じて自動で行われるタイミングもありますが、しかし点や線を 1 つ 1 つ描くごとに毎回行われたりはしません。 従って、描きたい内容を全部描き終えた後で、このように明示的に render メソッドで行ってください。

drawPoint などの各描画メソッドの引数に渡す座標は、グラフ空間における座標と見なされます。 そのため、描画される絶対的な位置はグラフの範囲設定によって変化します。また、グラフの範囲より外側にはみ出した内容は描画されません。

グラフ範囲やプロットオプションを変更しても描画内容が消えないようにするには

ところで、上のサンプルコードでは、後からグラフ範囲やプロットオプションの変更操作などを行うと、描画内容が消えてしまいます。 これは、マウス操作で画面をスライドさせたり、拡大・縮小させたりした際についても同様です。 それで消えてしまうのは、ちょっと不便ですね。 他にもファイルのデータを開いてプロットした際にも消えてしまうので、「 データに補助線などを重ねて描きたい 」といった場合にも不便です。

実は上のような操作は、ファイルや配列データから描画されている他の内容なども含めて、グラフ全体の描きなおし(再描画)が必要になるため、 最初にリニアングラフ 2D 側から描画内容をリセットする処理が呼ばれます。 その際に、描画エンジンを操作して描いた点や線も消えてしまうわけです。

そこで、グラフの再描画が必要なタイミングをイベントとして受け取って、その度に先ほどのコードの点や線も描画しなおすようにすれば、 上で述べたような操作を行っても、消えずにきちんと対応できるようになります。以下がその例です:


import com.rinearn.graph2d.RinearnGraph2D;
import com.rinearn.graph2d.RinearnGraph2DOptionItem;
import com.rinearn.graph2d.renderer.RinearnGraph2DRenderer;
import com.rinearn.graph2d.event.RinearnGraph2DPlottingEvent;
import com.rinearn.graph2d.event.RinearnGraph2DPlottingListener;
import java.awt.Color;

public class Sample6 implements RinearnGraph2DPlottingListener {
	RinearnGraph2D graph;
	RinearnGraph2DRenderer renderer;

	public static void main(String[] args) {
		new Sample6();
	}
	
	// グラフの起動と初期設定
	public Sample6() {

		// グラフを起動してレンダラー(描画エンジン)を取得
		this.graph = new RinearnGraph2D();
		this.renderer = graph.getRenderer();

		// 再描画が必要になったらイベントで受け取れるようにリスナー登録
		this.graph.addPlottingListener(this);
		
		// 描画処理を実行してスクリーンを3DCGレンダリング
		this.draw();
		renderer.render();
	}

	// 描画エンジンによる描画処理
	public void draw() {
        
		// グラフ領域内の(0.3, 0.5)の位置に、半径8ピクセルで赤色の点を描画
		renderer.drawPoint(0.3, 0.5, 8.0, Color.RED);

		// グラフ領域内の(-0.2,-0.3)と(0.8,0.5)を結ぶ、
		// 太さ3ピクセルで青色の線を描画
		renderer.drawLine(-0.2, -0.3, 0.8, 0.5, 3.0, Color.BLUE);
	}

	// 再描画が必要になった際に呼ばれるイベント処理
	@Override
	public void plottingRequested(RinearnGraph2DPlottingEvent e) {
		// 描画処理を再実行
		this.draw();
	}

	// 以下のイベント処理はここでは何もしない
	@Override
	public void plottingCanceled(RinearnGraph2DPlottingEvent e) {
	}
	@Override
	public void plottingFinished(RinearnGraph2DPlottingEvent e) {
	}
}
code/Sample6.java

このコードを実行した結果、描画される内容は、先ほどの Sample5 と全く同様です。 しかし、マウス操作やメニュー操作によってグラフ範囲を移動・拡大・縮小したり、ファイルを開いて別のデータをプロットしたりしても、 描いた点や線が消えてしまう事は無く、ちゃんと常に正しい位置に表示されます。

注意: plottingRequested 等の中で RinearnGraph2D クラスのメソッドは呼べない

ここで一つ注意が必要です。 RinearnGraph2DPlottingListenerインターフェースを実装する際、plottingRequestedメソッドなどの各イベント処理メソッド内から、 描画エンジンではなくリニアングラフ2D本体側である RinearnGraph2Dクラスのメソッドを呼ばないようにしてください (描画エンジンであるRinearnGraph2DRendererクラスのメソッドは呼んでもOKです)。

というのも、例えば plottingRequested メソッド内から、RinearnGraph2Dクラスの setXRange メソッドでグラフ範囲を変更すると、 それによって再描画が必要になるためplottingRequested が呼ばれ、またその中で setXRange メソッドを呼んで … と無限ループに陥ってしまいます。

他にも、RinearnGraph2Dクラスの多くのメソッドは、処理が中途半端なタイミングで行われてしまうのを避けるために、 描画エンジンの操作中は一旦待機して、描画完了してから処理を行うようになっています。 そして、plottingRequested などの実行中は描画エンジンが操作中であると見なされるため、その中でRinearnGraph2Dクラスのメソッドを呼ぶと、 描画完了をいつまでも待機してしまい、処理が進まなくなってしまいます。

RinearnGraph2DPlottingListener インターフェースを実装した結果、グラフが動かなくなってしまったら、上の点を踏まえて見直してみてください。