Gnuplot.java
/*
* $Id: Gnuplot.java,v 1.38 2008/07/16 08:00:38 koga Exp $
*
* Copyright (C) 2004 Koga Laboratory. All rights reserved.
* s
*/
package org.mklab.tool.graph.gnuplot;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import org.mklab.tool.graph.PlotterException;
import org.mklab.tool.graph.gnuplot.decoration.GnuplotComponent;
/**
* {@link Gnuplot}クラスは<a href="http://www.gnuplot.info/">gnuplot</a>でグラフを描画するためのラッパークラスです。
*
* @author koga
* @version $Revision: 1.38 $, 2004/04/30
*/
public class Gnuplot {
/** <code>gnuplot</code> への出力ストリームに対応する {@link Writer} */
private Writer gnuplotWriter;
/** gnuplotのプロセス */
private Process gnuplotProcess;
/** メインキャンバス */
private Canvas mainCanvas;
/** マルチキャンバス */
private Canvas[][] canvases;
/** マルチキャンバスを使用中ならばtrue */
private boolean hasMultipleCanvas = false;
/** */
private boolean gnuplotClosed = false;
/** コマンド出力のバッファリングするならば場合true */
private boolean buffering = false;
/** gnuplotの実行環境です。 */
private Environment environment;
/**
* オプションなしでGnuplotオブジェクトを生成します。
*
* @throws IOException gnuplotプロセスを起動できない場合
*/
public Gnuplot() throws IOException {
this(Environment.getSharedEnvironment(), ""); //$NON-NLS-1$
}
/**
* 新しく生成された<code>Gnuplot</code>オブジェクトを初期化します。
*
* @param options ウィンドウオプション
* @throws IOException gnuplotプロセスを起動できない場合
*/
public Gnuplot(String options) throws IOException {
this(Environment.getSharedEnvironment(), options);
}
/**
* ウインドウオプションを指定しGnuplotオブジェクトを生成します。
*
* @param env gnuplotの実行環境
* @param options ウインドウオプション
* @throws IOException gnuplotプロセスを起動できない場合
*/
public Gnuplot(final Environment env, final String options) throws IOException {
if (env == null) {
throw new IllegalArgumentException();
}
this.environment = env;
runGnuplotProcess(options);
waitForWindowOpen();
initializeIO();
initializeCanvas();
reset();
}
/**
* gnuplotプロセスを起動します。
*
* @param options 起動オプション
* @throws IOException 起動に失敗した場合
*/
private void runGnuplotProcess(final String options) throws IOException {
final List<String> commandList;
commandList = createCommandList(options, hashCode());
try {
this.gnuplotProcess = startProcess(commandList);
} catch (IOException ex) {
throw new IOException(Messages.getString("Gnuplot.3") + " with " + options, ex); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
* gnuplotのウィンドウが表示するまで待ちます。
*
* @throws IOException 割り込みが発生した場合
*/
private void waitForWindowOpen() throws IOException {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
destroy();
throw new IOException(Messages.getString("Gnuplot.0"), e); //$NON-NLS-1$
}
}
/**
* 入出力関連の初期化を行います。
*
* @throws IOException gnuplotターミナルの設定に失敗した場合
*/
private void initializeIO() throws IOException {
//this.gnuplotWriter = new BufferedWriter(new OutputStreamWriter(this.gnuplotProcess.getOutputStream()));
this.gnuplotWriter = new BufferedWriter(new OutputStreamWriter(this.gnuplotProcess.getOutputStream(), "UTF-8")); //$NON-NLS-1$
try {
setUpTerminal();
} catch (IOException e) {
this.gnuplotWriter.close();
this.gnuplotWriter = null;
this.gnuplotClosed = true;
destroy();
throw new IOException(Messages.getString("Gnuplot.1"), e); //$NON-NLS-1$
}
}
/**
* キャンバスの初期化を行います。
*/
private void initializeCanvas() {
this.mainCanvas = new Canvas(this);
this.mainCanvas.setFrame(0, 0, 0.98, 1.0);
}
/**
* gnuplot実行環境を取得します。
*
* @return gnuplot実行環境
*/
public Environment getEnvironment() {
return this.environment;
}
/**
* プロセスを起動します。
*
* @param commandList プロセスの起動に用いるコマンド
* @return 生成されたgnuplotプロセス
* @throws FileNotFoundException 作業ディレクトリが見つからない場合
* @throws IOException プロセスの起動に失敗した場合
*/
private Process startProcess(final List<String> commandList) throws FileNotFoundException, IOException {
final ProcessBuilder processBuilder = new ProcessBuilder(commandList);
final File gnuplotWorkingDirectory;
if (this.environment.hasWorkingDirectory()) {
gnuplotWorkingDirectory = this.environment.getWorkingDirectory();
} else {
gnuplotWorkingDirectory = new File("."); //$NON-NLS-1$
}
if (gnuplotWorkingDirectory != null) {
if (gnuplotWorkingDirectory.exists() == false) {
throw new FileNotFoundException(gnuplotWorkingDirectory.toString());
}
processBuilder.directory(gnuplotWorkingDirectory);
}
try {
final Process process = processBuilder.start();
return process;
} catch (IOException e) {
throw new IOException(toConnectedString(commandList), e);
}
}
/**
* Generates the connected string from list of string
*
* @param commandList list of string
* @return connected string from list of string
*/
private String toConnectedString(List<String> commandList) {
final StringBuffer connectedString = new StringBuffer();
for (final String commnad : commandList) {
connectedString.append(commnad);
}
return connectedString.toString();
}
/**
* gnuplot起動コマンドを生成します。
*
* @param options ウィンドウオプション
* @param pid PID
* @return コマンド
*/
private List<String> createCommandList(final String options, final int pid) {
final String gnuplotOptions;
if (System.getenv("GNUPLOT_OPTIONS") != null) { //$NON-NLS-1$
gnuplotOptions = System.getenv("GNUPLOT_OPTIONS") + " " + options; //$NON-NLS-1$ //$NON-NLS-2$
} else {
gnuplotOptions = options;
}
final List<String> commandList = new ArrayList<>();
if (isRunningOnWindows()) {
commandList.add("cmd.exe"); //$NON-NLS-1$
commandList.add("/c"); //$NON-NLS-1$
// final String pathForPgnuplot = getPathOnWindows("pgnuplot.exe"); //$NON-NLS-1$
// if (pathForPgnuplot != null) {
// commandList.add(pathForPgnuplot);
// commandList.add("-"); //$NON-NLS-1$
//
// commandList.add("-geometry"); //$NON-NLS-1$
// commandList.add("380x280"); //$NON-NLS-1$
// commandList.add("-title"); //$NON-NLS-1$
// commandList.add("plot-" + pid); //$NON-NLS-1$
// } else {
final String pathForGnuplot = getPathOnWindows("gnuplot.exe"); //$NON-NLS-1$
if (pathForGnuplot != null) {
commandList.add(pathForGnuplot);
} else {
commandList.add("gnuplot.exe"); //$NON-NLS-1$
}
// }
} else {
commandList.add("gnuplot"); //$NON-NLS-1$
}
if (gnuplotOptions.length() > 0) {
commandList.add(gnuplotOptions);
}
return commandList;
}
/**
* 実行ファイルのパスを取得します。
*
* @param command command
* @return 実行ファイルのパス。見つからない場合はnull
*/
private String getPathOnWindows(String command) {
assert isRunningOnWindows();
// %GNUPLOT_HOME%
if (this.environment.hasHomeDirectory() == false) {
return null;
}
final File home = this.environment.getHomeDirectory();
if (home.exists() == false) {
return null;
}
// %GNUPLOT_HOME%/bin or %GNUPLOT_HOME%/binary
File binDir = new File(home, "bin"); //$NON-NLS-1$
if (binDir.exists() == false) {
binDir = new File(home, "binary"); //$NON-NLS-1$
}
// executable file
final File gnuplotExecutable = new File(binDir, command);
if (gnuplotExecutable.exists() == false) {
return null;
}
return gnuplotExecutable.getAbsolutePath();
}
/**
* gnuplotのターミナルを設定します。
*
* @throws IOException gnuplotへメッセージを送れない場合
*/
private void setUpTerminal() throws IOException {
String plotTerm = System.getenv("GNUPLOT_TERM"); //$NON-NLS-1$
if (plotTerm == null || plotTerm.equals("")) { //$NON-NLS-1$
// if (isRunningOnWindows()) {
// plotTerm = "windows"; //$NON-NLS-1$
// } else {
// plotTerm = "qt"; //$NON-NLS-1$
// }
plotTerm = "qt"; //$NON-NLS-1$
}
sendString("set term " + plotTerm + ";" + System.getProperty("line.separator")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/**
* 動作環境がWindowsOSであるか判定します。
*
* @return 動作環境がWindowsOSならばtrue、そうでなければfalse
*/
private static boolean isRunningOnWindows() {
if (System.getProperty("os.name").startsWith("Windows")) { //$NON-NLS-1$ //$NON-NLS-2$
return true;
}
return false;
}
/**
* コマンドを実行します。
*
* @param command コマンド
*/
public void doCommand(final String command) {
try {
sendString(command + ";" + System.getProperty("line.separator")); //$NON-NLS-1$ //$NON-NLS-2$
} catch (IOException e) {
throw new PlotterException(command, e);
}
}
/**
* 文字列をプロッターに送ります。
*
* @param message メッセージ
* @throws IOException gnuplotへメッセージを送れない場合
*/
private void sendString(final String message) throws IOException {
try {
this.gnuplotWriter.write(message);
this.gnuplotWriter.flush();
} catch (IOException e) {
throw new IOException(message, e);
}
}
/**
* グラフ描画を消去します。
*/
public void clear() {
doCommand("clear"); //$NON-NLS-1$
}
/**
* メインキャンバスを返します。
*
* @return メインキャンバス
*/
public Canvas getCanvas() {
return this.mainCanvas;
}
/**
* 複数キャンバスが使用されているか判定します。
*
* @return 複数のキャンバスが使用されていればtrue、そうでなければfalse
*/
private boolean hasMultipleCanvas() {
return this.hasMultipleCanvas;
}
/**
* リセットします。
*/
public void reset() {
if (hasMultipleCanvas()) {
removeCanvases();
} else {
this.mainCanvas.reset();
}
}
/**
* リセットコードをプロッターに送ります。
*/
void sendResetCode() {
doCommand("unset logscale xyz"); //$NON-NLS-1$
doCommand("set autoscale"); //$NON-NLS-1$
doCommand("unset label"); //$NON-NLS-1$
doCommand("set xlabel"); //$NON-NLS-1$
doCommand("set ylabel"); //$NON-NLS-1$
doCommand("set zlabel"); //$NON-NLS-1$
doCommand("set title"); //$NON-NLS-1$
doCommand("set zero 1e-20"); //$NON-NLS-1$
doCommand("set style data lines"); //$NON-NLS-1$
doCommand("set encoding utf8"); //$NON-NLS-1$
}
/**
* グラフを再描画します。
*/
public void redraw() {
if (isBuffering()) {
return;
}
try {
if (hasMultipleCanvas()) {
drawCanvases();
} else if (this.mainCanvas.getLineSize() != 0) {
drawMainCanvas();
}
} catch (IOException e) {
throw new PlotterException("redraw()", e); //$NON-NLS-1$
}
}
/**
* 'replot'コマンドを実行します。
*/
public void replot() {
if (isBuffering()) {
return;
}
doCommand("replot"); //$NON-NLS-1$
}
/**
* コンポーネントを更新します。
*
* @param canvas コンポーネントが存在するキャンバス
* @param component コンポーネント
*/
void update(final Canvas canvas, final GnuplotComponent component) {
doCommand(canvas.getFrame().getCommand());
doCommand(component.getCommand());
redraw();
}
/**
* エキスポートのモードを設定します。
*
* @param mode モード
*/
private void setExportMode(final String mode) {
String exportTerm;
if (mode.equals("ps")) { //$NON-NLS-1$
exportTerm = "postscript monochrome 'Times-Roman' 16"; //$NON-NLS-1$
} else if (mode.equals("psplus")) { //$NON-NLS-1$
exportTerm = "postscript plus monochrome 'Times-Roman-Mincho' 16"; //$NON-NLS-1$
} else if (mode.equals("eps")) { //$NON-NLS-1$
exportTerm = "postscript eps monochrome 'Times-Roman' 22"; //$NON-NLS-1$
} else if (mode.equals("epsplus")) { //$NON-NLS-1$
exportTerm = "postscript eps plus monochrome 'Times-Roman-Mincho' 22"; //$NON-NLS-1$
} else if (mode.equals("fig")) { //$NON-NLS-1$
exportTerm = "fig monochrome portrait fontsize 16 size 5 3"; //$NON-NLS-1$
} else {
throw new IllegalArgumentException(mode + " is " + Messages.getString("Gnuplot.50")); //$NON-NLS-1$ //$NON-NLS-2$
}
doCommand("set terminal " + exportTerm); //$NON-NLS-1$
}
/**
* 現在持っているグラフ情報を各種ファイルに出力します。
*
* @param fileName 出力ファイル名
* @param mode ファイルの種類{"ps", "eps", "psplus", "epsplus", "fig"}
*/
public void export(final String fileName, final String mode) {
export(fileName, mode, ""); //$NON-NLS-1$
}
/**
* 現在持っているグラフ情報を各種ファイルに出力します。
*
* @param fileName 出力ファイル名
* @param mode ファイルの種類{"ps", "eps", "psplus", "epsplus", "fig"}
* @param command コマンド
*/
public void export(final String fileName, final String mode, final String command) {
if (hasMultipleCanvas()) {
doCommand("unset multiplot"); //$NON-NLS-1$
}
setExportMode(mode);
doCommand("set output '" + fileName + "'"); //$NON-NLS-1$ //$NON-NLS-2$
if (command.length() != 0) {
doCommand(command);
}
try {
drawCanvases();
if (hasMultipleCanvas()) {
doCommand("unset multiplot"); //$NON-NLS-1$
} else {
drawMainCanvas();
}
// reset the window as before
setUpTerminal();
doCommand("set output"); //$NON-NLS-1$
if (hasMultipleCanvas()) {
drawCanvases();
} else {
drawMainCanvas();
}
} catch (IOException e) {
throw new PlotterException(e);
}
}
/**
* キャンバスを描画します。
*
* @throws IOException gnuplotへメッセージを送れない場合
*/
private void drawCanvases() throws IOException {
doCommand("unset multiplot"); //$NON-NLS-1$
for (int i = 0; i < this.canvases.length; i++) {
for (int j = 0; j < this.canvases[0].length; j++) {
final Canvas canvas = this.canvases[i][j];
if (canvas == null) {
continue;
}
final String decorationCommands = canvas.getDecorationCommands();
if (decorationCommands.length() > 0) {
sendString(decorationCommands);
}
}
}
doCommand("set multiplot"); //$NON-NLS-1$
for (int i = 0; i < this.canvases.length; i++) {
for (int j = 0; j < this.canvases[0].length; j++) {
final Canvas canvas = this.canvases[i][j];
if (canvas == null) {
continue;
}
final String decorationCommands = canvas.getDecorationCommands();
if (decorationCommands.length() > 0) {
sendString(decorationCommands);
}
final String plotString = canvas.getPlotString();
if (plotString.length() > 0) {
doCommand("plot " + plotString); //$NON-NLS-1$
}
}
}
}
/**
* メインキャンバスを描画します。
*
* @throws IOException gnuplotへメッセージを送れない場合
*/
private void drawMainCanvas() throws IOException {
final String decorationCommands = this.mainCanvas.getDecorationCommands();
if (decorationCommands.length() > 0) {
sendString(decorationCommands);
}
final String plotString = this.mainCanvas.getPlotString();
if (plotString.length() > 0) {
doCommand("plot " + plotString); //$NON-NLS-1$
}
}
/**
* @see java.lang.Object#finalize()
*/
@Override
protected void finalize() {
close();
}
/**
* グラフ表示を終了します。
*/
public void close() {
if (this.gnuplotClosed) {
return;
}
reset();
if (this.gnuplotWriter != null) {
doCommand("quit"); //$NON-NLS-1$
}
try {
this.gnuplotWriter.close();
} catch (IOException e) {
throw new PlotterException(e);
} finally {
this.gnuplotWriter = null;
this.gnuplotClosed = true;
destroy();
}
}
/**
* グラフを強制終了します。
*/
public void destroy() {
if (this.gnuplotProcess != null) {
try {
this.gnuplotProcess.exitValue();
} catch (@SuppressWarnings("unused") IllegalThreadStateException e) {
this.gnuplotProcess.destroy();
this.gnuplotProcess = null;
}
}
}
/**
* Gnuplotプロセスが起動中であるか判定します。
*
* @return Gnuplotプロセスが起動中ならばtrue、そうでなければfalse
*/
public boolean isRunning() {
if (this.gnuplotProcess != null) {
try {
this.gnuplotProcess.exitValue();
// 終了コードが帰ってくるので起動していない
return false;
} catch (@SuppressWarnings("unused") IllegalThreadStateException e) {
// try {
// replot();
// } catch (@SuppressWarnings("unused") PlotterException e2) {
// return false;
// }
return true;
}
}
return false;
}
/**
* メインキャンバスを返します。
*
* @return メインキャンバス
*/
public Canvas createCanvas() {
if (hasMultipleCanvas()) {
removeCanvases();
doCommand("unset multiplot"); //$NON-NLS-1$
clear();
this.hasMultipleCanvas = false;
doCommand(this.mainCanvas.getFrame().getCommand());
}
return this.mainCanvas;
}
/**
* マルチキャンバスを削除します。
*/
private void removeCanvases() {
int column = 0;
if (0 < this.canvases.length) {
column = this.canvases[0].length;
}
for (int i = 0; i < this.canvases.length; i++) {
for (int j = 0; j < column; j++) {
this.canvases[i][j].reset();
this.canvases[i][j] = null;
}
this.canvases[i] = null;
}
this.canvases = null;
}
/**
* マルチキャンバスを生成します。
*
* @param rowSize 縦方向の分割数
* @param columnSize 横方向の分割数
*/
public void createCanvas(final int rowSize, final int columnSize) {
if (rowSize < 0) {
throw new IllegalArgumentException(Messages.getString("Gnuplot.67")); //$NON-NLS-1$
}
if (columnSize < 0) {
throw new IllegalArgumentException(Messages.getString("Gnuplot.68")); //$NON-NLS-1$
}
if (hasMultipleCanvas()) {
removeCanvases();
} else {
this.mainCanvas.reset();
doCommand("set multiplot"); //$NON-NLS-1$
this.hasMultipleCanvas = true;
}
this.canvases = new Canvas[rowSize][columnSize];
for (int i = 0; i < rowSize; i++) {
for (int j = 0; j < columnSize; j++) {
final double xsize = 1.0 / columnSize;
final double ysize = 1.0 / rowSize;
final double xorg = xsize * j;
final double yorg = ysize * (rowSize - 1 - i);
this.canvases[i][j] = new Canvas(this);
this.canvases[i][j].setFrame(xorg, yorg, 0.98 * xsize, ysize);
}
}
}
/**
* 指定されたキャンバスを返します。
*
* @param row 行番号(0から始まります)
* @param column 列番号(0から始まります)
* @return 指定されたキャンバス
*/
public Canvas getCanvas(final int row, final int column) {
return this.canvases[row][column];
}
/**
* 出力のバッファリングを設定します。
*
* @param buffering バッファリングするならばtrue、そうでなければfalse
*/
public void setBuffering(final boolean buffering) {
this.buffering = buffering;
}
/**
* バッファリングの設定を返します。
*
* @return バッファリングするならばtrue、そうでなければfalse
*/
public boolean isBuffering() {
return this.buffering;
}
}