凹みTips

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

C++でgnuplotを扱う

はじめに

(※windows向けのエントリです)
これまで,C++gnuplotを使う,ということで幾つかエントリを経てgnuplotを便利に使えるクラスを作成してきました.

tips.hecomi.com

tips.hecomi.com

tips.hecomi.com

tips.hecomi.com


ただ,ヘッダファイル・ソースファイルを取って来てほげほげ…とやって,更に何のメンバがあるんだろう…ってふがふがやっていると,使う気も失せてくると思います.検索して来られる方が一番多い内容でしたので,簡単に使って頂けるようにまとめてみました.

ダウンロード

扱いが簡単なようにヘッダファイルだけにまとめました.基本的にNYSLライセンスですが,ヘッダ部分だけ残してくれるとちょっと嬉しいです.

gist.github.com

利用に際して

windowsgnuplotに同梱されているpgnuplot.exeが必要です.ここにパイプを通して動作させる仕組みになっています.以下のURLから適当なバージョンのものをDL・展開して,適当な場所に配置した後,環境変数を通してください.
Source Forge: http://sourceforge.net/projects/gnuplot/files/

利用例

簡単な例として,sinを一周期だけ描いてみます.

#include <vector>
#include <algorithm>
#include <cmath>
#include "gnuplot.h"

int main()
{
	using namespace std;
	using namespace gnuplot;

	vector<double> 	x, y;
	for (int i = 0; i < 628; x.push_back(i*0.01), i++);
	transform(x.begin(), x.end(), back_inserter(y), sin);

	CGnuplot gp;
	gp.SetLabel("x", "sin(x)");
	gp.Plot(x, y);
	gp.DumpToPng("sin");

	__KEYWAIT__;
	return 0;
}

出力:

WEB上でも見れるようにpngで出力しましたが,DumpToEpsを使えばeps出力も可能です.DumpToFileで数値データとして吐き出すことも出来ます.vectorを使っていますが,配列でもプロットできるようになっています.

#include <cmath>
#include "gnuplot.h"

int main()
{
	using namespace std;
	using namespace gnuplot;

	double x[100], y[100];
	for (int i = 0; i<100; i++) {
		x[i] = 0.0628*i;
		y[i] = sin(x[i]);
	}

	CGnuplot gp;
	gp.SetLabel("x", "sin(x)");
	gp.SetPlotType(CGnuplot::PLOT_TYPE_LINES_POINTS);
	gp.Plot(x, y);
	gp.DumpToPng("sin");

	__KEYWAIT__;
	return 0;
}

結果:

ただ,次に紹介するMultiplotは配列によるプロットをサポートしていません.後日更新します(多分).

x軸が揃った複数プロットは次のようになります.

#include <vector>
#include <algorithm>
#include <cmath>
#include "gnuplot.h"

int main()
{
	using namespace std;
	using namespace gnuplot;
	
	vector<double> 	x, y1, y2;
	for (int i = 0; i < 628; x.push_back(i*0.01), i++);
	transform(x.begin(), x.end(), back_inserter(y1), sin);
	transform(x.begin(), x.end(), back_inserter(y2), cos);

	CGnuplot gp;
	gp.SetTitle("Trigonometric Functions");
	gp.Command("set key left bottom");
	gp.SetLabel("x", "y");
	gp.SetXRange(0, 6.28);
	gp.SetYRange(-1, 1);

	SemiMultiPlot plotData;
	plotData.push_back(make_pair("sin(x)", y1));
	plotData.push_back(make_pair("cos(x)", y2));
	gp.Multiplot(x, plotData);

	gp.DumpToEps("trigonometric");

	__KEYWAIT__;
	return 0;
}

結果(EPS出力したものをPNGに変換しました):

SemiMultiPlotは vector > >型をtypedefしているものです.Multiplotメンバ関数を用いて複数プロットを行います.また,gnuplot時の基本的な命令を行うメンバ関数であるSetTitle,SetLabel,SetXRange,SetYRangeや片対数プロットを行うSetLogPlotX,SetLogPlotY,両対数プロットを行うSetLogPlotなどを用意しています.これ以外の命令が必要な場合はCommandメンバ関数を用いれば可能です.単純にgnuplotへ命令文を伝えます.この関数はprintfライクに使えるように実装してあります.また,スタイルを一新したい場合などはCommandFromFileメンバ関数を利用して指定したファイルから一気に命令を実行させることも出来ます.

