0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

VC++ でエクセルを操作する (グラフ描画)

Last updated at Posted at 2022-05-16

概要

今回は C++ で作成した数値データをエクセルでグラフ化します。

大数の法則

題材として大数の法則を取り上げます。

サイコロを何回か振って出目の回数をそれぞれ集計して、理論値 16.666… % に近づく様子をグラフ化してみます。1

#define  WIN32_LEAN_AND_MEAN
#define  _WIN32_WINNT _WIN32_WINNT_WIN7
#include <windows.h>
#include <comdef.h>
#include <iostream>
#include <iomanip>
#include <random>

//Excelを操作するためのタイプライブラリを読みこむ (オフィスのバージョンに依存しない)
//Microsoft Office Object Library
#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" no_auto_exclude auto_rename dual_interfaces
//Microsoft Visual Basic for Applications Extensibillity
#import "libid:0002E157-0000-0000-C000-000000000046" dual_interfaces
//Mcrosoft Excel Object Library
#import "libid:00020813-0000-0000-C000-000000000046" no_auto_exclude auto_search auto_rename dual_interfaces

using namespace Excel; //名前空間の定義

struct StartOle {
	StartOle() { CoInitialize(NULL); }		//COMを初期化
	~StartOle() { CoUninitialize(); }		//COMを閉じる
} _inst_StartOle;

void dump_com_error(_com_error &e)
{
	std::cerr << "Com error!\n";
	std::cerr << "\tCode = " << std::setw(8) << std::hex << e.Error() << '\n';
	std::cerr << "\tCode meaning = " << e.ErrorMessage() << '\n';
	_bstr_t bstrSource(e.Source());
	_bstr_t bstrDescription(e.Description());
	LPCSTR	source = (LPCSTR)bstrSource;
	std::cerr << "\tSource = " << (source ? source : "(NULL)") << "\n";
	LPCSTR descript = (LPCSTR)bstrDescription;
	std::cerr << "\tDescription = " << (descript ? descript : "(NULL)") << "\n";
}

namespace {
	const int SMP  = 30000000;				//サンプル数
	const int SEG  =      100;				//分割数
	const int DICE =        6;				//出目の数
}

//乱数の平均を生成する
void set_data(_WorksheetPtr &sheet)
{
	std::random_device rand_dev;				//非決定的な乱数生成器を生成
	std::mt19937 mt(rand_dev());				//メルセンヌ・ツイスタの32ビット版、引数は初期シード値
	std::uniform_int_distribution<> rand_dice(0, DICE-1);
												//[0, 5] 範囲の一様乱数

	int		sum[DICE] = { 0 };					//1~DICEの出目
	double	ave[DICE][SEG];						//1~DICEの平均値

	for (int x = 0; x < SEG; ++x) {				//分割数(SEG)分繰り返し
		for (int i = 0; i < (SMP / SEG); ++i) {	//サンプル数÷分割数ずつ繰り返し
			int r = rand_dice(mt);				//サイコロの振る
			++sum[r];							//出目を加算する
		}
		for (int i = 0; i < DICE; ++i)
			ave[i][x] = (double)sum[i] / ((SMP / SEG) * (x + 1));
												//平均を求める
	}

	for (int x = 0; x < SEG; ++x) {
		RangePtr cell1 = sheet->Cells->Item[x+1][1];
		cell1->Value2 = (SMP / SEG) * (x + 1);	//サンプル数
		for (int i = 0; i < DICE; ++i) {
			RangePtr cell2 = sheet->Cells->Item[x+1][i + 2];									//セルを指定
			cell2->Value2 = ave[i][x];			//平均値
		}
	}
}

