【C++】matplotlib-cppでtrend chartを書く方法

# <font color="ForestGreen">C++ matplotlib-cppでtrend chartを書く方法<font>
　データの可視化は情報を整理したり、わかりやすく結果を示すときによく使います。グラフの種類は、トレンドチャートやバーチャート、モザイクチャートやヒートマップ、データテーブルなど様々あり、適切な表現で示すことで伝えたいことを100%伝えることができます。今回はC++のmatplotlib-cppを使ったGraphのつくり方をまとめました。

## <font color="Green">matplotlib-cppとは<font>
ドイツ ハンブルクのソフトウェア企業TenzirのBenno Eversが2014年にGitに公開したC++ Plotライブラリ。Python Matlabとmatplotlibを参考に作られており、使い方はmatplotlibに似ている。

[Tenzir Home Page](https://tenzir.com/)
[matplotlib-cpp Github](https://github.com/lava/matplotlib-cpp)
[matplotlib-cpp documentation](https://readthedocs.org/projects/matplotlib-cpp/downloads/pdf/latest/)

## <font color="Green">環境設定<font>
- OS : Windows10(2021-)
- Editor : VS code(version 1.58.0)
- C++ compiler : MinGW w64bit(gcc version 8.1.0)
- Python : Python39(v3.9.6)
- make : v3.81

## <font color="Green">初期設定<font>
pipでpython moduleをinstallする。
各環境のProxyサーバーを指定する。

```
pip --proxy=http://**********.com:80 install --trusted-host file.pythonhosted.org numpy
pip --proxy=http://**********.com:80 install --trusted-host file.pythonhosted.org matplotlib
```

MinGW w64bitをinstallする。

```
choco install mingw
```
もしくは
[mingw download page](http://mingw-w64.org/doku.php/download)

詳細は以下の記事参考。
https://qiita.com/ryo-sato/items/00c17469978e47d91a09

buildするのにmakeを使用するため、以下のサイトからmakeのinstallerをダウンロードする。
"Complete package, except sources"の横の"Setup"を押す。

https://gnuwin32.sourceforge.net/packages/make.htm

## <font color="Green">まずはデータをloadする
getlineで行毎に読み込み、for文で以下のように列毎に分け、データをpair vectorに代入する。
今回のデータセットの1列目に日付情報、2列目以降に数字・数値問わない縦軸となるデータがあることを前提にデータを読み込んでいます。

```c++
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <cmath>
#include <ctime>
#include <math.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cstdio>
#include <time.h>
#include <numeric>
#include <unistd.h>
#include <cctype>
#include <direct.h>
#include "Python.h"
#include "datetime.h"
#include "matplotlibcpp.h"

namespace plt = matplotlibcpp;

while (getline(ifs_csv_file, str_buf)) {
      std::istringstream i_stream(str_buf);
      std::string i_stream_tmp =  i_stream.str();
      long long row_n = std::count(i_stream_tmp.cbegin(), i_stream_tmp.cend(), ',') ; 
      while (getline(i_stream, str_line_buf, '\n')) {
          for (long long i = 0; i <= row_n; i++) {
              tokun = str_line_buf.substr(0, str_line_buf.find(delimiter));
              vvec.push_back(tokun);
              str_line_buf.erase(0, str_line_buf.find(delimiter) + delimiter.length());   
          }
          //After second Row reading
          if (nn != 0 & !(vvec[1].empty())) { 
              time_t t = string_to_time_t(vvec[1]);
              char longdate[30];
              strftime(longdate, 20, "%Y%m%d%H%M%S", localtime(&t));
              tvec.push_back(t);
              for (long long k = 2; k <= n; k++) {
                  if (!(vvec[k].empty()) && isdigit(vvec[k][0])){
                      double ytmp = stod(vvec[k]);
                      TY[k-1].push_back(std::make_pair(t,ytmp));
                      ty.push_back(std::make_pair(t,ytmp));
                  }
              }
           //Initial Row reading
          }else if (nn == 0){ 
              for (long long k = 0; k <= n; k++) {
                  label_vec.push_back(vvec[k]); 
              }
          }
          nn =+ 1;
          vvec.clear();     
      }
}
```
## <font color="Green">時間の軸ラベルを生成する<font>
matplotlibの横軸のラベルベクトルを作成する。
今回は横軸を日付にしたいので、実際の横軸の秒値を一日毎に数えて対応する日付を文字で入れている。

```c++
    //横軸時間のベクトルの重複を消し、日付若い順にソートする。
    sort(tvec.begin(), tvec.end());

    //x-axis Min Value
    time_t t_min1 = tvec[0]; 
    char ltmp_min[20];
    strftime(ltmp_min, 20, "%Y/%m/%d 00:00", localtime(&t_min1));
    std::string Ltmp_min = ltmp_min;
    time_t Lttmp_min = string_to_time_t(Ltmp_min);
    time_t t_min = Lttmp_min;

    //x-axis Max Value
    size_t tlen;
    tlen = tvec.size();
    time_t t_max = tvec[tlen-1];

    vector<pair<string,long long>> Ltx = time_t_to_date(t_min,t_max);
    
    //横軸のラベルに使う値と日付文字ベクトルを作成。
    for (auto& i: Ltx) {
      Label_strdate_row.push_back(i.first);
      Label_seconddate_row.push_back(i.second);
    }
```

## <font color="Green">最後にmatplotlib形式でGraphを書く。<font>
 
```c++
    std::vector<double> ycounts(Lx_row.size()), y_sum(Lx_row.size()), y_ave(Lx_row.size());
    for (long long j = 0; j < lvec.size(); j++) {
        ycounts.resize(Lx_row.size());
        y_sum.resize(Lx_row.size());
        y_ave.resize(Lx_row.size());       
        sort(TY[j].begin(), TY[j].end());
 
        for (auto& i: TY[j]) {
            time_hour = (long long)i.first;
            t.push_back(i.first);
            y.push_back(i.second);
            y_sum[(i.first-Lt_row[0])/86414] = y_sum[(i.first-Lt_row[0])/86414] + i.second;
            ycounts[(i.first-Lt_row[0])/86414] = ycounts[(i.first-Lt_row[0])/86414] + 1;           
        }
        for (long long k = 0; k <  Lx_row.size(); k++) {
            y_ave[k] = y_sum[k]/ycounts[k];
        }

        //matplotlib pltでグラフ作成
        plt::figure_size(600, 300);
        plt::plot(Lt, y_ave, "r-o"); 
        plt::grid("true");
        plt::ylabel("");
        plt::xlabel("");
        std::map<string,string> m;
        m["rotation"] = "vertical";
        m["fontsize"] = "small";
        m["fontstyle"] = "normal";
        m["fontstretch"] = "normal";
        plt::xticks(Lt_row,Lx_row,m);
        plt::axis("tight");//on,off,equal,scaled,tight,auto,image,square
        plt::title(lvec[k]);
        _mkdir("./image");
        std::string out_image_name = "./image/image_" + to_string(k);
        plt::tight_layout();
        plt::save(out_image_name);
        plt::close();
        t.clear();
        y.clear();
        y_sum.clear();
        y_ave.clear();
        ycounts.clear();
        k = k + 1;
 
    }
```

## <font color="Green">makeでコンパイル<font>
main.cppと同じフォルダにMakefileを以下のように作成し、makeする。


```makefile
CC            = g++
CFLAGS        = -IC:/matplotlib-cpp \
	-IC:/Python39/include \
	-IC:/Python39/Lib/site-packages/numpy/core/include \
	-IC:/Python39/Lib/site-packages/numpy/core/include/numpy
LDFLAGS       = -LC:/Python39/libs
LIBS          = -lpython39
OBJS          = main.cpp
PROGRAM       = output

all: $(PROGRAM)

$(PROGRAM): $(OBJS)
	$(CC) -o $(PROGRAM) $(OBJS) $(CFLAGS) $(LDFLAGS) $(LIBS)
```

するとoutput.exeができる。
できたプログラムでトレンドチャートを作るとこんなのが書ける。
乱数で作った以下のテストデータでトレンドチャートを書いてみよう。

|datetime|Para1|Para2|
|---|---|---|
|2021/5/1 0:00|0.01|0.85|
|2021/5/1 1:00|0.54|0.70|
|2021/5/1 2:00|0.09|0.00|
|2021/5/1 3:00|0.74|0.06|
|2021/5/1 4:00|0.50|0.03|
|2021/5/1 5:00|0.95|0.49|
|2021/5/1 6:00|0.62|0.45|
|2021/5/1 7:00|0.55|0.98|
|2021/5/1 8:00|0.42|0.34|
|2021/5/1 8:59|0.12|0.97|

出力はこんな感じ。よく見るmatplotlibと同じformatのグラフができる。
![image_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483556/ff9edb3d-4817-acc2-5ce5-6c32fc9cd1ef.png)

## まとめ
いかがでしたか。今回はC プロットライブラリ matplotlib-cppでのグラフ作成を紹介しました。
もともとpython matplotlibを使っていた人ならそれなりに理解しやすいのではないかと思います。
実際グラフを書くときは細かい調整をしたりするので、Pythonで作ったほうがいいですね。。。
大量にグラフを作る必要があるときは、C言語なので処理が速く、まぁ一応メリットはあるかなという感じです。
以上、ありがとうございました。