NyquistPlotter.java
/*
* Created on 2008/12/05
* Copyright (C) 2008 Koga Laboratory. All rights reserved.
*
*/
package org.mklab.tool.control;
import java.util.ArrayList;
import java.util.List;
import org.mklab.nfc.matrix.DoubleMatrix;
import org.mklab.tool.graph.gnuplot.Canvas;
import org.mklab.tool.graph.gnuplot.Gnuplot;
/**
* ナイキスト線図を描画するクラスです。
*
* @author koga
* @version $Revision$, 2008/12/05
*/
public class NyquistPlotter extends Plotter {
/** Gnuplot */
private Gnuplot gnuplot;
/** キャンバス */
private Canvas graph;
/** 実部データ */
private DoubleMatrix reals;
/** 虚部データ */
private DoubleMatrix imags;
/** 単位円を描くならばtrue */
private boolean isDrawingUnitCircle = true;
/** (-1,0)点を描くならばtrue */
private boolean isDrawingMinusOneZeroPoint = true;
/** 実軸を描くならばtrue */
private boolean isDrawingRealAxis = false;
/** 虚軸を描くならばtrue */
private boolean isDrawingImaginaryAxis = false;
/**
* 新しく生成された<code>NyquistPlotter</code>オブジェクトを初期化します。
*
* @param gnuplot Gnuplot
*/
public NyquistPlotter(final Gnuplot gnuplot) {
this.gnuplot = gnuplot;
this.graph = this.gnuplot.createCanvas();
}
/**
* ナイキスト線図をプロットします。
*
* @param realImagList 実部と虚部
*/
public void plot(List<List<DoubleMatrix>> realImagList) {
final List<DoubleMatrix> magnitudes = new ArrayList<>();
for (final List<DoubleMatrix> realImag : realImagList) {
final DoubleMatrix realColumn = realImag.get(0);
final DoubleMatrix imagColumn = realImag.get(1);
magnitudes.add(realColumn.powerElementWise(2).add(imagColumn.powerElementWise(2)).sqrtElementWise());
}
if (this.graph.isKeepingLineProperties() == false) {
setupLineNameAndType(magnitudes);
}
setupRealImag(realImagList);
plotNyquist();
if (this.graph.isKeepingLineProperties() == false) {
initializeLineTypes();
}
}
/**
* ナイキスト線図を描画します。
*/
private void plotNyquist() {
final double xInterval = Math.abs(this.reals.max().doubleValue() - this.reals.min().doubleValue());
final double xScaling = Math.pow(10, -Math.floor(Math.log10(xInterval)));
final double xGridInterval = Math.ceil(xInterval * xScaling / 6) / xScaling;
final double xMin = Math.floor(this.reals.min().doubleValue() / xGridInterval) * xGridInterval;
final double xMax = Math.ceil(this.reals.max().doubleValue() / xGridInterval) * xGridInterval;
final double yInterval = Math.abs(this.imags.max().doubleValue() - this.imags.min().doubleValue());
final double yScaling = Math.pow(10, -Math.floor(Math.log10(yInterval)));
final double yGridInterval = Math.ceil(yInterval * yScaling / 6) / yScaling;
final double yMin = Math.floor(this.imags.min().doubleValue() / yGridInterval) * yGridInterval;
final double yMax = Math.ceil(this.imags.max().doubleValue() / yGridInterval) * yGridInterval;
this.graph.setXLabel("Re"); //$NON-NLS-1$
this.graph.setYLabel("Im"); //$NON-NLS-1$
this.graph.setGridVisible(true);
this.graph.setTitle("Nyquist Plot"); //$NON-NLS-1$
this.graph.setXRange(xMin, xMax);
this.graph.setXTics(xMin, xGridInterval, xMax);
this.graph.setYRange(yMin, yMax);
this.graph.setYTics(yMin, yGridInterval, yMax);
this.graph.plot(this.reals, this.imags, this.lineNames.values().toArray(new String[this.lineNames.size()]));
}
/**
* 実部データと虚部データを設定します。
*
* @param realImagList 実部データと虚部データ
*/
private void setupRealImag(List<List<DoubleMatrix>> realImagList) {
DoubleMatrix localReals = new DoubleMatrix(0, 0);
DoubleMatrix localImags = new DoubleMatrix(0, 0);
double realMin = Double.POSITIVE_INFINITY;
double realMax = Double.NEGATIVE_INFINITY;
double imaginaryMin = Double.POSITIVE_INFINITY;
double imaginaryMax = Double.NEGATIVE_INFINITY;
for (List<DoubleMatrix> realImag : realImagList) {
final DoubleMatrix real = realImag.get(0);
final DoubleMatrix imag = realImag.get(1);
for (int outputNumber = 1; outputNumber <= real.getRowSize(); outputNumber++) {
final DoubleMatrix re = real.getRowVector(outputNumber);
final DoubleMatrix im = imag.getRowVector(outputNumber);
if (re.isZero() && im.isZero()) {
continue;
}
if (localReals.getRowSize() == 0 && localImags.getRowSize() == 0) {
localReals = re;
localImags = im;
} else {
localReals = localReals.appendDown(re);
localImags = localImags.appendDown(im);
}
realMin = Math.min(realMin, re.min().doubleValue());
realMax = Math.max(realMax, re.max().doubleValue());
imaginaryMin = Math.min(imaginaryMin, im.min().doubleValue());
imaginaryMax = Math.max(imaginaryMax, im.max().doubleValue());
}
}
this.reals = localReals;
this.imags = localImags;
if (this.isDrawingMinusOneZeroPoint) {
drawMinusOneZeroPoint(realMin, realMax, imaginaryMin, imaginaryMax);
}
if (this.isDrawingUnitCircle) {
drawUnitCircle();
}
if (this.isDrawingRealAxis) {
drawRealAxis(realMin, realMax);
}
if (this.isDrawingImaginaryAxis) {
drawImaginaryAxis(imaginaryMin, imaginaryMax);
}
}
/**
* (-1,0)の点を描画します。
*
* @param realMin 実部の最小値
* @param realMax 実部の最大値
* @param imagMin 虚部の最小値
* @param imagMax 虚部の最大値
*/
private void drawMinusOneZeroPoint(final double realMin, final double realMax, final double imagMin, final double imagMax) {
final double maxRadius = 0.01;
final double radius = Math.min(Math.min(realMax - realMin, imagMax - imagMin) / 100, maxRadius);
final double centerX = -1;
final double centerY = 0;
drawCircle(centerX, centerY, radius);
}
/**
* 単位円を描画します。
*/
private void drawUnitCircle() {
final double radius = 1;
final double centerX = 0;
final double centerY = 0;
drawCircle(centerX, centerY, radius);
}
/**
* 円を描画します。
*
* @param centerX 円の中心のx座標
* @param centerY 円の中心のy座標
* @param radius 円の半径
*/
private void drawCircle(final double centerX, final double centerY, final double radius) {
final int size = this.reals.getColumnSize();
final DoubleMatrix localReal = new DoubleMatrix(1, size);
final DoubleMatrix localImag = new DoubleMatrix(1, size);
for (int i = 0; i < size; i++) {
final double theta = 2 * Math.PI / (size - 1) * i;
localReal.setElement(1, i + 1, centerX + radius * Math.cos(theta));
localImag.setElement(1, i + 1, centerY + radius * Math.sin(theta));
}
final int lineSize = this.reals.getRowSize();
this.reals = this.reals.appendDown(localReal);
this.imags = this.imags.appendDown(localImag);
final SystemInputOutputKey key = new SystemInputOutputKey(lineSize + 1, lineSize + 1);
this.lineNames.put(key, ""); //$NON-NLS-1$
this.lineNumbers.put(key, Integer.valueOf(lineSize + 1));
}
/**
* 実軸を描画します。
*
* @param min 実部データの最小値
* @param max 実部データの最大値
*/
private void drawRealAxis(final double min, final double max) {
double reMin = min;
double reMax = max;
if (0.1 < Math.abs(reMax) || 0.1 < Math.abs(reMin)) {
if (reMax < 1) {
reMax = 1;
} else if (100 < reMax) {
reMax = 100;
} else {
reMax = Math.ceil(reMax / 10) * 10;
}
if (-1 < reMin) {
reMin = -2;
} else if (reMin < -100) {
reMin = -100;
} else {
reMin = Math.floor(reMin / 10) * 10;
}
}
// Real axis
this.graph.setHolding(true);
this.graph.setXRange(reMin, reMax);
final double grid = Math.pow(10, Math.floor(Math.log10((reMax - reMin) / 10))) * 2;
this.graph.setXTics(reMin, grid, reMax);
this.graph.plot(new DoubleMatrix(new double[] {reMin, reMax}), new DoubleMatrix(1, 2), new String[] {""}, new String[0], new String[] {"with lines 1 0"}); //$NON-NLS-1$ //$NON-NLS-2$
this.graph.setHolding(false);
}
/**
* 虚軸を描画します。
*
* @param min 虚部データの最小値
* @param max 虚部データの最大値
*/
private void drawImaginaryAxis(final double min, final double max) {
double imMin = min;
double imMax = max;
if (0.1 < Math.abs(imMax) || 0.1 < Math.abs(imMin)) {
if (imMax < 1) {
imMax = 1;
} else if (100 < imMax) {
imMax = 100;
} else {
imMax = Math.ceil(imMax / 10) * 10;
}
if (-1 < imMin) {
imMin = -1;
} else if (imMin < -100) {
imMin = -100;
} else {
imMin = Math.floor(imMin / 10) * 10;
}
}
// Imaginary axis
this.graph.setHolding(true);
this.graph.setYRange(imMin, imMax);
final double grid = Math.pow(10, Math.floor(Math.log10((imMax - imMin) / 10))) * 2;
this.graph.setYTics(imMin, grid, imMax);
this.graph.plot(new DoubleMatrix(1, 2), new DoubleMatrix(new double[] {-imMax, imMax}), new String[] {""}, new String[0], new String[] {"with lines 1 0"}); //$NON-NLS-1$ //$NON-NLS-2$
this.graph.setHolding(false);
}
/**
* @see org.mklab.tool.control.Plotter#setGraphLineWidth(int, int)
*/
@Override
public void setGraphLineWidth(final int lineNumber, final int width) {
if (this.graph != null) {
this.graph.setLineWidth(lineNumber, width);
}
}
/**
* @see org.mklab.tool.control.Plotter#getGraphLineWidth(int)
*/
@Override
public int getGraphLineWidth(final int lineNumber) {
return this.graph.getLineWidth(lineNumber);
}
/**
* @see org.mklab.tool.control.Plotter#setGraphLineType(int, int)
*/
@Override
public void setGraphLineType(final int lineNumber, final int type) {
if (this.graph != null) {
this.graph.setLineType(lineNumber, type);
}
}
/**
* @see org.mklab.tool.control.Plotter#getGraphLineType(int)
*/
@Override
public int getGraphLineType(final int lineNumber) {
return this.graph.getLineType(lineNumber);
}
/**
* @see org.mklab.tool.control.Plotter#setGraphLineVisible(int, boolean)
*/
@Override
public void setGraphLineVisible(final int lineNumber, final boolean visible) {
if (this.graph != null) {
this.graph.setLineVisible(lineNumber, visible);
}
}
/**
* @see org.mklab.tool.control.Plotter#isGraphLineVisible(int)
*/
@Override
public boolean isGraphLineVisible(final int lineNumber) {
return this.graph.isLineVisible(lineNumber);
}
/**
* @see org.mklab.tool.control.Plotter#setGraphLineName(int, java.lang.String)
*/
@Override
public void setGraphLineName(final int lineNumber, final String name) {
if (this.graph != null) {
this.graph.setLineName(lineNumber, name);
}
}
/**
* @see org.mklab.tool.control.Plotter#getGraphLineName(int)
*/
@Override
public String getGraphLineName(final int lineNumber) {
return this.graph.getLineName(lineNumber);
}
/**
* @see org.mklab.tool.control.Plotter#setGraphFontSize(int)
*/
@Override
public void setGraphFontSize(final int fontSize) {
if (this.graph != null) {
this.graph.setFontSize(fontSize);
}
}
/**
* @see org.mklab.tool.control.Plotter#getGraphFontSize()
*/
@Override
public int getGraphFontSize() {
return this.graph.getFontSize();
}
/**
* 線のプロパティを保存するか設定します。
*
* @param isKeeping 線のプロパティを保存するならばtrue、そうでなければfalse
*/
public void setKeepingLineProperties(final boolean isKeeping) {
this.graph.setKeepingLineProperties(isKeeping);
}
/**
* 単位円を描くか判定します。
*
* @return 単位円を描くならばtrue
*/
public boolean isDrawingUnitCircle() {
return this.isDrawingUnitCircle;
}
/**
* 単位円を描くから設定します。
*
* @param isDrawing 単位円を描くならばtrue
*/
public void setDrawingUnitCircle(final boolean isDrawing) {
this.isDrawingUnitCircle = isDrawing;
}
/**
* (-1,0)点を描くか判定します。
*
* @return (-1,0)点を描くならばtrue
*/
public boolean isDrawingMinusOneZeroPoint() {
return this.isDrawingMinusOneZeroPoint;
}
/**
* (-1,0)点を描くから設定します。
*
* @param isDrawing (-1,0)点を描くならばtrue
*/
public void setDrawingMinusOneZeroPoint(final boolean isDrawing) {
this.isDrawingMinusOneZeroPoint = isDrawing;
}
}