また,x軸のプロット用ベクトルが異なる状況もあるかもしれません.その際は次のようにします.

#include <vector>
#include <algorithm>
#include <cmath>
#include "gnuplot.h"

int main()
{
	using namespace std;
	using namespace gnuplot;
	
	vector<double> 	x1, x2, y1, y2;
	for (int i = 0; i <= 628; x1.push_back(i*0.01), i++);
	for (int i = 0; i <= 10; x2.push_back(i*0.628), i++);
	transform(x1.begin(), x1.end(), back_inserter(y1), sin);
	transform(x2.begin(), x2.end(), back_inserter(y2), cos);

	CGnuplot gp;
	gp.SetTitle("Trigonometric Functions");
	gp.Command("set key left bottom");
	gp.SetLabel("x", "y");
	gp.SetXRange(0, 6.28);
	gp.SetYRange(-1, 1);

	MultiPlot plotData;
	plotData.push_back(svv("sin(x)", x1, y1));
	plotData.push_back(svv("cos(x)", x2, y2));
	gp.Multiplot(plotData);

	gp.DumpToEps("trigonometric2");

	__KEYWAIT__;
	return 0;
}

結果:

MultiPlotはvector, vector > > >をtypedefしたものです.svv(string vector vectorのつもりです)関数は,pair, vector > >を返します.

三次元プロットを行いたい場合には次のように行います.これについては配列でも出来るので,配列を用いて書いておきます.もちろんvector等でも同様にプロットすることが可能です.

#include <cmath>
#include "gnuplot.h"

int main()
{
	using namespace std;
	using namespace gnuplot;

	double x[1200], y[1200], z[1200];
	for (int i = 0; i<1200; i++) {
		if (i < 1000) {
			double
				t = 0.1*i,
				s = 1 - 0.001*i;
			x[i] = s * cos(t);
			y[i] = s * sin(t);
			z[i] = t;
		} else {
			x[i] = 0;
			y[i] = 0;
			z[i] = 100-(i-1000);
		}
	}

	CGnuplot gp;
	gp.SetYRange(-2, 2);
	gp.SetXRange(-2, 2);
	gp.Plot(x, y, z);
	gp.DumpToFile("kinoko");

	__KEYWAIT__;
	return 0;
}

結果:

ただ,これでは3次元に点をプロットしているだけなので,面プロットも欲しいと思います.その場合は次のように書きます.

#include <vector>
#include <cmath>
#include "gnuplot.h"

int main()
{
	using namespace std;
	using namespace gnuplot;

	vector<double> x, y;
	vector<vector<double> > z;
	
	for (int i=0; i<100; x.push_back(i*0.1), y.push_back(i*0.1), i++);
	for (int i=0; i<100; i++) {
		vector<double> _z;
		for (int j=0; j<100; j++) {
			_z.push_back(sin(x[i])*cos(y[j]));
		}
		z.push_back(_z);
	}

	CGnuplot gp;
	gp.Plot(x, y, z);

	__KEYWAIT__;
	return 0;
}

結果:

boost::multi_arrayを使っても同様にできます.#ifdef USE_BOOSTの中に書きこまれていますので,#define USE_BOOSTをgnuplot.hをインクルードする前に書きこんでください.コードはこちらのほうがシンプルになりますね.

#define USE_BOOST

#include <vector>
#include <cmath>
#include "gnuplot.h"

int main()
{
	using namespace std;
	using namespace gnuplot;

	vector<double> x, y;
	boost::multi_array<double, 2> z(boost::extents[100][100]);
	
	for (int i=0; i<100; x.push_back(i*0.1), y.push_back(i*0.1), i++);
	for (int i=0; i<100; i++) {
		for (int j=0; j<100; j++) {
			z[i][j] = sin(x[i])*cos(y[j]);
		}
	}

	CGnuplot gp;
	gp.Plot(x, y, z);

	__KEYWAIT__;
	return 0;
}

最後に

機能を追加次第,このエントリを更新していきたいと思います.バグ・要望等ございましたらコメントして頂けると嬉しいです.