凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

C++(C)でgnuplotにパイプを繋げてグラフを簡単に描画Comments 其の三

前回(d:id:hecomi:20100708),前々回(d:id:hecomi:20100707)に引き続き,gnuplotをプロットするクラスのお話です.

追加機能 / 変更箇所

大したものは追加してませんが,追加機能は以下の通りです.

  • 現在プロットしているデータをファイルに保存
  • 現在プロットしているデータをEPSに出力
  • コマンドを書いてある外部ファイルを読み込み・実行

後はちょいちょいと不統一だった変数の記法などを修正をしました.

加えて,Doxygenに対応したコメントを付加しました.
Doxygenは本当に便利なので使ったことない方は是非インストールされることをおすすめします(http://www.doxygen.jp/).

これで取り敢えず,自分にとっては不足しない程度に一通り機能が揃いましたので,ここらで開発を終了したいと思います.
コードは自由に使っていただいて構いません.
(ただし,何か起きても責任は取りかねます)

ソースコード

gnuplot.h

#pragma once

/*! @file
 @brief		gnuplotクラス
 @author	hecomi
 @date		July 9, 2010.
*/

#include <vector>
#include <fstream>
#include <string>

/*! @brief gnuplotを扱うライブラリ
gnuplotを扱うクラスに関するライブラリです.
pgnuplotへとパイプを繋ぎ,関数やデータをgnuplotを用いてプロットする手助けをします.
pgnuplot.exeが環境変数へと登録されている必要があります.
*/
class CGnuplot {
private:
	//! プロットする要素の開始位置と終了位置
	FILE* Fp;

	//! プロットする要素の開始位置と終了位置
	template <class Container>
	struct PlotInfo
	{
		Container begin;	//!< プロット開始位置
		Container end;		//!< プロット終了位置
	};

	/*!
	@brief バッファフラッシュ
	*/
	void Flush();

	/*!
	@brief 1次元要素をプロットする
	@param[in] x プロット情報を格納したxデータ
	*/
	template <class T>
	void PlotX(PlotInfo<T> x)
	{
		T it = x.begin;
		std::ofstream fout(TempFileName);
		if (fout.fail()) {
			std::cout << "Error! (@PlotX)" << std::endl;
			return;
		}
		while (it != x.end) {
			fout << *it << std::endl;
			it++;
		}
		Command("plot '%s' w lines", TempFileName);
	}

	/*!
	@brief 2次元要素をプロットする
	@param[in] x プロット情報を格納したxデータ
	@param[in] y プロット情報を格納したyデータ
	*/
	template <class T>
	void PlotXY(PlotInfo<T> x, PlotInfo<T> y)
	{
		T itX = x.begin, itY = y.begin;
		std::ofstream fout(TempFileName);
		if (fout.fail()) {
			std::cout << "Error! (@PlotXY)" << std::endl;
			return;
		}
		while (itX != x.end && itY != y.end) {
			fout << *itX << " " << *itY << std::endl;
			itX++; itY++;
		}
		Command("plot '%s' w lines", TempFileName);
	}

	/*!
	@brief 3次元要素をプロットする
	@param[in] x プロット情報を格納したxデータ
	@param[in] y プロット情報を格納したyデータ
	@param[in] z プロット情報を格納したzデータ
	*/
	template <class T>
	void PlotXYZ(PlotInfo<T> x, PlotInfo<T> y, PlotInfo<T> z)
	{
		T itX = x.begin, itY = y.begin, itZ = z.begin;
		std::ofstream fout(TempFileName);
		if (fout.fail()) {
			std::cout << "Error! (@PlotXYZ)" << std::endl;
			return;
		}
		while (itX != x.end && itY != y.end && itZ != z.end) {
			fout << *itX << " " << *itY << " " << *itZ << std::endl;
			itX++; itY++; itZ++;
		}
		Command("splot '%s' w lines", TempFileName);
	}

public:
	/*! @brief コンストラクタ */
	CGnuplot();
	CGnuplot(const char* fileName);

	/*! @brief デストラクタ */
	~CGnuplot();

	/*! @brief 一時ファイル名 */
	static const char* TempFileName;

	/*! @brief 正常に機能しているかどうか */
	bool Check();
	
	/*!
	@brief printfライクにgnuplotのコマンドを実行
	@param[in] format printfに用いるフォーマット
	@param[in] ... 可変長引数
	*/
	void Command(const char* format, ...);

	/*!
	@brief 関数をプロット
	Ex. DrawFunc("sin (x)")
	@param[in] format プロット対称関数文字列
	*/
	void DrawFunc(const char* format);

	/*!
	@brief 1次元要素をプロットする
	@param[in] cont プロット対称コンテナ
	*/
	template <class T, template <class A, class Allocator = std::allocator<A> > class Container>
	void Plot(Container<T> cont)
	{
		PlotInfo<Container<T>::iterator> pi = { cont.begin(), cont.end() };
		PlotX(pi);
	}

	/*!
	@brief 1次元要素をプロットする(配列)
	@param[in] cont プロット対称配列
	*/
	template <class T, int N>
	void Plot(T (&cont)[N])
	{
		PlotInfo<T*> pi = { &cont[0], &cont[N-1] };
		PlotX(pi);
	}

	/*!
	@brief 2次元要素をプロットする
	@param[in] contX プロット対称コンテナ(x)
	@param[in] contY プロット対称コンテナ(y)
	*/
	template <class T, template <class A, class Allocator = std::allocator<A> > class Container>
	void Plot(Container<T> contX, Container<T> contY)
	{
		PlotInfo<Container<T>::iterator> 
			piX = { contX.begin(), contX.end() },
			piY = { contY.begin(), contY.end() };
		PlotXY(piX, piY);
	}

	/*!
	@brief 2次元要素をプロットする(配列)
	@param[in] contX プロット対称配列(x)
	@param[in] contY プロット対称配列(y)
	*/
	template <class T, int N, int M>
	void Plot(T (&contX)[N], T (&contY)[M])
	{
		PlotInfo<T*> 
			piX = { &contX[0], &contX[N] },
			piY = { &contY[0], &contY[M] };
		PlotXY(piX, piY);
	}

	/*!
	@brief 3次元要素をプロットする
	@param[in] contX プロット対称コンテナ(x)
	@param[in] contY プロット対称コンテナ(y)
	@param[in] contZ プロット対称コンテナ(z)
	*/
	template <class T, template <class A, class Allocator = std::allocator<A> > class Container>
	void Plot(Container<T> contX, Container<T> contY, Container<T> contZ)
	{
		PlotInfo<Container<T>::iterator> 
			piX = { contX.begin(), contX.end() },
			piY = { contY.begin(), contY.end() },
			piZ = { contZ.begin(), contZ.end() };
		PlotXYZ(piX, piY, piZ);
	}

	/*!
	@brief 3次元要素をプロットする(配列)
	@param[in] contX プロット対称配列(x)
	@param[in] contY プロット対称配列(y)
	@param[in] contZ プロット対称配列(z)
	*/
	template <class T, int N, int M, int L>
	void Plot(T (&contX)[N], T (&contY)[M], T (&contZ)[L])
	{
		PlotInfo<T*> 
			piX = { &contX[0], &contX[N] },
			piY = { &contY[0], &contY[M] },
			piZ = { &contZ[0], &contZ[L] };
		PlotXYZ(piX, piY, piZ);
	}

	/*!
	@brief ラベルをセット
	@param[in] formatX Xラベル名
	@param[in] formatY Yラベル名
	*/
	void SetLabel(const char* formatX, const char* formatY);

	/*!
	@brief プロット範囲を指定
	@param[in] min x軸プロット範囲最小値
	@param[in] min y軸プロット範囲最小値
	*/
	void SetXRange(const double min, const double max);

	/*!
	@brief プロット範囲を指定
	@param[in] min y軸プロット範囲最小値
	@param[in] min y軸プロット範囲最小値
	*/
	void SetYRange(const double min, const double max);

	/*!
	@brief リプロット
	*/
	void Replot();

	/*!
	@brief 現在プロットしているデータをファイルに書き出す
	中身はTempFileNameに保存されているデータをコピーしているだけ
	@param[in] fileName 書き出し先ファイル名
	*/
	void DumpToFile(const char* fileName);

	/*!
	@brief 現在プロットしているデータをEPSに書き出す
	@param[in] fileName 書き出し先ファイル名
	*/
	void DumpToEps(const char* fileName);

	/*!
	@brief 外部ファイルからコマンドを読み込み実行
	@param[in] fileName 読み込み元ファイル名
	*/
	void CommandFromFile(const char* fileName);
};

gnuplot.cpp

#pragma warning(disable: 4996)

#include <stdio.h>
#include <stdarg.h>
#include <iostream>
#include "gnuplot.h"

const char* CGnuplot::TempFileName = "temp.dat";

CGnuplot::CGnuplot()
{
	Fp = _popen("pgnuplot", "w");
	if (Fp == NULL) {
		printf("pipe error\n");
		exit(EXIT_FAILURE);
	}
}

CGnuplot::CGnuplot(const char* fileName)
{
	Fp = fopen(fileName, "w");
	if (Fp == NULL) {
		printf("pipe error\n");
		exit(EXIT_FAILURE);
	}
}

CGnuplot::~CGnuplot()
{

	_pclose(Fp);
}

bool CGnuplot::Check()
{
	if (Fp == NULL) {
		return false;
	}
	return true;
}

void CGnuplot::Command(const char* format, ...)
{
	char buf[1024];
	va_list ap;

	va_start(ap, format);
	vsprintf(buf, format, ap);
	va_end(ap);

	fprintf(Fp, "%s\n", buf);
	Flush();
}

void CGnuplot::DrawFunc(const char* format)
{
	Command("plot %s", format);
}

void CGnuplot::SetLabel(const char* formatX, const char* formatY)
{
	Command("set xlabel '%s'", formatX);
	Command("set ylabel '%s'", formatY);
}

void CGnuplot::SetXRange(const double min, const double max)
{
	Command("set xrange [%f:%f]", min, max);
}

void CGnuplot::SetYRange(const double min, const double max)
{
	Command("set yrange [%f:%f]", min, max);
}

void CGnuplot::Flush()
{
	fflush(Fp);
}

void CGnuplot::Replot()
{
	Command("replot");
}

void CGnuplot::DumpToFile(const char* fileName)
{
	std::ofstream fout(fileName, std::ios_base::binary);
	std::ifstream fin(TempFileName, std::ios_base::binary);
	if (fin.fail() || fout.fail()) {
		std::cout << "Error! (@DumpToFile)" << std::endl;
		return;
	}
	while (!fin.eof()) {
		const int BUF_SIZE = 4096;
		char buf[BUF_SIZE];
		fin.read(buf, BUF_SIZE);
		fout.write(buf, BUF_SIZE);
	}
}

void CGnuplot::DumpToEps(const char* fileName)
{
	Command("set term postscript eps enhanced");
	Command("set output '%s'", fileName);
	Command("replot");
	Command("set output");
	Command("set terminal window");
}

void CGnuplot::CommandFromFile(const char* fileName)
{
	std::ifstream fin(fileName);
	if (fin.fail()) {
		std::cout << "Error! (@CommandFromFile)" << std::endl;
		return;
	}
	std::string str;
	while (!fin.eof()) {
		std::getline(fin, str);
		Command(str.c_str());
	}
}