Canvas.java

/*
 * Created on 2005/07/29
 * Copyright (C) 2005 Koga Laboratory. All rights reserved.
 *
 */
package org.mklab.tool.graph.gnuplot;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.mklab.nfc.matrix.DoubleMatrix;
import org.mklab.tool.graph.AbstractPlotter;
import org.mklab.tool.graph.PlotterException;
import org.mklab.tool.graph.gnuplot.decoration.ButtomMargin;
import org.mklab.tool.graph.gnuplot.decoration.DataStyle;
import org.mklab.tool.graph.gnuplot.decoration.FontSize;
import org.mklab.tool.graph.gnuplot.decoration.Frame;
import org.mklab.tool.graph.gnuplot.decoration.GnuplotComponent;
import org.mklab.tool.graph.gnuplot.decoration.Grid;
import org.mklab.tool.graph.gnuplot.decoration.Hidden3d;
import org.mklab.tool.graph.gnuplot.decoration.Key;
import org.mklab.tool.graph.gnuplot.decoration.LeftMargin;
import org.mklab.tool.graph.gnuplot.decoration.LineType;
import org.mklab.tool.graph.gnuplot.decoration.LineWidth;
import org.mklab.tool.graph.gnuplot.decoration.LogScale;
import org.mklab.tool.graph.gnuplot.decoration.Parametric;
import org.mklab.tool.graph.gnuplot.decoration.RightMargin;
import org.mklab.tool.graph.gnuplot.decoration.TextLabel;
import org.mklab.tool.graph.gnuplot.decoration.TitleLabel;
import org.mklab.tool.graph.gnuplot.decoration.TopMargin;
import org.mklab.tool.graph.gnuplot.decoration.ViewPoint;
import org.mklab.tool.graph.gnuplot.decoration.X2Label;
import org.mklab.tool.graph.gnuplot.decoration.X2Tics;
import org.mklab.tool.graph.gnuplot.decoration.XLabel;
import org.mklab.tool.graph.gnuplot.decoration.XRange;
import org.mklab.tool.graph.gnuplot.decoration.XTics;
import org.mklab.tool.graph.gnuplot.decoration.Y2Label;
import org.mklab.tool.graph.gnuplot.decoration.Y2Tics;
import org.mklab.tool.graph.gnuplot.decoration.YLabel;
import org.mklab.tool.graph.gnuplot.decoration.YRange;
import org.mklab.tool.graph.gnuplot.decoration.YTics;
import org.mklab.tool.graph.gnuplot.decoration.ZLabel;
import org.mklab.tool.graph.gnuplot.decoration.ZRange;
import org.mklab.tool.graph.gnuplot.decoration.ZTics;


/**
 * グラフを描画するキャンバスを表すクラスです。
 * 
 * @author koga
 * @version $Revision: 1.33 $, 2005/07/29
 */
public class Canvas extends AbstractPlotter {

  /** Gnuplot */
  private Gnuplot gnuplot;
  /** 重ね書きをする場合true */
  private boolean holding = false;
  /** 線の数 */
  private int lineSize = 0;
  /** プロットに関する設定を行う文字列 */
  private List<String> plotStrings = new ArrayList<>();
  /** プロットに関する設定を行う文字列(削除された文字列) */
  private List<String> removedPlotStrings = new ArrayList<>();

  /** フレーム情報(原点のx座標、y座標、幅、高さ) */
  private Frame frame = new Frame(0, 0, 0.98, 1.0);
  /** xラベル */
  private XLabel xLabel = new XLabel();
  /** x2ラベル */
  private X2Label x2Label = new X2Label();
  /** Yラベル */
  private YLabel yLabel = new YLabel();
  /** Y2ラベル */
  private Y2Label y2Label = new Y2Label();
  /** zラベル */
  private ZLabel zLabel = new ZLabel();
  /** テキストラベル */
  private TextLabel textLabel = new TextLabel();
  /** タイトル */
  private TitleLabel title = new TitleLabel();
  /** x方向に関する描画範囲 */
  private XRange xRange = new XRange(0, 1);
  /** y方向に関する描画範囲 */
  private YRange yRange = new YRange(0, 1);
  /** z方向に関する描画範囲 */
  private ZRange zRange = new ZRange(0, 1);
  /** 上マージン */
  private TopMargin topMargin = new TopMargin();
  /** 下マージン */
  private ButtomMargin buttomMargin = new ButtomMargin();
  /** 左マージン */
  private LeftMargin leftMargin = new LeftMargin();
  /** 右マージン */
  private RightMargin rightMargin = new RightMargin();
  /** x軸の目盛りの刻み位置 */
  private XTics xTics = new XTics(0, 0.1, 1);
  /** y軸の目盛りの刻み位置 */
  private YTics yTics = new YTics(0, 0.1, 1);
  /** X2軸の目盛りの刻み位置 */
  private X2Tics x2Tics = new X2Tics(0, 0.1, 1);
  /** Y2軸の目盛りの刻み位置 */
  private Y2Tics y2Tics = new Y2Tics(0, 0.1, 1);
  /** z軸の目盛りの刻み位置 */
  private ZTics zTics = new ZTics(0, 0.1, 1);
  /** 視点 */
  private ViewPoint viewPoint = new ViewPoint();
  /** グラフのスタイル */
  private DataStyle dataStyle = new DataStyle();
  /** フォントの大きさ */
  private FontSize fontSize = new FontSize();

  /** 線のプロパティを保存するならばtrue、そうでなければfalse */
  private boolean keepingLineProperties = false;
  /** 線の幅 */
  private List<LineWidth> lineWidthes = new ArrayList<>();
  /** 線の名前 */
  private List<String> lineNames = new ArrayList<>();
  /** 線のタイプ */
  private List<LineType> lineTypes = new ArrayList<>();
  /** 線の表示・非表示 */
  private List<Boolean> lineVisibles = new ArrayList<>();

