如何在C++项目中优雅地绘制图表
- matplotlib-cpp
- prepare
- matplotlibcpp.h
- python3
- VS project
- Gnuplot
- install
- Test
- MathGL
在C++项目中,在进行一些数据分析时往往不够直观,若能借助图表进行分析可以达到事半功倍的效果,通常可以将数据写出到文本,再在excel或python上进行图表绘制和分析,那为什么不直接在像python一样,借助三方库进行图表绘制呢?以下将介绍简单易行的方法,尝试在C++中优雅地绘制图表(测试环境:Windows+VS2019)。
matplotlib-cpp
实际上是调用Python中的matplotlib提供的C++ API,无需编译,直接包含头文件然后在C++中配置相应python环境就可以,对于熟悉python的伙伴来说几乎没有学习成本(其本身语法也很简单)。
prepare
- 可访问github的网络
- git(非必须)
- python3(< 3.11)
- vs环境
matplotlibcpp.h
有git的直接clone到本地
git clone https://github.com/lava/matplotlib-cpp
PS D:\Project\CMakeProject> git clone https://github.com/lava/matplotlib-cpp
Cloning into 'matplotlib-cpp'...
remote: Enumerating objects: 645, done.
remote: Counting objects: 100% (49/49), done.
remote: Compressing objects: 100% (23/23), done.
remote: Total 645 (delta 29), reused 26 (delta 26), pack-reused 596 (from 2)
Receiving objects: 100% (645/645), 758.80 KiB | 6.27 MiB/s, done.
Resolving deltas: 100% (372/372), done.
没有git可以下载代码或者直接在线复制 matplotlibcpp.h
对 matplotlibcpp.h
作如下修改
新版的
matplotlibcpp.h
中支持了C++17语法,将其改回C++11,且有重定义,需要注释
将353行至356行代码用以下代码覆盖
static_assert(sizeof(long long) == 8, "long type must occupy 8 bytes");
//template <> struct select_npy_type<long long> { const static NPY_TYPES type = NPY_INT64; };
static_assert(sizeof(unsigned long long) == 8, "long type must occupy 8 bytes");
//template <> struct select_npy_type<unsigned long long> { const static NPY_TYPES type = NPY_UINT64; };
python3
matplotlibcpp.h
语法与python3.11以上版本不兼容,报错如下
1>matpoltlibTest.cpp
1>D:\Program Files\MatplotlibCpp\matplotlibcpp.h(174): error C4996: 'Py_SetProgramName': deprecated in 3.11
1>d:\programdata\anaconda3\include\pylifecycle.h(37): note: 参见“Py_SetProgramName”的声明
1>D:\Program Files\MatplotlibCpp\matplotlibcpp.h(182): error C4996: 'PySys_SetArgv': deprecated in 3.11
1>d:\programdata\anaconda3\include\sysmodule.h(13): note: 参见“PySys_SetArgv”的声明
使用3.11以下的python版本即可,这里我创建一个新的虚拟环境
(base)>conda create -n Env310 python=3.10.16
(base)>conda activate Env310
(Env310)>conda install matplotlib numpy
将 复制到 D:\Program Files\MatplotlibCpp 下(非必须)
VS project
创建一个vs项目,切换为Release x64平台,在属性管理器的【Release | 64】下中新增一个属性表
双击该属性表,开始配置
【通用属性】-【VC++目录】-【包含目录】添加:
D:\Program Files\MatplotlibCpp
D:\ProgramData\anaconda3\envs\Env310\include
D:\ProgramData\anaconda3\envs\Env310\Lib\site-packages\numpy\_core\include
注意:不同版本的numpy包含目录所在路径可能不同,可通过以下代码确定
import numpy
print(numpy.get_include())
【通用属性】-【VC++目录】-【库目录】添加:
D:\ProgramData\anaconda3\envs\Env310\libs
【通用属性】-【链接器】-【常规】-【附加库目录】添加:
D:\ProgramData\anaconda3\envs\Env310
【通用属性】-【链接器】-【输入】-【附加依赖项】添加:
python3.lib
保存属性表,在解决方案管理器中右击,更改属性
【配置属性】-【调试】-【工作目录】更改为
D:\ProgramData\anaconda3\envs\Env310
添加测试代码,如
#include <cmath>
#include "matplotlibcpp.h"namespace plt = matplotlibcpp;#define M_PI 3.141592653589793238432643int main()
{// Prepare data.int n = 5000; // number of data pointsstd::vector<double> x(n), y(n);for (int i = 0; i < n; ++i){double t = 2 * M_PI * i / n;x.at(i) = 16 * sin(t) * sin(t) * sin(t);y.at(i) = 13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t);}// plot() takes an arbitrary number of (x,y,format)-triples.// x must be iterable (that is, anything providing begin(x) and end(x)),// y must either be callable (providing operator() const) or iterable.plt::plot(x, y, "r-", x, [](double d){return 12.5 + abs(sin(d));}, "k-");// show plotsplt::show();
}
运行,可得图片如下,配置完成
Gnuplot
无需特定的环境和复杂的配置,直接调用编译好的二进制文件,可通过命令在终端中使用,需要一定学习成本。
install
在官网 可找到最新稳定版下载页面,下载编译好的二进制文件。
解压后长这个样子:
在bin
文件夹下双击打开gnupolt.exe
,输入测试代码如下:
plot [-6:6] [-3:3] sin(x),cos(x)
可得:
将整个gunplot
文件夹剪切至D:\Program Files\
下,并重命名为Gnuplot
(个人喜好),然后将D:\Program Files\Gnuplot\bin
添加至系统环境变量Path
中,通过重启或者其他方式确保环境变量生效(打开任意终端,输入gunplot未报错即可)。
Test
找一段测试代码在vs项目中测试:
#include <stdio.h>void main()
{FILE* pipe = _popen("gnuplot", "w");if (pipe == NULL){exit(-1);}fprintf(pipe, "set terminal wxt size 600, 400\n");fprintf(pipe, "unset border\n");fprintf(pipe, "set dummy u, v\n");fprintf(pipe, "set angles degrees\n");fprintf(pipe, "set parametric\n");fprintf(pipe, "set view 60, 136, 1.22, 1.26\n");fprintf(pipe, "set samples 64, 64\n");fprintf(pipe, "set isosamples 13, 13\n");fprintf(pipe, "set mapping spherical\n");fprintf(pipe, "set style data lines\n");fprintf(pipe, "unset xtics\n");fprintf(pipe, "unset ytics\n");fprintf(pipe, "unset ztics\n");fprintf(pipe, "set title 'Labels colored by GeV plotted in spherical coordinate system'\n");fprintf(pipe, "set urange [ -90.0000 : 90.0000 ] noreverse nowriteback\n");fprintf(pipe, "set vrange [ 0.00000 : 360.000 ] noreverse nowriteback\n");fprintf(pipe, "set xrange [ * : * ] noreverse writeback\n");fprintf(pipe, "set x2range [ * : * ] noreverse writeback\n");fprintf(pipe, "set yrange [ * : * ] noreverse writeback\n");fprintf(pipe, "set y2range [ * : * ] noreverse writeback\n");fprintf(pipe, "set zrange [ * : * ] noreverse writeback\n");fprintf(pipe, "set cblabel 'GeV'\n");fprintf(pipe, "set cbrange [ 0.00000 : 8.00000 ] noreverse nowriteback\n");fprintf(pipe, "set rrange [ * : * ] noreverse writeback\n");fprintf(pipe, "set colorbox user\n");fprintf(pipe, "set colorbox vertical origin screen 0.9, 0.2 size screen 0.02, 0.75 front noinvert bdefault\n");fprintf(pipe, "NO_ANIMATION = 1\n");fprintf(pipe, "splot cos(u)*cos(v),cos(u)*sin(v),sin(u) notitle with lines lt 5\n");fprintf(pipe, "pause mouse\n");_pclose(pipe);
}
结果如下,配置成功。
MathGL
需要特定的环境和复杂的配置,其发布的库也是Linux平台下的,在Windows需要和其他依赖库一起重新编译。
同样在官网找到最新版下载入口,这里有两个版本,我两个版本都下载并配置了,很不幸都在测试阶段报错了,要么缺少头文件要么缺依赖库。虽然可以拿源码自己重新cmake,但其需要依赖其他可视化库(背离了简单易行的初衷),本着世上无难事只要肯放弃的精神,暂停MathGL的测试。
测试代码:
#include <mgl2/mgl.h>int main()
{mglGraph gr;gr.SetRange('x', -1, 1);gr.SetRange('y', -1, 1);gr.Axis();gr.FPlot("sin(1.7*2*pi*x) + sin(1.9*2*pi*x)", "r-2");gr.WriteFrame("test.png");return 0;
}
mathgl-8.0.3-ucrt64
报错:
1>D:\Program Files\MathGL2\include\mgl2\data_cf.h(26,10): fatal error C1083: 无法打开包括文件: “gsl/gsl_vector.h”: No such file or directory
mathgl-8.0.3.LGPL-ucrt64
报错:
1>D:\Program Files\MathGL2\include\mgl2\data.h(27,10): fatal error C1083: 无法打开包括文件: “armadillo”: No such file or directory
完事儿。