BodePlotter.java

/*
 * Created on 2007/03/30
 * Copyright (C) 2007 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: 1.7 $, 2007/03/30
 */
public class BodePlotter extends Plotter {

  /** Gnuplot */
  private Gnuplot gnuplot;
  /** ゲイン線図のキャンバス */
  private Canvas gainGraph;
  /** 位相線図のキャンバス */
  private Canvas phaseGraph;

  /** ゲインデータ */
  private DoubleMatrix gains;
  /** 位相データ */
  private DoubleMatrix phases;
  /** 評価する周波数 */
  private DoubleMatrix w;

  /**
   * 新しく生成された<code>BodePlotter</code>オブジェクトを初期化します。
   * 
   * @param gnuplot Gnuplot
   */
  public BodePlotter(final Gnuplot gnuplot) {
    this.gnuplot = gnuplot;

    this.gnuplot.createCanvas(2, 1);
    this.gainGraph = this.gnuplot.getCanvas(0, 0);
    this.phaseGraph = this.gnuplot.getCanvas(1, 0);
  }

  /**
   * ゲイン線図と位相線図を描画します。
   * 
   * @param magnitudePhaseList 大きさと位相のリスト
   * @param angularFrequencies 評価する周波数
   */
  public void plot(final List<List<DoubleMatrix>> magnitudePhaseList, final DoubleMatrix angularFrequencies) {
    final List<DoubleMatrix> magnitudes = new ArrayList<>();
    for (final List<DoubleMatrix> magnitudePhase : magnitudePhaseList) {
      final DoubleMatrix magnitudeColumn = magnitudePhase.get(0);
      magnitudes.add(magnitudeColumn);
    }

    if (this.gainGraph.isKeepingLineProperties() == false) {
      setupLineNameAndType(magnitudes);
    }

    setupGainPhase(magnitudePhaseList, angularFrequencies);
    plotGain();
    plotPhase();

    if (this.gainGraph.isKeepingLineProperties() == false) {
      initializeLineTypes();
    }
  }

  /**
   * ゲインデータと位相データを設定します。
   * 
   * @param magnitudesPhases 大きさと位相のリスト
   * @param angularFrequencies 評価する周波数
   */
  private void setupGainPhase(final List<List<DoubleMatrix>> magnitudesPhases, final DoubleMatrix angularFrequencies) {
    DoubleMatrix localGains = new DoubleMatrix(0, angularFrequencies.length());
    DoubleMatrix localPhases = new DoubleMatrix(0, angularFrequencies.length());

    for (final List<DoubleMatrix> magnitudePhase : magnitudesPhases) {
      final DoubleMatrix magnitudeColumn = magnitudePhase.get(0);
      final DoubleMatrix phaseColumn = magnitudePhase.get(1);

      for (int outputNumber = 1; outputNumber <= magnitudeColumn.getRowSize(); outputNumber++) {
        final DoubleMatrix magnitude = magnitudeColumn.getRowVector(outputNumber);
        final DoubleMatrix phase = phaseColumn.getRowVector(outputNumber);

        if (magnitude.isZero()) {
          continue;
        }

        localGains = localGains.appendDown(magnitude.log10ElementWise().multiply(20));
        localPhases = localPhases.appendDown(phase);
      }
    }

    this.w = angularFrequencies;
    this.gains = localGains;
    this.phases = localPhases;
  }

  /**
   * ゲイン線図を描画します。
   */
  private void plotGain() {
    final double frequencyGridInterval = 10;
    final double frequencyMin = Math.pow(10, Math.floor(Math.log10(this.w.min().doubleValue())));
    final double frequencyMax = Math.pow(10, Math.ceil(Math.log10(this.w.max().doubleValue())));

    double gainGridInterval = Math.ceil(Math.ceil((this.gains.max().doubleValue() - this.gains.min().doubleValue()) / 20) / 6) * 20;
    gainGridInterval = Math.max(gainGridInterval, 10);
    double gainMin = Math.floor(this.gains.min().doubleValue() / gainGridInterval) * gainGridInterval;
    double gainMax = Math.ceil(this.gains.max().doubleValue() / gainGridInterval) * gainGridInterval;

    if (Math.abs(gainMax - gainMin) <= gainGridInterval) {
      if (Math.abs(gainMax) < 1) {
        gainMax = 20;
        gainMin = -40;
      } else {
        if (gainMax < 0) {
          gainMax = 0;
          gainMin = gainMin - 20;
        } else {
          gainMax = gainMax + 20;
          gainMin = 0;
        }
      }

      gainGridInterval = Math.ceil(Math.ceil((gainMax - gainMin) / 20) / 6) * 20;
    }

    this.gainGraph.setYLabel("[dB]", -1.5, 4.0); //$NON-NLS-1$
    this.gainGraph.setGridVisible(true);
    this.gainGraph.setLeftMargin(10);
    this.gainGraph.setXRange(frequencyMin, frequencyMax);
    this.gainGraph.setXTics(frequencyMin, frequencyGridInterval, frequencyMax);
    this.gainGraph.setYRange(gainMin, gainMax);
    this.gainGraph.setYTics(gainMin, gainGridInterval, gainMax);
    this.gainGraph.semilogx(this.w, this.gains, this.lineNames.values().toArray(new String[this.lineNames.size()]));
  }

