数値計算をして,結果を吐き出して,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"); }
にしても,テンプレートをヘッダとソースに分けて記述できないのは何とかならないのか….