GnuplotSink.java
/*
* Created on 2007/02/05
* Copyright (C) 2007 Koga Laboratory. All rights reserved.
*
*/
package org.mklab.tool.control.system.sink;
import java.io.IOException;
import org.mklab.nfc.matrix.DoubleMatrix;
import org.mklab.tool.control.system.SystemOperatorException;
import org.mklab.tool.control.system.parameter.Parameter;
import org.mklab.tool.control.system.parameter.ParameterAccessException;
import org.mklab.tool.control.system.parameter.ParameterContainer;
import org.mklab.tool.graph.gnuplot.Canvas;
import org.mklab.tool.graph.gnuplot.Gnuplot;
/**
* Gnuplotへの出力器を表わすクラスです。
*
* @author koga
* @version $Revision: 1.35 $, 2007/02/05
*/
public class GnuplotSink extends DoubleMatrixExportSink {
/** Gnuplot */
private Gnuplot gnuplot;
/** キャンバス */
private Canvas canvas;
/** タイトル */
@Parameter(name = "title", description = "GnuplotSink.1", update = true, internationalization = true)
private String title = ""; //$NON-NLS-1$
/** x軸ラベル */
@Parameter(name = "xlabel", description = "GnuplotSink.4", update = true, internationalization = true)
private String xLabel = ""; //$NON-NLS-1$
/** y軸ラベル */
@Parameter(name = "ylabel", description = "GnuplotSink.7", update = true, internationalization = true)
private String yLabel = ""; //$NON-NLS-1$
/** x軸の表示最小値 */
@Parameter(name = "xMinimum", description = "GnuplotSink.10", update = true, internationalization = true)
private double xMinimum;
/** x軸の表示最大値 */
@Parameter(name = "xMaximum", description = "GnuplotSink.12", update = true, internationalization = true)
private double xMaximum;
/** y軸の表示最小値 */
@Parameter(name = "yMinimum", description = "GnuplotSink.14", update = true, internationalization = true)
private double yMinimum;
/** y軸の表示最大値 */
@Parameter(name = "yMaximum", description = "GnuplotSink.16", update = true, internationalization = true)
private double yMaximum;
/** x軸のグリッド間隔 */
@Parameter(name = "xGridInterval", description = "GnuplotSink.18", update = true, internationalization = true)
private double xGridInterval;
/** y軸のグリッド間隔 */
@Parameter(name = "yGridInterval", description = "GnuplotSink.20", update = true, internationalization = true)
private double yGridInterval;
/** グリッドを表示するならばtrue */
@Parameter(name = "grid", description = "GnuplotSink.22", update = true, internationalization = true)
private boolean grid = true;
/** 表示領域やグリッド間隔を自動設定にするならばtrue */
@Parameter(name = "autoScale", description = "GnuplotSink.0", update = true, internationalization = true)
private boolean autoScale = true;
/** 線の名前 */
@Parameter(name = "lineNames", description = "GnuplotSink.24", update = true, internationalization = true)
private String[] lineNames;
/** 線の幅 */
@Parameter(name = "lineWidth", description = "GnuplotSink.25", update = true, internationalization = true)
private int lineWidth = 1;
/** フォントの大きさ */
@Parameter(name = "fontSize", description = "GnuplotSink.26", update = true, internationalization = true)
private int fontSize = 12;
/** 新しい設定条件で再描画するならばtrue */
private boolean justReploting = false;
/**
* 新しく生成された<code>GnuplotSink</code>オブジェクトを初期化します。
*/
public GnuplotSink() {
super();
}
/**
* @see org.mklab.tool.control.system.sink.DoubleMatrixExportSink#open()
*/
@Override
public void open() {
super.open();
if (this.gnuplot != null && this.gnuplot.isRunning()) {
this.gnuplot.close();
}
try {
this.gnuplot = new Gnuplot();
} catch (IOException e) {
throw new SystemOperatorException(e);
}
this.canvas = this.gnuplot.createCanvas();
this.canvas.setGridVisible(this.grid);
this.canvas.setTitle(this.title);
this.canvas.setXLabel(this.xLabel);
this.canvas.setYLabel(this.yLabel);
this.canvas.setFontSize(this.fontSize);
}
/**
* @see org.mklab.tool.control.system.sink.Exporter#close()
*/
public void close() {
if (this.gnuplot != null && this.gnuplot.isRunning()) {
this.gnuplot.close();
}
this.gnuplot = null;
this.canvas = null;
}
/**
* @see org.mklab.tool.control.system.sink.Exporter#isActive()
*/
public boolean isActive() {
if (this.gnuplot == null) {
return false;
}
return this.gnuplot.isRunning();
}
/**
* @see org.mklab.tool.control.system.sink.ContinuousSink#setInputSize(int)
*/
@Override
public void setInputSize(final int inputSize) {
super.setInputSize(inputSize);
if (inputSize == -1) {
return;
}
if (isNameInitializing(inputSize)) {
setupLineNames(inputSize, "y"); //$NON-NLS-1$
try {
setupParameters(this.getClass());
} catch (ParameterAccessException e) {
throw new RuntimeException(e);
}
}
}
/**
* @see org.mklab.tool.control.system.sink.Exporter#exportData()
*/
public void exportData() {
if (isReady() == false) {
return;
}
final DoubleMatrix data = getData();
final DoubleMatrix x = data.getRowVector(1);
final DoubleMatrix y = data.getRowVectors(2, data.getRowSize());
if (isJustReploting() == false && isNameInitializing(y.getRowSize())) {
setupLineNames(y.getRowSize(), "y"); //$NON-NLS-1$
}
exportData(x, y);
}
/**
* 名前を初期化するべきか判定します。
*
* @param lineSize 線の数
* @return 名前を初期化するべきならばtrue、そうでなければfalse
*/
boolean isNameInitializing(final int lineSize) {
return this.lineNames == null || this.lineNames.length != lineSize;
}
/**
* 出力の準備ができているか判定します。
*
* @return 出力の準備ができていればtrue、そうでなければfalse
*/
boolean isReady() {
if (getDataLength() == 0) {
this.justReploting = false;
return false;
}
if (this.canvas == null) {
return false;
}
return true;
}
/**
* データを出力します。
*
* @param x x軸方向のデータ
* @param y y軸方向のデータ
*/
void exportData(final DoubleMatrix x, final DoubleMatrix y) {
this.canvas.setBuffering(true);
this.canvas.plot(x, y, this.lineNames);
this.canvas.setLineWidth(this.lineWidth);
DoubleMatrix finiteY = new DoubleMatrix(y.getRowSize(), y.getColumnSize());
for (int i = 1; i <= y.getColumnSize(); i++) {
for (int j = 1; j <= y.getRowSize(); j++) {
if (y.getElement(j, i).isFinite()) {
finiteY.setElement(j, i, y.getDoubleElement(j, i));
}
}
}
if (this.justReploting == false) {
if (this.autoScale) {
setXMinimum(x.min().doubleValue());
setXMaximum(x.max().doubleValue());
setXGridInterval(getGridIntervalCandidate(this.xMinimum, this.xMaximum));
} else {
setXMinimum(getXMinimum());
setXMaximum(getXMaximum());
setXGridInterval(getXGridInterval());
}
if (this.autoScale) {
setYMinimum(getMinimumCandidate(finiteY));
setYMaximum(getMaximumCandidate(finiteY));
setYGridInterval(getGridIntervalCandidate(this.yMinimum, this.yMaximum));
} else {
setYMinimum(getYMinimum());
setYMaximum(getYMaximum());
setYGridInterval(getYGridInterval());
}
}
this.canvas.setBuffering(false);
this.canvas.redraw();
this.justReploting = false;
}
/**
* 線の名前を決定します。
*
* @param lineSize 線の数
* @param lineName 線の名前
*/
void setupLineNames(final int lineSize, final String lineName) {
final String[] oldLineNames = this.lineNames;
this.lineNames = new String[lineSize];
int oldLineSize = 0;
if (oldLineNames != null) {
oldLineSize = oldLineNames.length;
System.arraycopy(oldLineNames, 0, this.lineNames, 0, Math.min(lineSize, oldLineSize));
}
if (lineSize == 1) {
this.lineNames[0] = lineName;
} else {
for (int i = oldLineSize; i < lineSize; i++) {
this.lineNames[i] = lineName + (i + 1);
}
}
if (this.canvas != null) {
this.canvas.setKeyVisible(true);
}
}
/**
* 自動調節するか設定します。
*
* @param autoScale 自動調節するならばtrue
*/
public void setAutoScale(final boolean autoScale) {
this.autoScale = autoScale;
}
/**
* 自動調節するかを返します。
*
* @return 自動調節するか
*/
public boolean isAutoScale() {
return this.autoScale;
}
/**
* グリッド間隔の候補を返します。
*
* @param minimum 最小値
* @param maximum 最大値
* @return グリッド間隔の候補
*/
private double getGridIntervalCandidate(final double minimum, final double maximum) {
final double maximumScale;
if (maximum == 0) {
maximumScale = 0;
} else {
maximumScale = Math.pow(10, Math.floor(Math.log10(Math.abs(maximum) / 2)));
}
final double minimumScale;
if (minimum == 0) {
minimumScale = 0;
} else {
minimumScale = Math.pow(10, Math.floor(Math.log10(Math.abs(minimum) / 2)));
}
final double scaleCandidate = Math.max(minimumScale, maximumScale);
final double scale = (0 == scaleCandidate) ? 1 : scaleCandidate;
final int gridNumber;
if (Math.signum(minimum) == Math.signum(maximum)) {
gridNumber = (int)(Math.ceil(Math.abs(maximum - minimum) / scale));
} else {
gridNumber = (int)(Math.ceil(Math.abs(minimum) / scale) + Math.ceil(Math.abs(maximum) / scale));
}
if (20 <= gridNumber) {
return scale * 4;
}
if (10 <= gridNumber) {
if (gridNumber % 3 == 0) {
return scale * 3;
}
return scale * 2;
}
if (gridNumber < 2) {
final int expectedGridNumber5 = (int)Math.rint((maximum - minimum) / (scale / 5));
if (expectedGridNumber5 % 5 == 0) {
return scale / 5;
}
final int expectedGridNumber4 = (int)Math.rint((maximum - minimum) / (scale / 4));
if (expectedGridNumber4 >= 4) {
return scale / 4;
}
return scale / 8;
}
if (gridNumber < 4) {
return scale / 2;
}
return scale;
}
/**
* 表示最大値の第一候補を返します。
*
* @param data データ
* @return 表示最大値の第一候補
*/
private double getMaximumFirstCandidate(final DoubleMatrix data) {
final double minimum = data.min().doubleValue();
final double maximum = data.max().doubleValue();
final double scale = getScaleForCandidate(minimum, maximum);
double scaledMagnitude;
if (maximum < 0) {
scaledMagnitude = Math.floor(Math.abs(maximum) / scale);
} else {
scaledMagnitude = Math.ceil(Math.abs(maximum) / scale);
}
if (minimum == maximum) {
scaledMagnitude = scaledMagnitude + 1;
}
return Math.signum(maximum) * scaledMagnitude * scale;
}
/**
* 表示最小値の第一候補を返します。
*
* @param data データ
* @return 表示最小値の第一候補
*/
private double getMinimumFirstCandidate(final DoubleMatrix data) {
final double minimum = data.min().doubleValue();
final double maximum = data.max().doubleValue();
final double scale = getScaleForCandidate(minimum, maximum);
double scaledMagnitude;
if (minimum < 0) {
scaledMagnitude = Math.ceil(Math.abs(minimum) / scale);
} else {
scaledMagnitude = Math.floor(Math.abs(minimum) / scale);
}
if (minimum == maximum) {
scaledMagnitude = scaledMagnitude - 1;
}
return Math.signum(minimum) * scaledMagnitude * scale;
}
/**
* 表示最大値の候補を返します。
*
* @param data データ
* @return 表示最大値の候補
*/
private double getMaximumCandidate(final DoubleMatrix data) {
final double maximumCandidate = getMaximumFirstCandidate(data);
final double minimumCandidate = getMinimumFirstCandidate(data);
final double gridCandidate = getGridIntervalCandidate(minimumCandidate, maximumCandidate);
return Math.signum(maximumCandidate) * Math.ceil(Math.abs(maximumCandidate / gridCandidate)) * gridCandidate;
}
/**
* 表示最小値の候補を返します。
*
* @param data データ
* @return 表示最小値の候補
*/
private double getMinimumCandidate(final DoubleMatrix data) {
final double maximumCandidate = getMaximumFirstCandidate(data);
final double minimumCandidate = getMinimumFirstCandidate(data);
final double gridCandidate = getGridIntervalCandidate(minimumCandidate, maximumCandidate);
return Math.signum(minimumCandidate) * Math.ceil(Math.abs(minimumCandidate / gridCandidate)) * gridCandidate;
}
/**
* 表示最大・最小値の候補を決定するためのスケーリングファクタを返します。
*
* @param minimum 最小値
* @param maximum 最大値
* @return 表示最大・最小値の候補を決定するためのスケーリングファクタ
*/
private double getScaleForCandidate(final double minimum, final double maximum) {
final double minimumMagnitude = Math.abs(minimum);
final double maximumMagnitude = Math.abs(maximum);
final double maximumScale;
if (maximumMagnitude == 0) {
maximumScale = 0.1;
} else {
maximumScale = Math.pow(10, Math.floor(Math.log10(maximumMagnitude))) / 10;
}
final double minimumScale;
if (minimumMagnitude == 0) {
minimumScale = 0.1;
} else {
minimumScale = Math.pow(10, Math.floor(Math.log10(minimumMagnitude))) / 10;
}
final double scale = minimumScale < maximumScale ? maximumScale : minimumScale;
return scale;
}
/**
* タイトルを設定します。
*
* @param title タイトル
*/
public void setTitle(final String title) {
this.title = title;
if (this.canvas != null) {
this.canvas.setTitle(this.title);
}
}
/**
* 線の幅を設定します。
*
* @param lineWidth 線の幅
*/
public void setLineWidth(final int lineWidth) {
this.lineWidth = lineWidth;
if (this.canvas != null) {
this.canvas.setLineWidth(lineWidth);
}
}
/**
* フォントの大きさを設定します。
*
* @param fontSize フォントの大きさ
*/
public void setFontSize(final int fontSize) {
this.fontSize = fontSize;
if (this.canvas != null) {
this.canvas.setFontSize(fontSize);
}
}
/**
* フォントの大きさを返します。
*
* @return フォントの大きさ
*/
public int getFontSize() {
return this.fontSize;
}
/**
* x軸ラベルを設定します。
*
* @param label ラベル
*/
public void setXLabel(final String label) {
this.xLabel = label;
if (this.canvas != null) {
this.canvas.setXLabel(label);
}
}
/**
* y軸ラベルを設定します。
*
* @param label ラベル
*/
public void setYLabel(final String label) {
this.yLabel = label;
if (this.canvas != null) {
this.canvas.setYLabel(label);
}
}
/**
* 線の名前を設定します。
*
* @param index 線の番号
* @param name 線の名前
*/
public void setLineNames(final int index, final String name) {
this.lineNames[index] = name;
//this.justReploting = true;
exportData();
}
/**
* x軸の表示最小値を設定します。
*
* @param minimum x軸の表示最小値
*/
public void setXMinimum(final double minimum) {
this.xMinimum = minimum;
if (this.canvas != null) {
this.canvas.setXRange(this.xMinimum, this.xMaximum);
if (this.autoScale) {
setXGridInterval(getGridIntervalCandidate(this.xMinimum, this.xMaximum));
}
}
}
/**
* x軸の表示最大値を設定します。
*
* @param maximum x軸の表示最大値
*/
public void setXMaximum(final double maximum) {
this.xMaximum = maximum;
if (this.canvas != null) {
this.canvas.setXRange(this.xMinimum, this.xMaximum);
if (this.autoScale) {
setXGridInterval(getGridIntervalCandidate(this.xMinimum, this.xMaximum));
}
}
}
/**
* x軸の表示最大値を返します。
*
* @return x軸の表示最大値
*/
public double getXMaximum() {
return this.xMaximum;
}
/**
* x軸の表示最小値を返します。
*
* @return x軸の表示最小値
*/
public double getXMinimum() {
return this.xMinimum;
}
/**
* y軸の表示最小値を設定します。
*
* @param minimum y軸の表示最小値
*/
public void setYMinimum(final double minimum) {
this.yMinimum = minimum;
if (this.canvas != null) {
this.canvas.setYRange(this.yMinimum, this.yMaximum);
if (this.autoScale) {
setYGridInterval(getGridIntervalCandidate(this.yMinimum, this.yMaximum));
}
}
}
/**
* y軸の表示最大値を設定します。
*
* @param maximum y軸の表示最大値
*/
public void setYMaximum(final double maximum) {
this.yMaximum = maximum;
if (this.canvas != null) {
this.canvas.setYRange(this.yMinimum, this.yMaximum);
if (this.autoScale) {
setYGridInterval(getGridIntervalCandidate(this.yMinimum, this.yMaximum));
}
}
}
/**
* y軸の表示最小値を返します。
*
* @return y軸の表示最小値
*/
public double getYMinimum() {
return this.yMinimum;
}
/**
* y軸の表示最大値を返します。
*
* @return y軸の表示最大値
*/
public double getYMaximum() {
return this.yMaximum;
}
/**
* x軸のグリッド間隔を設定します。
*
* @param interval x軸のグリッド間隔
*/
public void setXGridInterval(final double interval) {
this.xGridInterval = interval;
if (this.canvas != null) {
this.canvas.setXTics(this.xMinimum, this.xGridInterval, this.xMaximum);
}
}
/**
* x軸のグリッド間隔を返します。
*
* @return x軸のグリッド間隔
*/
public double getXGridInterval() {
return this.xGridInterval;
}
/**
* y軸のグリッド間隔を設定します。
*
* @param interval y軸のグリッド間隔
*/
public void setYGridInterval(final double interval) {
this.yGridInterval = interval;
if (this.canvas != null) {
this.canvas.setYTics(this.yMinimum, this.yGridInterval, this.yMaximum);
}
}
/**
* y軸のグリッド間隔を返します。
*
* @return y軸のグリッド間隔
*/
public double getYGridInterval() {
return this.yGridInterval;
}
/**
* グリッドを表示するか設定します。
*
* @param grid グリッドを表示するならばtrue
*/
public void setGrid(final boolean grid) {
this.grid = grid;
if (this.canvas != null) {
this.canvas.setGridVisible(this.grid);
}
}
/**
* Gnuplotを返します。
*
* @return Gnuplot
*/
public Gnuplot getGnuplot() {
return this.gnuplot;
}
/**
* @see org.mklab.tool.control.system.parameter.ParameterUpdator#updateWith(java.lang.String)
*/
@Override
public boolean updateWith(String parameter) {
if (super.updateWith(parameter)) {
return true;
}
if (this.gnuplot != null && this.gnuplot.isRunning() == false) {
open();
this.gnuplot.reset();
}
if (parameter.equals("title")) { //$NON-NLS-1$
setTitle(this.title);
return true;
} else if (parameter.equals("xlabel")) { //$NON-NLS-1$
setXLabel(this.xLabel);
return true;
} else if (parameter.equals("ylabel")) { //$NON-NLS-1$
setYLabel(this.yLabel);
return true;
} else if (parameter.equals("xMinimum")) { //$NON-NLS-1$
setXMinimum(this.xMinimum);
return true;
} else if (parameter.equals("xMaximum")) { //$NON-NLS-1$
setXMaximum(this.xMaximum);
return true;
} else if (parameter.equals("yMinimum")) { //$NON-NLS-1$
setYMinimum(this.yMinimum);
return true;
} else if (parameter.equals("yMaximum")) { //$NON-NLS-1$
setYMaximum(this.yMaximum);
return true;
} else if (parameter.equals("xGridInterval")) { //$NON-NLS-1$
setXGridInterval(this.xGridInterval);
return true;
} else if (parameter.equals("yGridInterval")) { //$NON-NLS-1$
setYGridInterval(this.yGridInterval);
return true;
} else if (parameter.equals("grid")) { //$NON-NLS-1$
setGrid(this.grid);
return true;
} else if (parameter.equals("fontSize")) { //$NON-NLS-1$
setFontSize(this.fontSize);
return true;
} else if (parameter.equals("lineWidth")) { //$NON-NLS-1$
setLineWidth(this.lineWidth);
return true;
} else if (parameter.startsWith("lineNames[")) { //$NON-NLS-1$
final int index = ParameterContainer.getIndexOfArray(parameter);
setLineNames(index, this.lineNames[index]);
return true;
}
return false;
}
/**
* @see org.mklab.tool.control.system.SystemOperator#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!super.equals(o)) {
return false;
}
if (o == null) {
return false;
}
if (o.getClass() != getClass()) {
return false;
}
GnuplotSink castedObj = (GnuplotSink)o;
return ((this.gnuplot == null ? castedObj.gnuplot == null : this.gnuplot.equals(castedObj.gnuplot)) && (this.canvas == null ? castedObj.canvas == null : this.canvas.equals(castedObj.canvas))
&& (this.title == null ? castedObj.title == null : this.title.equals(castedObj.title)) && (this.xLabel == null ? castedObj.xLabel == null : this.xLabel.equals(castedObj.xLabel))
&& (this.yLabel == null ? castedObj.yLabel == null : this.yLabel.equals(castedObj.yLabel)) && (this.xMinimum == castedObj.xMinimum) && (this.xMaximum == castedObj.xMaximum)
&& (this.yMinimum == castedObj.yMinimum) && (this.yMaximum == castedObj.yMaximum) && (this.xGridInterval == castedObj.xGridInterval) && (this.yGridInterval == castedObj.yGridInterval)
&& (this.grid == castedObj.grid) && java.util.Arrays.equals(this.lineNames, castedObj.lineNames) && (this.justReploting == castedObj.justReploting));
}
/**
* @see org.mklab.tool.control.system.SystemOperator#hashCode()
*/
@Override
public int hashCode() {
int hashCode = super.hashCode();
hashCode = 31 * hashCode + (this.gnuplot == null ? 0 : this.gnuplot.hashCode());
hashCode = 31 * hashCode + (this.canvas == null ? 0 : this.canvas.hashCode());
hashCode = 31 * hashCode + (this.title == null ? 0 : this.title.hashCode());
hashCode = 31 * hashCode + (this.xLabel == null ? 0 : this.xLabel.hashCode());
hashCode = 31 * hashCode + (this.yLabel == null ? 0 : this.yLabel.hashCode());
hashCode = 31 * hashCode + (int)(Double.doubleToLongBits(this.xMinimum) ^ (Double.doubleToLongBits(this.xMinimum) >>> 32));
hashCode = 31 * hashCode + (int)(Double.doubleToLongBits(this.xMaximum) ^ (Double.doubleToLongBits(this.xMaximum) >>> 32));
hashCode = 31 * hashCode + (int)(Double.doubleToLongBits(this.yMinimum) ^ (Double.doubleToLongBits(this.yMinimum) >>> 32));
hashCode = 31 * hashCode + (int)(Double.doubleToLongBits(this.yMaximum) ^ (Double.doubleToLongBits(this.yMaximum) >>> 32));
hashCode = 31 * hashCode + (int)(Double.doubleToLongBits(this.xGridInterval) ^ (Double.doubleToLongBits(this.xGridInterval) >>> 32));
hashCode = 31 * hashCode + (int)(Double.doubleToLongBits(this.yGridInterval) ^ (Double.doubleToLongBits(this.yGridInterval) >>> 32));
hashCode = 31 * hashCode + (this.grid ? 1231 : 1237);
for (int i0 = 0; this.lineNames != null && i0 < this.lineNames.length; i0++) {
hashCode = 31 * hashCode + (this.lineNames == null ? 0 : this.lineNames[i0].hashCode());
}
hashCode = 31 * hashCode + (this.justReploting ? 1231 : 1237);
return hashCode;
}
/**
* 新しい設定条件で再描画するか判定します。
*
* @return 新しい設定条件で再描画するならばtrue
*/
boolean isJustReploting() {
return this.justReploting;
}
}