  /** グリッド */
  private Grid grid = new Grid(false);
  /** キー */
  private Key key = new Key(false);
  /** 媒介変数描画設定 */
  private Parametric parametric = new Parametric(false);
  /** 3次元陰線処理設定 */
  private Hidden3d hidden3d = new Hidden3d(false);
  /** 全ての軸のログスケール */
  private LogScale logScale = new LogScale();

  /** ロングファイル名が使えるならばtrue */
  private boolean possibleLongFilename = true;

  /** 装飾のマップ */
  private Map<Class<? extends GnuplotComponent>, GnuplotComponent> decorations = new HashMap<>();

  /** 改行コード */
  private static final String newLine = System.getProperty("line.separator"); //$NON-NLS-1$

  /** 色の選択肢 */
  private String[] colors = new String[] {"red", "green", "blue", "orange", "cyan", "yellow", "black"}; //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$

  /**
   * コンストラクター
   * 
   * @param gnuplot Gnuplot
   */
  public Canvas(final Gnuplot gnuplot) {
    this.gnuplot = gnuplot;
  }

  /**
   * リセットします。
   */
  public void reset() {
    removeDataFile();
    doCommand(this.frame.getCommand());
    this.gnuplot.sendResetCode();

    this.lineSize = 0;
    resetLineProperties();
    this.decorations = new HashMap<>();
    this.plotStrings = new ArrayList<>();
    this.frame = new Frame(0, 0, 0.98, 1.0);
    this.xLabel = new XLabel();
    this.x2Label = new X2Label();
    this.yLabel = new YLabel();
    this.y2Label = new Y2Label();
    this.zLabel = new ZLabel();
    this.textLabel = new TextLabel();
    this.title = new TitleLabel();
    this.xRange = new XRange(0, 1);
    this.yRange = new YRange(0, 1);
    this.zRange = new ZRange(0, 1);
    this.topMargin = new TopMargin();
    this.buttomMargin = new ButtomMargin();
    this.leftMargin = new LeftMargin();
    this.rightMargin = new RightMargin();
    this.xTics = new XTics(0, 0.1, 1);
    this.yTics = new YTics(0, 0.1, 1);
    this.x2Tics = new X2Tics(0, 0.1, 1);
    this.y2Tics = new Y2Tics(0, 0.1, 1);
    this.zTics = new ZTics(0, 0.1, 1);
    this.viewPoint = new ViewPoint();
  }

  /**
   * 線の属性をクリアーします。
   */
  private void resetLineProperties() {
    this.lineNames.clear();
    this.lineTypes.clear();
    this.lineWidthes.clear();
    this.lineVisibles.clear();
  }

  /**
   * テキスト文字列を配置します。
   * 
   * @param text テキスト文字列
   * @param x x座標
   * @param y y座標
   */
  public void setText(String text, double x, double y) {
    setText(text, x, y, ""); //$NON-NLS-1$
  }

  /**
   * テキスト文字列を配置し、コマンドを実行します。
   * 
   * @param text テキスト文字列
   * @param x x座標
   * @param y y座標
   * @param attribute テキスト文字の属性
   */
  public void setText(String text, double x, double y, String attribute) {
    if (new TextLabel(text, x, y, attribute).equals(this.textLabel)) {
      return;
    }
    
    this.textLabel.setLabel(text, x, y, attribute);
    addDecoration(this.textLabel);
    if (isHolding() == false) {
      this.gnuplot.doCommand(this.textLabel.getCommand());
      this.gnuplot.replot();
    }
  }

  /**
   * タイトルの設定をします。
   * 
   * @param text タイトル表示テキスト
   */
  public void setTitle(String text) {
    setTitle(text, 0, 0);
  }

  /**
   * タイトルをデフォルト表示位置からx方向に<code>xOffset</code>、y方向に <code>yOffset</code> の場所に表示します。
   * 
   * @param title タイトル
   * @param xOffset 表示位置のx方向オフセット
   * @param yOffset 表示位置のy方向オフセット
   */
  public void setTitle(String title, int xOffset, int yOffset) {
    if (new TitleLabel(title, xOffset, yOffset).equals(this.title)) {
      return;
    }
    
    this.title.setLabel(title, xOffset, yOffset);
    addDecoration(this.title);
    this.gnuplot.doCommand(this.title.getCommand());
    this.gnuplot.replot();
  }

  /**
   * グラフのスタイルを設定します。
   * 
   * @param style スタイル
   */
  public void setDataStyle(String style) {
    if (this.dataStyle.getStyle().equals(style)) {
      return;
    }
    
    this.dataStyle.setStyle(style);
    addDecoration(this.dataStyle);
    this.gnuplot.doCommand(this.dataStyle.getCommand());
    this.gnuplot.replot();
  }

  /**
   * フォントの大きさを設定します。
   * 
   * @param fontSize フォントの大きさ
   */
  public void setFontSize(final int fontSize) {
    if (this.fontSize.getFontSize() == fontSize) {
      return;
    }
    
    this.fontSize.setFontSize(fontSize);
    addDecoration(this.fontSize);
    this.gnuplot.doCommand(this.fontSize.getCommand());
    this.gnuplot.replot();
  }

  /**
   * フォントの大きさを返します。
   * 
   * @return フォントの大きさ
   */
  public int getFontSize() {
    return this.fontSize.getFontSize();
  }

  /**
   * 線の幅を設定します。
   * 
   * @param width 線の幅
   */
  public void setLineWidth(final int width) {
    boolean changed = false;
    
    for (int number = 1; number <= this.lineWidthes.size(); number++) {
      final LineWidth lineWidth = this.lineWidthes.get(number - 1);
      if (lineWidth.getLineWidth() == width) {
        continue;
      }
      lineWidth.setLineWidth(width);
      final String lineWidthString = "linewidth " + width; //$NON-NLS-1$
      final String newPlotString = this.plotStrings.get(number - 1).toString().replaceAll("linewidth \\d+", lineWidthString); //$NON-NLS-1$
      this.plotStrings.set(number - 1, newPlotString);
      
      changed = true;
    }

    if (changed) {
      this.gnuplot.redraw();
    }
  }