  /**
   * 位相線図を描画します。
   */
  private void plotPhase() {
    final double frequencyGridInterval = 10;
    final double frequencyMin = Math.pow(10, Math.floor(Math.log10(this.w.min().doubleValue())));
    final double frequencyMax = Math.pow(10, Math.ceil(Math.log10(this.w.max().doubleValue())));

    double phaseGridInterval = Math.ceil(Math.ceil((this.phases.max().doubleValue() - this.phases.min().doubleValue()) / 30) / 6) * 30;
    phaseGridInterval = Math.max(phaseGridInterval, 10);
    double phaseMin = Math.floor(this.phases.min().doubleValue() / phaseGridInterval) * phaseGridInterval;
    double phaseMax = Math.ceil(this.phases.max().doubleValue() / phaseGridInterval) * phaseGridInterval;

    if (Math.abs(phaseMax - phaseMin) <= phaseGridInterval) {
      if (Math.abs(phaseMax) < 1) {
        phaseMax = 90;
        phaseMin = -90;
      } else {
        if (phaseMax < 0) {
          phaseMax = 0;
          phaseMin = phaseMin - 90;
        } else {
          phaseMax = phaseMax + 90;
          phaseMin = 0;
        }
      }

      phaseGridInterval = Math.ceil(Math.ceil((phaseMax - phaseMin) / 30) / 6) * 30;
    }

    this.phaseGraph.setYLabel("[deg]", -1.5, 3.5); //$NON-NLS-1$
    this.phaseGraph.setGridVisible(true);
    this.phaseGraph.setLeftMargin(10);
    this.phaseGraph.setXRange(frequencyMin, frequencyMax);
    this.phaseGraph.setXTics(frequencyMin, frequencyGridInterval, frequencyMax);
    this.phaseGraph.setYRange(phaseMin, phaseMax);
    this.phaseGraph.setYTics(phaseMin, phaseGridInterval, phaseMax);
    this.phaseGraph.semilogx(this.w, this.phases, this.lineNames.values().toArray(new String[this.lineNames.size()]));
  }

  /**
   * @see org.mklab.tool.control.Plotter#setGraphLineWidth(int, int)
   */
  @Override
  public void setGraphLineWidth(final int lineNumber, final int width) {
    if (this.gainGraph != null) {
      this.gainGraph.setLineWidth(lineNumber, width);
    }
    if (this.phaseGraph != null) {
      this.phaseGraph.setLineWidth(lineNumber, width);
    }
  }

  /**
   * @see org.mklab.tool.control.Plotter#getGraphLineWidth(int)
   */
  @Override
  public int getGraphLineWidth(final int lineNumber) {
    return this.gainGraph.getLineWidth(lineNumber);
  }

  /**
   * @see org.mklab.tool.control.Plotter#setGraphLineType(int, int)
   */
  @Override
  public void setGraphLineType(final int lineNumber, final int type) {
    if (this.gainGraph != null) {
      this.gainGraph.setLineType(lineNumber, type);
    }
    if (this.phaseGraph != null) {
      this.phaseGraph.setLineType(lineNumber, type);
    }
  }

  /**
   * @see org.mklab.tool.control.Plotter#getGraphLineType(int)
   */
  @Override
  public int getGraphLineType(final int lineNumber) {
    return this.gainGraph.getLineType(lineNumber);
  }

  /**
   * @see org.mklab.tool.control.Plotter#setGraphLineVisible(int, boolean)
   */
  @Override
  public void setGraphLineVisible(final int lineNumber, final boolean visible) {
    if (this.gainGraph != null) {
      this.gainGraph.setLineVisible(lineNumber, visible);
    }
    if (this.phaseGraph != null) {
      this.phaseGraph.setLineVisible(lineNumber, visible);
    }
  }

  /**
   * @see org.mklab.tool.control.Plotter#isGraphLineVisible(int)
   */
  @Override
  public boolean isGraphLineVisible(final int lineNumber) {
    return this.gainGraph.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.gainGraph != null) {
      this.gainGraph.setLineName(lineNumber, name);
    }
    if (this.phaseGraph != null) {
      this.phaseGraph.setLineName(lineNumber, name);
    }
  }

  /**
   * @see org.mklab.tool.control.Plotter#getGraphLineName(int)
   */
  @Override
  public String getGraphLineName(final int lineNumber) {
    return this.gainGraph.getLineName(lineNumber);
  }

  /**
   * @see org.mklab.tool.control.Plotter#setGraphFontSize(int)
   */
  @Override
  public void setGraphFontSize(final int fontSize) {
    if (this.gainGraph != null) {
      this.gainGraph.setFontSize(fontSize);
    }
    if (this.phaseGraph != null) {
      this.phaseGraph.setFontSize(fontSize);
    }
  }

  /**
   * @see org.mklab.tool.control.Plotter#getGraphFontSize()
   */
  @Override
  public int getGraphFontSize() {
    return this.gainGraph.getFontSize();
  }

  /**
   * 線のプロパティを保存するか設定します。
   * 
   * @param keepingLineProperties 線のプロパティを保存するならばtrue、そうでなければfalse
   */
  public void setKeepingLineProperties(final boolean keepingLineProperties) {
    this.gainGraph.setKeepingLineProperties(keepingLineProperties);
    this.phaseGraph.setKeepingLineProperties(keepingLineProperties);
  }
}