凹みTips

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

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

数値計算をして,結果を吐き出して,Excelに貼りつけて,グラフ化して….
なんて手順をたどってると時間が勿体無いですよね.
そこでgnuplotを使うと,プログラム上から簡単にグラフを描画することが出来ます.

wgnuplotの導入

まずはwindows版gnuplotであるwgnuplotをダウンロードしてください.
google:wgnuplot
解凍して適当な場所に置いたら,\binフォルダを環境変数のPATHに登録してください.

パイプを通す.

windows版のpopenである_popenを用いて,pgnuplot.exeへパイプを通します.
パイプを通すと,fprintfするだけでgnuplotのコマンドを実行出来るようになります.

Fp = _popen("pgnuplot", "w");
fprintf(Fp, "plot sin(x)\n");  // sinカーブが表示される
fflush(Fp);

ただしfprintfした状態ではバッファにコマンドが蓄えられたままですので,flushしてあげる必要があります.

gnuplot描画クラス

で,これらをちょっと簡単にやってくれるようなクラスが欲しいなー,と思って作成してみました.
こんな感じで使うことが出来ます.

#include "gnuplot.h"
int main()
{
	CGnuplot gplot;
	gplot.DrawFunc("sin(x)"); // sinカーブを描画
	return 0;
}
#include "gnuplot.h"

int main()
{
	CGnuplot gplot;
	std::vector<double> vecX, vecY;
	for (int i=0; i<100; i++) {
		double x = (double)i/100;
		double y = (x-0.25)*(x-0.5)*(x-0.75);
		vecX.push_back(x);
		vecY.push_back(y);
	}
	gplot.Command("set xrange[0:%f]", vecX[99]); // printfライクにコマンドが打てます
	gplot.Command("set yrange[0:%f]", vecY[99]);
	gplot.Plot(vecX, vecY);  // 配列でもコンテナでもプロットできます(まだ1次元と2次元のみ)
	return 0;
}

今後は,3次元プロットや,EPS出力,マクロ機能などを追加していきたいと考えています.

gnuplot.h

#pragma once

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

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

class CGnuplot {
private:
	/*! @brief パイプを繋げるファイルポインタ */
	FILE* Fp;

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

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

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

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

	/*! @brief 正常に機能しているかどうか */
	bool Check();
	
	/*! @brief printfライクに指定のコマンドを実行 */
	void Command(const char* format, ...);

	/*! @brief 関数を描画 */
	void DrawFunc(const char* format);

	/* プロットタイプ */
	static const int PLOT_TYPE_NOOUTPUT = 0;	//< プロット時に一時ファイルを作成しない
	static const int PLOT_TYPE_OUTPUT = 1;		//< プロット時に一時ファイルを作成

	/*! @brief 1次元要素を描画 
	@param[in] cont プロット対称コンテナ
	@param[in] plot_type 直接gnuplotにデータを打ち込む(0: default)か,一時ファイルを作成するか(1: 未実装)の選択
	*/
	template <class T, template <class A, class Allocator = std::allocator<A> > class Container>
	void Plot(Container<T> cont, const int plot_type = PLOT_TYPE_OUTPUT, const char* file_name = TempFileName.c_str())
	{
		Container<T>::iterator it = cont.begin();

		switch (plot_type) {
			case PLOT_TYPE_NOOUTPUT:
				Command("plot '-' w lp");
				while (it != cont.end()) {
					Command("%f", *it);
					it++;
				}
				Command("e");
				break;

			case PLOT_TYPE_OUTPUT:
				std::ofstream fout(file_name);
				while (it != cont.end()) {
					fout << *it << std::endl;
					it++;
				}
				Command("plot '%s' w lines", file_name);
				break;
		}
	}

	/*! @brief 1次元要素を描画(配列) */
	template <class T, int N>
	void Plot(T (&cont)[N], const int plot_type = PLOT_TYPE_OUTPUT, const char* file_name = TempFileName.c_str())
	{
		int x=0;

		switch (plot_type) {
			case PLOT_TYPE_NOOUTPUT:
				Command("plot '-' w lp");
				while (x < N) {
					Command("%f", cont[x]);
					x++;
				}
				Command("e");
				break;

			case PLOT_TYPE_OUTPUT:
				std::ofstream fout(file_name);
				while (x < N) {
					fout << cont[x] << std::endl;
					x++;
				}
				Command("plot '%s' w lines", file_name);
				break;
		}
	}

	/*! @brief 2次元要素を描画 */
	template <class T, template <class A, class Allocator = std::allocator<A> > class Container>
	void Plot(Container<T> contX, Container<T> contY, const int plot_type = PLOT_TYPE_OUTPUT, const char* file_name = TempFileName.c_str())
	{
		Container<T>::iterator itX = contX.begin();
		Container<T>::iterator itY = contY.begin();
		switch (plot_type) {
			case PLOT_TYPE_NOOUTPUT:
				Command("plot '-' w lp");
				while (itX != contX.end() && itY != contY.end()) {
					Command("%f %f", *itX, *itY);
					itX++; itY++;
				}
				Command("e");
				break;

			case PLOT_TYPE_OUTPUT:
				std::ofstream fout(file_name);
				while (itX != contX.end() && itY != contY.end()) {
					fout << *itX << " " << *itY << std::endl;
					itX++; itY++;
				}
				Command("plot '%s' w lines", file_name);
				break;
		}
	}

	/*! @brief 2次元要素を描画(配列) */
	template <class T, int N, int M>
	void Plot(T (&contX)[N], T (&contY)[M], const int plot_type = PLOT_TYPE_NOOUTPUT, const char* file_name = TempFileName.c_str())
	{
		int x=0, y=0;
		switch (plot_type) {
			case PLOT_TYPE_NOOUTPUT:
				Command("plot '-' w lp");
				while (x < N && y < M) {
					Command("%f %f", contX[x], contY[y]);
					x++; y++;
				}
				Command("e");
				break;

			case PLOT_TYPE_OUTPUT:
				std::ofstream fout(file_name);
				while (x < N && y < M) {
					fout << contX[x] << " " << contY[y] << std::endl;
					x++; y++;
				}
				Command("plot '%s' w lines", file_name);
				break;
		}
	}

	/*! @brief Xラベルをセット */
	void SetXLabel(const char* format);

	/*! @brief Yラベルをセット */
	void SetYLabel(const char* format);

	/*! @brief Xプロット範囲を設定 */
	void SetXRange(const double x_min, const double x_max);

	/*! @brief Yプロット範囲を設定 */
	void SetYRange(const double y_min, const double y_max);

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

gnuplot.cpp

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

const std::string CGnuplot::TempFileName = "temp.dat";

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

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

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::SetXLabel(const char* format)
{
	Command("set xlabel '%s'", format);
}

void CGnuplot::SetYLabel(const char* format)
{
	Command("set ylabel '%s'", format);
}

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

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

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

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

にしても,テンプレートをヘッダとソースに分けて記述できないのは何とかならないのか….

参考頁

そもそもの動機となったサイト様.ただライブラリがリンク切れになってました.

gnuplotで判らないことがあったらここ.