  /**
   * 線の幅を設定します。
   * 
   * @param number 線の番号
   * @param width 線の幅
   */
  public void setLineWidth(final int number, final int width) {
    final LineWidth lineWidth = this.lineWidthes.get(number - 1);
    if (lineWidth.getLineWidth() == width) {
      return;
    }
    lineWidth.setLineWidth(width);
    final String lineWidthString = "linewidth " + width; //$NON-NLS-1$
    final String newPlotString = this.plotStrings.get(number - 1).toString().replaceAll("linewidth \\d+", lineWidthString); //$NON-NLS-1$
    this.plotStrings.set(number - 1, newPlotString);

    this.gnuplot.redraw();
  }

  /**
   * 線の幅を返します。
   * 
   * @param number 線の番号
   * @return 線の幅
   */
  public int getLineWidth(final int number) {
    if (number < 0 || this.lineWidthes.size() < number) {
      return 1;
    }

    return this.lineWidthes.get(number - 1).getLineWidth();
  }

  /**
   * 線の名前を設定します。
   * 
   * @param number 線の番号
   * @param name 線の名前
   */
  public void setLineName(final int number, final String name) {
    if (this.lineNames.get(number -1).equals(name)) {
      return;
    }
    
    this.lineNames.set(number - 1, name);
    final String lineNameString = "title '" + name + "'"; //$NON-NLS-1$//$NON-NLS-2$
    final String newPlotString = this.plotStrings.get(number - 1).toString().replaceAll("title '[^']*'", lineNameString); //$NON-NLS-1$
    this.plotStrings.set(number - 1, newPlotString);

    final String newRemovedPlotString = this.removedPlotStrings.get(number - 1).toString().replaceAll("title '[^']*'", lineNameString); //$NON-NLS-1$
    this.removedPlotStrings.set(number - 1, newRemovedPlotString);
    this.gnuplot.redraw();
  }

  /**
   * 線の名前を返します。
   * 
   * @param number 線の番号
   * @return 線の名前
   */
  public String getLineName(final int number) {
    if (number < 0 || this.lineNames.size() < number) {
      return ""; //$NON-NLS-1$
    }
    return this.lineNames.get(number - 1);
  }

  /**
   * 線のタイプ(色)を設定します。
   * 
   * @param number 線の番号
   * @param type 線のタイプ(色)
   */
  public void setLineType(final int number, final int type) {
    final LineType lineType = this.lineTypes.get(number - 1);
    
    if (lineType.getLineType() == type) {
      return;
    }
    
    lineType.setLineType(type);
    final String lineTypeString = "linetype rgb " + "\"" + this.colors[type] + "\""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    final String newPlotString = this.plotStrings.get(number - 1).toString().replaceAll("linetype rgb \"\\w+\"", lineTypeString); //$NON-NLS-1$
    this.plotStrings.set(number - 1, newPlotString);

    this.gnuplot.redraw();

  }

  /**
   * 線のタイプ(色)を返します。
   * 
   * @param number 線の番号
   * @return 線のタイプ(色)
   */
  public int getLineType(final int number) {
    if (number < 0 || this.lineTypes.size() < number) {
      return 1;
    }
    return this.lineTypes.get(number - 1).getLineType();
  }

  /**
   * 線の表示・非表示を設定します。
   * 
   * @param number 線の番号
   * @param visible 線の表示・非表示
   */
  public void setLineVisible(final int number, final boolean visible) {
    if (this.lineVisibles.get(number - 1).booleanValue() == visible) {
      return;
    }
    this.lineVisibles.set(number - 1, Boolean.valueOf(visible));
    if (visible == false) {
      this.removedPlotStrings.set(number - 1, this.plotStrings.get(number - 1));
      this.plotStrings.set(number - 1, ""); //$NON-NLS-1$
    } else {
      this.plotStrings.set(number - 1, this.removedPlotStrings.get(number - 1));
    }
    this.gnuplot.redraw();
  }

  /**
   * 線の表示・非表示を返します。
   * 
   * @param number 線の番号
   * @return 線の表示・非表示
   */
  public boolean isLineVisible(final int number) {
    if (number < 0 || this.lineVisibles.size() < number) {
      return false;
    }
    return this.lineVisibles.get(number - 1).booleanValue();
  }

  /**
   * x軸のラベルをデフォルトの表示位置に表示します。
   * 
   * @param xLabel x軸のラベル
   */
  public void setXLabel(final String xLabel) {
    setXLabel(xLabel, 0, 0);
  }

  /**
   * x軸のラベルをデフォルト表示位置からx方向に<code>xOffset</code>、y方向に <code>yOffset</code> の場所に表示します。
   * 
   * @param xLabel x軸のラベル
   * @param xOffset 表示位置のx方向オフセット
   * @param yOffset 表示位置のy方向オフセット
   */
  public void setXLabel(final String xLabel, final double xOffset, final double yOffset) {
    if (new XLabel(xLabel, xOffset, yOffset).equals(this.xLabel)) {
      return;
    }
    
    this.xLabel.setLabel(xLabel, xOffset, yOffset);
    addDecoration(this.xLabel);
    this.gnuplot.doCommand(this.xLabel.getCommand());
    this.gnuplot.replot();
  }

  /**
   * {@inheritDoc}
   */
  public void setLogScale(final int scale) {
    if (this.logScale.getScale() == scale) {
      return;
    }
    
    this.logScale.setScale(scale);
    addDecoration(this.logScale);

    this.gnuplot.doCommand(this.logScale.getCommand());
    this.gnuplot.replot();
  }

  /**
   * X2軸のラベルをデフォルトの表示位置に表示します。
   * 
   * @param x2Label X2軸のラベル
   */
  public void setX2Label(final String x2Label) {
    setX2Label(x2Label, 0, 0);
  }