int main(void)
{
	const	int WaitSec = 5;					//休止秒数

	char buff[256];
	_ApplicationPtr xl;

	//---------------------------------------------------------
	//Excelの起動
	HRESULT hr = xl.CreateInstance(L"Excel.Application");

	if (xl) {									//エクセル インスタンスの生成を確認
		xl->Visible[0] = VARIANT_TRUE;			//表示する
		xl->UserControl = VARIANT_TRUE;			//ユーザーの操作を可能にする

		try {									//例外を捕捉
			WorkbooksPtr workbooks = xl->Workbooks;
												//WorkBookを追加
			_WorkbookPtr workbook = workbooks->Add(xlWorksheet);
												//ワークシートを追加
			_WorksheetPtr worksheet = workbook->ActiveSheet;
												//アクティブ・シート

			//データ作成
			set_data(worksheet);

			RangePtr range = worksheet->Range["B1:B100"][vtMissing];
												//グラフ化する範囲を指定

			//グラフ作成
			_ChartPtr chart = workbook->Charts->Add();
												//グラフを追加
			chart->SetSourceData(range, xlColumns);
												//データソースを指定

			chart->HasTitle[0] = VARIANT_TRUE;	//タイトルあり
			bstr_t	title{ "大数の法則 (サイコロ 出目の確率)" };
			chart->ChartTitle->Caption = title;	//タイトル指定
			chart->ChartType = xlXYScatterSmoothNoMarkers;
												//散布グラフ(平滑線マーカーなし)

			chart = chart->Location(xlLocationAsObject, bstr_t("Sheet1"));
												//オブジェクトとして埋め込む
			chart->HasLegend[0] = VARIANT_TRUE;	//凡例あり
			chart->Legend->Position = xlLegendPositionBottom;
												//凡例位置を下に

			char	xValues[256] = "=Sheet1!R1C1:R100C1";
												//x軸
			SeriesPtr series1 = chart->SeriesCollection(1);
												//系列1
			series1->XValues = variant_t(xValues);
												//Xの値
			series1->Name = bstr_t("1の目");	//凡例1
			series1.Release();

			AxisPtr axis1 = chart->Axes(xlCategory, xlPrimary);
												//X軸
			axis1->MaximumScale = SMP;			//最大値
			axis1.Release();

			AxisPtr axis2 = chart->Axes(xlValue, xlPrimary);
												//Y軸
			axis2->HasMajorGridlines = VARIANT_TRUE;
												//目盛線あり
			axis2.Release();

			SeriesCollectionPtr collection = chart->SeriesCollection();
												//データ系列のコレクション

			//系列追加
			for (int i = 1; i < DICE; ++i) {	//出目のループ
				chart->Refresh();				//グラフ更新

				SeriesPtr series = collection->NewSeries();
												//系列を追加
				char	yValues[256];
				_snprintf_s(yValues, _TRUNCATE, "=Sheet1!R1C%d:R100C%d", i+2, i+2);
				series->Values = variant_t(yValues);
												//データを指定
				series->XValues = variant_t(xValues);
												//Xの値

				char	legend[256];
				_snprintf_s(legend, _TRUNCATE, "%dの目", i + 1);
				series->Name = bstr_t(legend);	//系列名指定
				series.Release();
			}
			//グラフの位置とサイズの調整
			ShapePtr shape = worksheet->Shapes->Item(1);
			shape->Left   = 240.;				//左端
			shape->Top    =  10.;				//上端
			shape->Width  = 640.;				//横幅
			shape->Height = 480.;				//高さ

			chart->ChartArea->Copy();			//グラフをコピー


			//COMオブジェクトを解放する
			collection.Release();
			shape.Release();
			chart.Release();
			range.Release();
			worksheet.Release();

			//WaitSec秒休止
			::Sleep(WaitSec * 1000);

			//ウィンドウを最小化
			xl->WindowState[0] = xlMinimized;
			std::cout << "確認のために一時停止:";
			std::cin.getline(buff, sizeof buff);

			workbook->Saved[0] = VARIANT_TRUE;	//警告メッセージが出ないようにする
			workbook->Close();					//ワークシートを閉じる
			workbook.Release();					//COMオブジェクトを解放する
			workbooks.Release();				//COMオブジェクトを解放する
		} catch (_com_error &e) {				//COMの例外処理
			dump_com_error(e);					//エラーログを出力
		}
		//Excelを終了する
		xl->Quit();
		xl.Release();							//COMオブジェクトを解放する
	} else {
		std::cerr << "エクセルを起動できません\n";
	}

	std::cout << "テストプログラムを終了:";
	std::cin.getline(buff, sizeof buff);
	return 0;
}

エクセルの起動など以前のプログラムと同様です。

set_data()SMP回サイコロを振って、区分ごとの各目の平均を求めます。求めた平均回数をエクセルのセルに出力します。

	//グラフ作成
	_ChartPtr chart = workbook->Charts->Add();
	chart->SetSourceData(range, xlColumns);

グラフを追加し、データソースを指定します。

最初に一つだけ系列 (1の目) を指定して、後で他の系列 (2~6の目) を追加します。

	chart->HasTitle[0] = VARIANT_TRUE;
	bstr_t	title{ "大数の法則 (サイコロ 出目の確率)" };
	chart->ChartTitle->Caption = title;
	chart->ChartType = xlXYScatterSmoothNoMarkers;

タイトルやグラフの種類を指定します。

	//系列追加
	for (int i = 1; i < DICE; ++i) {
		chart->Refresh();
		SeriesPtr series = collection->NewSeries();
		char	yValues[256];
		_snprintf_s(yValues, _TRUNCATE, "=Sheet1!R1C%d:R100C%d", i+2, i+2);
		series->Values = variant_t(yValues);
		series->XValues = variant_t(xValues);
		char	legend[256];
		_snprintf_s(legend, _TRUNCATE, "%dの目", i + 1);
		series->Name = bstr_t(legend);
		series.Release();
	}

2~6の目の系列を追加します。

	chart->ChartArea->Copy();

作成したグラフをクリップボードにコピーします。

実行結果

大数の法則
試行回数が多くなると理論値0.1666…に近づくことがわかります。

開発環境および実行環境

  • Windows10
  • Visual Studio 2019 Community
  • Excel 2013
  1. 平均の計算方法に間違いがあるようでしたら指摘して下さい。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?