  /**
   * x2軸のラベルをデフォルト表示位置からx方向に<code>xOffset</code>、y方向に <code>yOffset</code> の場所に表示します。
   * 
   * @param xLabel x軸のラベル
   * @param xOffset 表示位置のx方向オフセット
   * @param yOffset 表示位置のy方向オフセット
   */
  public void setX2Label(String xLabel, int xOffset, int yOffset) {
    if (new X2Label(xLabel, xOffset, yOffset).equals(this.x2Label)) {
      return;
    }
    
    this.x2Label.setLabel(xLabel, xOffset, yOffset);
    addDecoration(this.x2Label);

    this.gnuplot.doCommand(this.x2Label.getCommand());
    this.gnuplot.replot();
  }

  /**
   * y軸のラベルをデフォルトの表示位置に表示します。
   * 
   * @param yLabel y軸のラベル
   */
  public void setYLabel(String yLabel) {
    setYLabel(yLabel, 0, 0);
  }

  /**
   * y軸のラベルをデフォルト表示位置からx方向に<code>xOffset</code>、y方向に <code>yOffset</code> の場所に表示します。
   * 
   * @param yLabel y軸のラベル
   * @param xOffset 表示位置のx方向オフセット
   * @param yOffset 表示位置のy方向オフセット
   */
  public void setYLabel(String yLabel, double xOffset, double yOffset) {
    if (new YLabel(yLabel, xOffset, yOffset).equals(this.yLabel)) {
      return;
    }
    
    this.yLabel.setLabel(yLabel, xOffset, yOffset);
    addDecoration(this.yLabel);

    this.gnuplot.doCommand(this.yLabel.getCommand());
    this.gnuplot.replot();
  }

  /**
   * Y2軸のラベルをデフォルトの表示位置に表示します。
   * 
   * @param y2Label Y2軸のラベル
   */
  public void setY2Label(String y2Label) {
    setY2Label(y2Label, 0, 0);
  }

  /**
   * Y2軸のラベルをデフォルト表示位置からx方向に<code>xOffset</code>、y方向に <code>yOffset</code> の場所に表示します。
   * 
   * @param y2Label Y2軸のラベル
   * @param xOffset 表示位置のx方向オフセット
   * @param yOffset 表示位置のy方向オフセット
   */
  public void setY2Label(String y2Label, int xOffset, int yOffset) {
    if (new Y2Label(y2Label, xOffset, yOffset).equals(this.y2Label)) {
      return;
    }
    
    this.y2Label.setLabel(y2Label, xOffset, yOffset);
    addDecoration(this.y2Label);

    this.gnuplot.doCommand(this.y2Label.getCommand());
    this.gnuplot.replot();
  }

  /**
   * z軸のラベルをデフォルトの表示位置に表示します。
   * 
   * @param zLabel z軸のラベル
   */
  public void setZLabel(String zLabel) {
    setZLabel(zLabel, 0, 0);
  }

  /**
   * z軸のラベルをデフォルト表示位置からx方向に<code>xOffset</code>、y方向に <code>yOffset</code> の場所に表示します。
   * 
   * @param zLabel z軸のラベル
   * @param xOffset 表示位置のx方向オフセット
   * @param yOffset 表示位置のy方向オフセット
   */
  public void setZLabel(String zLabel, int xOffset, int yOffset) {
    if (new ZLabel(zLabel, xOffset, yOffset).equals(this.zLabel)) {
      return;
    }
    
    this.zLabel.setLabel(zLabel, xOffset, yOffset);
    addDecoration(this.zLabel);

    this.gnuplot.doCommand(this.zLabel.getCommand());
    this.gnuplot.replot();
  }

  /**
   * x軸の表示範囲を設定します。
   * 
   * @param minimum x軸の最小値
   * @param maximum x軸の最大値
   */
  public void setXRange(final double minimum, final double maximum) {
    if (new XRange(minimum, maximum).equals(this.xRange)) {
      return;
    }
    
    this.xRange.setRange(minimum, maximum);
    addDecoration(this.xRange);
    this.gnuplot.doCommand(this.xRange.getCommand());
    
    this.xTics.setTics(minimum, this.xTics.getIncrease(), maximum);
    addDecoration(this.xTics);
    this.gnuplot.doCommand(this.xTics.getCommand());
    
    this.gnuplot.replot();
  }

  /**
   * x軸の表示範囲を返します。
   * 
   * @return x軸の表示範囲
   */
  public XRange getXRange() {
    return (XRange)this.xRange.clone();
  }

  /**
   * y軸の表示範囲を設定します。
   * 
   * @param minimum y軸の最小値
   * @param maximum y軸の最大値
   */
  public void setYRange(final double minimum, final double maximum) {
    if (new YRange(minimum, maximum).equals(this.yRange)) {
      return;
    }
    
    this.yRange.setRange(minimum, maximum);
    addDecoration(this.yRange);
    this.gnuplot.doCommand(this.yRange.getCommand());
    
    this.yTics.setTics(minimum, this.yTics.getIncrease(), maximum);
    addDecoration(this.yTics);
    this.gnuplot.doCommand(this.yTics.getCommand());
    
    this.gnuplot.replot();
  }

  /**
   * y軸の表示範囲を返します。
   * 
   * @return y軸の表示範囲
   */
  public YRange getYRange() {
    return (YRange)this.yRange.clone();
  }

  /**
   * z軸の表示範囲を設定します。
   * 
   * @param minimum z軸の最小値
   * @param maximum z軸の最大値
   */
  public void setZRange(double minimum, double maximum) {
    if (new ZRange(minimum, maximum).equals(this.zRange)) {
      return;
    }
    
    this.zRange.setRange(minimum, maximum);
    addDecoration(this.zRange);

    this.gnuplot.doCommand(this.zRange.getCommand());
    this.gnuplot.replot();
  }

  /**
   * z軸の表示範囲を返します。
   * 
   * @return z軸の表示範囲
   */
  public ZRange getZRange() {
    return (ZRange)this.zRange.clone();
  }

  /**
   * 上マージンを設定します。
   * 
   * @param margin マージン
   */
  public void setTopMargin(int margin) {
    if (this.topMargin.getMargin() == margin) {
      return;
    }
    
    this.topMargin.setMargin(margin);
    addDecoration(this.topMargin);

    this.gnuplot.doCommand(this.topMargin.getCommand());
    this.gnuplot.replot();
  }

  /**
   * 下マージンを設定します。
   * 
   * @param margin マージン
   */
  public void setButtomMargin(int margin) {
    if (this.buttomMargin.getMargin() == margin) {
      return;
    }
    
    this.buttomMargin.setMargin(margin);
    addDecoration(this.buttomMargin);

    this.gnuplot.doCommand(this.buttomMargin.getCommand());
    this.gnuplot.replot();
  }

  /**
   * 左マージンを設定します。
   * 
   * @param margin マージン
   */
  public void setLeftMargin(final int margin) {
    if (this.leftMargin.getMargin() == margin) {
      return;
    }
    
    this.leftMargin.setMargin(margin);
    this.gnuplot.update(this, this.leftMargin);
    addDecoration(this.leftMargin);

    this.gnuplot.doCommand(this.leftMargin.getCommand());
    this.gnuplot.replot();
  }

  /**
   * 右マージンを設定します。
   * 
   * @param margin マージン
   */
  public void setRightMargin(final int margin) {
    if (this.rightMargin.getMargin() == margin) {
      return;
    }
    
    this.rightMargin.setMargin(margin);
    addDecoration(this.rightMargin);

    this.gnuplot.doCommand(this.rightMargin.getCommand());
    this.gnuplot.replot();
  }

  /**
   * x軸の目盛りの刻み位置を設定します。
   * 
   * @param start 始点
   * @param increase 増分
   * @param end 終点
   */
  public void setXTics(double start, double increase, double end) {
    if (new XTics(start, increase, end).equals(this.xTics)) {
      return;
    }
    
    this.xTics.setTics(start, increase, end);
    addDecoration(this.xTics);

    this.gnuplot.doCommand(this.xTics.getCommand());
    this.gnuplot.replot();
  }

  /**
   * x軸の目盛りの刻み位置を返します。
   * 
   * @return x軸の目盛りの刻み位置
   */
  public XTics getXTics() {
    return (XTics)this.xTics.clone();
  }

  /**
   * y軸の目盛りの刻み位置を設定します。
   * 
   * @param start 始点
   * @param increase 増分
   * @param end 終点
   */
  public void setYTics(double start, double increase, double end) {
    if (new YTics(start, increase, end).equals(this.yTics)) {
      return;
    }
    
    this.yTics.setTics(start, increase, end);
    addDecoration(this.yTics);

    this.gnuplot.doCommand(this.yTics.getCommand());
    this.gnuplot.replot();
  }

  /**
   * y軸の目盛りの刻み位置を返します。
   * 
   * @return y軸の目盛りの刻み位置
   */
  public YTics getYTics() {
    return (YTics)this.yTics.clone();
  }

  /**
   * X2軸の目盛りの刻み位置を設定します。
   * 
   * @param start 始点
   * @param increase 増分
   * @param end 終点
   */
  public void setX2Tics(double start, double increase, double end) {
    if (new X2Tics(start, increase, end).equals(this.x2Tics)) {
      return;
    }
    
    this.x2Tics.setTics(start, increase, end);
    addDecoration(this.x2Tics);

    this.gnuplot.doCommand(this.x2Tics.getCommand());
    this.gnuplot.replot();
  }

  /**
   * Y2軸の目盛りの刻み位置を設定します。
   * 
   * @param start 始点
   * @param increase 増分
   * @param end 終点
   */
  public void setY2Tics(double start, double increase, double end) {
    if (new Y2Tics(start, increase, end).equals(this.y2Tics)) {
      return;
    }
    
    this.y2Tics.setTics(start, increase, end);
    addDecoration(this.y2Tics);

    this.gnuplot.doCommand(this.y2Tics.getCommand());
    this.gnuplot.replot();
  }

  /**
   * z軸の目盛りの刻み位置を設定します。
   * 
   * @param start 始点
   * @param increase 増分
   * @param end 終点
   */
  public void setZTics(double start, double increase, double end) {
    if (new ZTics(start, increase, end).equals(this.zTics)) {
      return;
    }
    
    this.zTics.setTics(start, increase, end);
    addDecoration(this.zTics);

    this.gnuplot.doCommand(this.zTics.getCommand());
    this.gnuplot.replot();
  }

  /**
   * 視点をx方向に関してangleラジアン回転します。
   * 
   * @param angle x方向に関して回転する量(度)
   */
  public void setXRotation(double angle) {
    if (this.viewPoint.getXRotation() == angle) {
      return;
    }
    
    this.viewPoint.setXRotation(angle);
    addDecoration(this.viewPoint);

    this.gnuplot.doCommand(this.viewPoint.getCommand());
    this.gnuplot.replot();
  }

  /**
   * 視点をx方向に関してxRotation、z方向に関してangleラジアン回転します。
   * 
   * @param angle z方向に関して回転する量(度)
   */
  public void setZRotation(double angle) {
    if (this.viewPoint.getZRotation() == angle) {
      return;
    }
    
    this.viewPoint.setZRotation(angle);
    addDecoration(this.viewPoint);

    this.gnuplot.doCommand(this.viewPoint.getCommand());
    this.gnuplot.replot();
  }

  /**
   * 全体をscale倍だけ拡大します。
   * 
   * @param scale 全体を拡縮させる倍率
   */
  public void setScale(double scale) {
    if (this.viewPoint.getScale() == scale) {
      return;
    }
    
    this.viewPoint.setScale(scale);
    addDecoration(this.viewPoint);

    this.gnuplot.doCommand(this.viewPoint.getCommand());
    this.gnuplot.replot();
  }

  /**
   * z方向にだけscale倍拡大します。
   * 
   * @param scale 拡縮させる倍率(z方向に)
   */
  public void setZScale(double scale) {
    if (this.viewPoint.getZSale() == scale) {
      return;
    }
    
    this.viewPoint.setZScale(scale);
    addDecoration(this.viewPoint);

    this.gnuplot.doCommand(this.viewPoint.getCommand());
    this.gnuplot.replot();
  }

  /**
   * {@inheritDoc}
   */
  public void setGridVisible(boolean visible) {
    if (this.grid.isOn() == visible) {
      return;
    }
    
    this.grid.setOn(visible);
    addDecoration(this.grid);

    this.gnuplot.doCommand(this.grid.getCommand());
    this.gnuplot.replot();
  }

  /**
   * キーの表示・非表示を設定します。
   * 
   * @param visible 表示するならばtrue、そうでなければfalse
   */
  public void setKeyVisible(boolean visible) {
    if (this.key.isOn() == visible) {
      return;
    }
    
    this.key.setOn(visible);
    addDecoration(this.key);

    this.gnuplot.doCommand(this.key.getCommand());
    this.gnuplot.replot();
  }

  /**
   * 媒介変数描画設定を設定します。
   * 
   * @param visible 媒介変数描画ならばtrue、そうでなければfalse
   */
  public void setParametric(boolean visible) {
    if (this.parametric.isOn() == visible) {
      return;
    }
    
    this.parametric.setOn(visible);
    addDecoration(this.parametric);

    this.gnuplot.doCommand(this.parametric.getCommand());
    this.gnuplot.replot();
  }

  /**
   * 3次元陰線処理設定を設定します。
   * 
   * @param hidden 陰線処理するならばtrue、しないならfalse
   */
  public void setHidden3d(boolean hidden) {
    if (this.hidden3d.isOn() == hidden) {
      return;
    }
    
    this.hidden3d.setOn(hidden);
    addDecoration(this.hidden3d);

    this.gnuplot.doCommand(this.hidden3d.getCommand());
    this.gnuplot.replot();
  }

  /**
   * 原点のx座標、y座標、幅、高さを設定します。
   * 
   * @param xOrigin 原点のx座標
   * @param yOrigin 原点のy座標
   * @param width 幅
   * @param height 高さ
   */
  void setFrame(double xOrigin, double yOrigin, double width, double height) {
    if (new Frame(xOrigin, yOrigin, width, height).equals(this.frame)) {
      return;
    }
    
    this.frame.setFrame(xOrigin, yOrigin, width, height);
    addDecoration(this.frame);

    this.gnuplot.doCommand(this.frame.getCommand());
    this.gnuplot.replot();
  }

  /**
   * 線の数を返します。
   * 
   * @return 線の数
   */
  int getLineSize() {
    return this.lineSize;
  }

  /**
   * 線の数を設定します。
   * 
   * @param lineSize 線の数
   */
  void setLineSize(int lineSize) {
    this.lineSize = lineSize;
  }

  /**
   * 表示に関する文字列を追加します。
   * 
   * @param command 表示に関する文字列
   */
  private void addDecoration(GnuplotComponent command) {
    this.decorations.put(command.getClass(), command);
  }

  /**
   * 表示に関する文字列を得る。
   * 
   * @return 表示に関する文字列
   */
  String getDecorationCommands() {
    final StringBuffer commands = new StringBuffer(""); //$NON-NLS-1$

    final GnuplotComponent frameCommand = this.decorations.get(Frame.class);
    if (frameCommand != null) {
      commands.append(frameCommand.getCommand() + ";"); //$NON-NLS-1$
      commands.append(newLine);
    }

    for (GnuplotComponent command : this.decorations.values()) {
      if (command instanceof Frame) {
        continue;
      }

      commands.append(command.getCommand() + ";"); //$NON-NLS-1$
      commands.append(newLine);
    }

    return commands.toString();
  }

  /**
   * プロットに関する文字列を得る。
   * 
   * @return プロットに関する設定を行う文字列
   */
  String getPlotString() {
    final StringBuffer plotString = new StringBuffer(""); //$NON-NLS-1$
    for (final String string : this.plotStrings) {
      if (string.length() == 0) {
        continue;
      }
      if (plotString.length() != 0) {
        plotString.append(", "); //$NON-NLS-1$
      }
      plotString.append(string);
    }
    return plotString.toString();
  }

  /**
   * フレーム情報を得る。
   * 
   * @return フレーム情報
   */
  Frame getFrame() {
    return this.frame;
  }

  /**
   * 重ね描画の設定をします。
   * 
   * @param holding 重ね描画をするならばtrue、そうでなければfalse
   */
  public void setHolding(boolean holding) {
    this.holding = holding;
  }

  /**
   * 重ね描画をするか判定します。
   * 
   * @return 重ね描画をするならばtrue、そうでなければfalse
   */
  public boolean isHolding() {
    return this.holding;
  }

  /**
   * {@inheritDoc}
   */
  public void plot1D(DoubleMatrix yData, String[] names, String[] attribute1, String[] attribute2) {
    if (this.keepingLineProperties == false) {
      resetLineProperties();
    }

    int size = 0;
    if (isHolding()) {
      size = getLineSize();
    }

    setLineSize(size + 1);
    String dataFile = getDataFilePath(size);

    try {
      yData.writeMatFormat(new File(dataFile));
    } catch (IOException e) {
      throw new PlotterException(e);
    }

    for (int i = 1; i <= yData.getRowSize(); i++) {
      String lineName;
      if (i <= names.length) {
        lineName = names[i - 1];
      } else {
        lineName = "data-" + (size + 1) + "-" + i; //$NON-NLS-1$ //$NON-NLS-2$
      }

      String cmd1;
      if (i <= attribute1.length) {
        cmd1 = attribute1[i - 1];
      } else {
        cmd1 = ""; //$NON-NLS-1$
      }

      String cmd2;
      if (i <= attribute2.length) {
        cmd2 = attribute2[i - 1];
      } else {
        cmd2 = ""; //$NON-NLS-1$
      }

      if (this.keepingLineProperties == false) {
        this.lineWidthes.add(new LineWidth(1));
        this.lineTypes.add(new LineType((i - 1)%this.colors.length));
        this.lineVisibles.add(Boolean.TRUE);
        this.lineNames.add(lineName);
      }

      final String dataString = "'" + dataFile + "' using 0:" + i; //$NON-NLS-1$ //$NON-NLS-2$
      //final String lineTypeString = "linetype " + this.lineTypes.get(i-1).getLineType() + 1; //$NON-NLS-1$
      final String lineTypeString = "linetype rgb " + "\"" + this.colors[this.lineTypes.get(i - 1).getLineType()] + "\""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      final String lineWidthString = "linewidth " + this.lineWidthes.get(i - 1).getLineWidth(); //$NON-NLS-1$
      final String titleString = "title '" + lineName + "'"; //$NON-NLS-1$//$NON-NLS-2$
      final String command = dataString + " " + lineTypeString + " " + lineWidthString + " " + cmd1 + " " + titleString + " " + cmd2; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$

      if (i == 1 && size == 0) {
        this.plotStrings.clear();
        this.removedPlotStrings.clear();
      }

      if (this.lineVisibles.get(i - 1).booleanValue()) {
        this.plotStrings.add(command);
        this.removedPlotStrings.add(""); //$NON-NLS-1$
      } else {
        this.plotStrings.add(""); //$NON-NLS-1$
        this.removedPlotStrings.add(command);
      }
    }

    File f = new File(dataFile);
    while (!f.exists()) {
      //
    }

    this.gnuplot.redraw();
  }

  /**
   * {@inheritDoc}
   */
  public void plot2D(DoubleMatrix xData, DoubleMatrix yData, String[] names, String[] attribute1, String[] attribute2) {
    if (this.keepingLineProperties == false) {
      resetLineProperties();
    }

    final int xRow = xData.getRowSize();
    final int yRow = yData.getRowSize();

    if (xRow != 1 && yRow != 1 && xRow != yRow) {
      throw new PlotterException(Messages.getString("Canvas.0")); //$NON-NLS-1$
    }

    int size = 0;
    if (isHolding()) {
      size = getLineSize();
    }

    setLineSize(size + 1);
    String dataFile = getDataFilePath(size);

    try {
      xData.appendDown(yData).writeMatFormat(new File(dataFile));
    } catch (IOException e) {
      throw new PlotterException(e);
    }

    for (int i = 1; i <= Math.max(xRow, yRow); i++) {
      String lineName;
      if (i <= names.length) {
        lineName = names[i - 1];
      } else {
        lineName = "data-" + (size + 1) + "-" + i; //$NON-NLS-1$ //$NON-NLS-2$
      }

      String cmd1;
      if (i <= attribute1.length) {
        cmd1 = attribute1[i - 1];
      } else {
        cmd1 = ""; //$NON-NLS-1$
      }

      String cmd2;
      if (i <= attribute2.length) {
        cmd2 = attribute2[i - 1];
      } else {
        cmd2 = ""; //$NON-NLS-1$
      }

      if (this.keepingLineProperties == false) {
        this.lineWidthes.add(new LineWidth(1));
        this.lineTypes.add(new LineType((i - 1)%this.colors.length));
        this.lineVisibles.add(Boolean.TRUE);
        this.lineNames.add(lineName);
      }

      String command = ""; //$NON-NLS-1$
      final String lineTypeString = "linetype rgb " + "\"" + this.colors[this.lineTypes.get(i - 1).getLineType()] + "\""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      final String lineWidthString = "linewidth " + this.lineWidthes.get(i - 1).getLineWidth(); //$NON-NLS-1$
      final String titleString = "title \'" + lineName + "\'"; //$NON-NLS-1$ //$NON-NLS-2$

      if (xRow == 1) {
        final String dataString = "\'" + dataFile + "\' using 1:" + (i + 1); //$NON-NLS-1$ //$NON-NLS-2$
        command = dataString + " " + lineTypeString + " " + lineWidthString + " " + cmd1 + " " + titleString + " " + cmd2; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
      } else if (yRow == 1) {
        final String dataString = "\'" + dataFile + "\' using " + i + ":" + (xRow + 1); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        command = dataString + " " + lineTypeString + " " + lineWidthString + " " + cmd1 + " " + titleString + " " + cmd2; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
      } else if (xRow == yRow) {
        final String dataString = "\'" + dataFile + "\' using " + i + ":" + (xRow + i); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        command = dataString + " " + lineTypeString + " " + lineWidthString + " " + cmd1 + " " + titleString + " " + cmd2; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
      }

      if (i == 1 && size == 0) {
        this.plotStrings.clear();
        this.removedPlotStrings.clear();
      }

      if (this.lineVisibles.get(i - 1).booleanValue()) {
        this.plotStrings.add(command);
        this.removedPlotStrings.add(""); //$NON-NLS-1$
      } else {
        this.plotStrings.add(""); //$NON-NLS-1$
        this.removedPlotStrings.add(command);
      }
    }

    File f = new File(dataFile);
    while (!f.exists()) {
      //
    }

    this.gnuplot.redraw();
  }

  /**
   * {@inheritDoc}
   */
  public void plot3D(DoubleMatrix xData, DoubleMatrix yData, DoubleMatrix zData, String[] names, String[] attribute1, String[] attribute2) {
    if (this.keepingLineProperties == false) {
      resetLineProperties();
    }

    final int xSize = zData.getRowSize();
    final int ySize = zData.getColumnSize();

    setLineSize(1);
    String dataFile = getDataFilePath(0);

    try(final PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(dataFile)))) {
      for (int i = 1; i <= xSize; i++) {
        for (int j = 1; j <= ySize; j++) {
          pw.println("" + xData.getDoubleElement(i, j) + " " + yData.getDoubleElement(i, j) + " " + zData.getDoubleElement(i, j)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        }
        pw.println(""); //$NON-NLS-1$
        pw.println(""); //$NON-NLS-1$
      }
    } catch (IOException e) {
      throw new PlotterException(e);
    }

    setParametric(true);

    String str = ""; //$NON-NLS-1$
    for (int i = 1; i <= xSize; i++) {
      final String lineName;
      if (i <= names.length) {
        lineName = names[i - 1];
      } else {
        lineName = "data-" + 0 + "-" + i; //$NON-NLS-1$ //$NON-NLS-2$
      }

      final String cmd1;
      if (i <= attribute1.length) {
        cmd1 = attribute1[i - 1];
      } else {
        cmd1 = ""; //$NON-NLS-1$
      }

      final String cmd2;
      if (i <= attribute2.length) {
        cmd2 = attribute2[i - 1];
      } else {
        cmd2 = ""; //$NON-NLS-1$
      }

      if (this.keepingLineProperties == false) {
        this.lineWidthes.add(new LineWidth(1));
        this.lineTypes.add(new LineType((i - 1)%this.colors.length));
        this.lineVisibles.add(Boolean.TRUE);
        this.lineNames.add(lineName);
      }

      final String lineTypeString = "linetype rgb " + "\"" + this.colors[this.lineTypes.get(i - 1).getLineType()] + "\""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      final String lineWidthString = "linewidth " + this.lineWidthes.get(i - 1).getLineWidth(); //$NON-NLS-1$

      final String dataString = "'" + dataFile + "' index " + (i - 1); //$NON-NLS-1$//$NON-NLS-2$
      final String titleString = " title '" + lineName + "' "; //$NON-NLS-1$ //$NON-NLS-2$
      final String command = dataString + " " + lineTypeString + " " + lineWidthString + " " + cmd1 + " " + titleString + " " + cmd2; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
      if (str.length() == 0) {
        str = command;
      } else {
        str = str + ", " + command; //$NON-NLS-1$
      }
    }

    final String command = "splot " + str; //$NON-NLS-1$
    this.plotStrings.clear();
    this.removedPlotStrings.clear();

    this.plotStrings.add(command);
    this.removedPlotStrings.add(""); //$NON-NLS-1$

    this.gnuplot.redraw();
  }

  /**
   * {@inheritDoc}
   */
  public void plot3DSurface(DoubleMatrix xData, DoubleMatrix yData, DoubleMatrix zData, String lineName, String attribute1, String attribute2) {
    if (this.keepingLineProperties == false) {
      resetLineProperties();
    }

    final int xSize = xData.getRowSize();
    final int ySize = xData.getColumnSize();

    setLineSize(1);
    String dataFile = getDataFilePath(0);

    try (final PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(dataFile)))) {
      for (int i = 1; i <= xSize; i++) {
        for (int j = 1; j <= ySize; j++) {
          pw.println("" + xData.getDoubleElement(i, j) + " " + yData.getDoubleElement(i, j) + " " + zData.getDoubleElement(i, j)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        }
        pw.println(""); //$NON-NLS-1$
      }
    } catch (IOException e) {
      throw new PlotterException(e);
    }

    setParametric(true);
    setHidden3d(true);

    final String titleString = " title '" + lineName + "' "; //$NON-NLS-1$ //$NON-NLS-2$
    String command = "splot " + "'" + dataFile + "' " + attribute1 + " " + titleString + " " + attribute2; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
    this.plotStrings.clear();
    this.plotStrings.add(command);

    this.gnuplot.redraw();
  }

  /**
   * データファイルのパスを返します。
   * 
   * @param number データ番号
   * @return データファイルのパス
   */
  private String getDataFilePath(int number) {
    final String fname;
    if (this.possibleLongFilename) {
      final int pid = this.gnuplot.hashCode();
      fname = "plot-" + pid + "-w" + "-c" + hashCode() + "-l" + number; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
    } else {
      fname = "w" + "c" + hashCode() + "l" + number + ".dat";  //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
    }

    final Environment environment = this.gnuplot.getEnvironment();
    final File baseDir;
    if (environment.hasWorkingDirectory()) {
      baseDir = environment.getWorkingDirectory();      
    } else {
      baseDir = new File("."); //$NON-NLS-1$
    }

    final File dataFile = baseDir != null ? new File(baseDir, fname) : new File(fname);
    return dataFile.getAbsolutePath();
  }

  /**
   * 一時的に作成されたデータファイルを削除します。
   */
  private void removeDataFile() {
    for (int j = 0; j <= (getLineSize() - 1); j++) {
      new File(getDataFilePath(j)).delete();
    }
  }

  /**
   * コマンドを実行します。
   * 
   * @param command コマンド
   */
  public void doCommand(String command) {
    this.gnuplot.doCommand(this.frame.getCommand());
    this.gnuplot.doCommand(command);
  }

  /**
   * キャンバスを再描画します。
   */
  public void redraw() {
    this.gnuplot.redraw();
  }

  /**
   * グリッドを表示するかを判定する。
   * 
   * @return グリッドの表示・非表示
   */
  public boolean isGridVisible() {
    return this.grid.isOn();
  }

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

  /**
   * 線のプロパティを保存するか返します。
   * 
   * @return 線のプロパティを保存するならばtrue、そうでなければfalse
   */
  public boolean isKeepingLineProperties() {
    return this.keepingLineProperties;
  }
  
  /**
   * 出力のバッファリングを設定します。
   * 
   * @param buffering 出力のバッファリングをするならばtrue
   */
  public void setBuffering(boolean buffering) {
    this.gnuplot.setBuffering(buffering);
  }
}