如安在C++中优雅地绘制图表

[复制链接]
发表于 2025-4-17 08:42:10 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
在C++项目中,在进行一些数据分析时每每不够直观,若能借助图表进行分析可以到达事半功倍的效果,通常可以将数据写出到文本,再在excel或python上进行图表绘制和分析,那为什么不直接在像python一样,借助三方库进行图表绘制呢?以下将先容简单易行的方法,实验在C++中优雅地绘制图表(测试情况:Windows+VS2019)。
  
matplotlib

   现实上是调用Python中的matplotlib提供的C++ API,无需编译,直接包罗头文件然后在C++中设置相应python情况就可以,对于认识python的同伴来说险些没有学习成本(其本身语法也很简单)。
  prepare


  • 可访问github的网络
  • git(非必须)
  • python3(< 3.11)
  • vs情况

matplotlibcpp.h

有git的直接clone到本地:
  1. git clone https://github.com/lava/matplotlib-cpp.git
复制代码
没有git可以下载代码或者直接在线复制 matplotlibcpp.h。
对 matplotlibcpp.h 作如下修改:
   新版的matplotlibcpp.h中支持了C++17语法,将其改回C++11,且有重界说,必要表明。
  将353行至356行代码用以下代码覆盖:
  1. static_assert(sizeof(long long) == 8, "long type must occupy 8 bytes");
  2. //template <> struct select_npy_type<long long> { const static NPY_TYPES type = NPY_INT64; };
  3. static_assert(sizeof(unsigned long long) == 8, "long type must occupy 8 bytes");
  4. //template <> struct select_npy_type<unsigned long long> { const static NPY_TYPES type = NPY_UINT64; };
复制代码

python3

matplotlibcpp.h 语法与python3.11以上版本不兼容,报错如下:
  1. 1>matpoltlibTest.cpp
  2. 1>D:\Program Files\MatplotlibCpp\matplotlibcpp.h(174): error C4996: 'Py_SetProgramName': deprecated in 3.11
  3. 1>d:\programdata\anaconda3\include\pylifecycle.h(37): note: 参见“Py_SetProgramName”的声明
  4. 1>D:\Program Files\MatplotlibCpp\matplotlibcpp.h(182): error C4996: 'PySys_SetArgv': deprecated in 3.11
  5. 1>d:\programdata\anaconda3\include\sysmodule.h(13): note: 参见“PySys_SetArgv”的声明
复制代码
利用3.11以下的python版本即可,这里我创建一个新的假造情况:
  1. (base)>conda create -n Env310 python=3.10.16
  2. (base)>conda activate Env310
  3. (Env310)>conda install matplotlib numpy
复制代码
将 复制到 D:\Program Files\MatplotlibCpp 下(非必须)。

vs configure

创建一个vs项目,切换为Release x64平台,在属性管理器的【Release | 64】下中新增一个属性表:

双击该属性表,开始设置:
【通用属性】-【VC++目录】-【包罗目录】添加:
  1. D:\Program Files\MatplotlibCpp
  2. D:\ProgramData\anaconda3\envs\Env310
  3. \include
  4. D:\ProgramData\anaconda3\envs\Env310
  5. \Lib\site-packages\numpy\_core\include
复制代码

注意:不同版本的numpy包罗目录地点路径可能不同,可通过以下代码确定:
  1. import numpy
  2. print(numpy.get_include())
复制代码
【通用属性】-【VC++目录】-【库目录】添加:
  1. D:\ProgramData\anaconda3\envs\Env310
  2. \libs
复制代码

【通用属性】-【链接器】-【常规】-【附加库目录】添加:
  1. D:\ProgramData\anaconda3\envs\Env310
复制代码

【通用属性】-【链接器】-【输入】-【附加依赖项】添加:
  1. python3.lib
复制代码

保存属性表,在解决方案管理器中右击,更改属性:
【设置属性】-【调试】-【工作目录】更改为:
  1. D:\ProgramData\anaconda3\envs\Env310
复制代码

test

添加C++测试代码,如:
  1. #include <cmath>
  2. #include "matplotlibcpp.h"
  3. namespace plt = matplotlibcpp;
  4. #define M_PI 3.141592653589793238432643
  5. int main()
  6. {
  7.     // Prepare data.
  8.     int n = 5000; // number of data points
  9.         std::vector<double> x(n), y(n);
  10.     for (int i = 0; i < n; ++i)
  11.     {
  12.         double t = 2 * M_PI * i / n;
  13.         x.at(i) = 16 * sin(t) * sin(t) * sin(t);
  14.         y.at(i) = 13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t);
  15.     }
  16.     // plot() takes an arbitrary number of (x,y,format)-triples.
  17.     // x must be iterable (that is, anything providing begin(x) and end(x)),
  18.     // y must either be callable (providing operator() const) or iterable.
  19.     plt::plot(x, y, "r-", x, [](double d)
  20.         {
  21.             return 12.5 + abs(sin(d));
  22.         }, "k-");
  23.     // show plots
  24.     plt::show();
  25. }
复制代码
运行,可得图片如下,设置完成。


Gnuplot

   雷同matplotlib,先安装gnuplot本体,然后通过C++API调用库,不同的是其可通过下令在终端中利用,必要一定学习成本。
  prepare


  • 可访问github的网络
  • git(非必须)
  • boost
  • vs情况
gnuplot

在官网 可找到最新稳固版下载页面,下载编译好的二进制文件。

解压后长这个样子:

在bin文件夹下双击打开gnupolt.exe,输入测试代码如下:
  1. plot [-6:6] [-3:3] sin(x),cos(x)
复制代码
可得:

将整个gunplot文件夹剪切至D:\Program Files\下,并重命名为Gnuplot(个人喜好),然后将D:\Program Files\Gnuplot\bin添加至系统情况变量Path中,通过重启或者其他方式确保情况变量生效(打开任意终端,输入gunplot未报错即可)。

那么同样可以在c++代码中调用该exe:
  1. #include <stdio.h>
  2. void main()
  3. {
  4.         FILE* pipe = _popen("gnuplot", "w");
  5.         if (pipe == NULL)
  6.         {
  7.                 exit(-1);
  8.         }
  9.         fprintf(pipe, "set terminal wxt size 600, 400\n");
  10.         fprintf(pipe, "unset border\n");
  11.         fprintf(pipe, "set dummy u, v\n");
  12.         fprintf(pipe, "set angles degrees\n");
  13.         fprintf(pipe, "set parametric\n");
  14.         fprintf(pipe, "set view 60, 136, 1.22, 1.26\n");
  15.         fprintf(pipe, "set samples 64, 64\n");
  16.         fprintf(pipe, "set isosamples 13, 13\n");
  17.         fprintf(pipe, "set mapping spherical\n");
  18.         fprintf(pipe, "set style data lines\n");
  19.         fprintf(pipe, "unset xtics\n");
  20.         fprintf(pipe, "unset ytics\n");
  21.         fprintf(pipe, "unset ztics\n");
  22.         fprintf(pipe, "set title 'Labels colored by GeV plotted in spherical coordinate system'\n");
  23.         fprintf(pipe, "set urange [ -90.0000 : 90.0000 ] noreverse nowriteback\n");
  24.         fprintf(pipe, "set vrange [ 0.00000 : 360.000 ] noreverse nowriteback\n");
  25.         fprintf(pipe, "set xrange [ * : * ] noreverse writeback\n");
  26.         fprintf(pipe, "set x2range [ * : * ] noreverse writeback\n");
  27.         fprintf(pipe, "set yrange [ * : * ] noreverse writeback\n");
  28.         fprintf(pipe, "set y2range [ * : * ] noreverse writeback\n");
  29.         fprintf(pipe, "set zrange [ * : * ] noreverse writeback\n");
  30.         fprintf(pipe, "set cblabel 'GeV'\n");
  31.         fprintf(pipe, "set cbrange [ 0.00000 : 8.00000 ] noreverse nowriteback\n");
  32.         fprintf(pipe, "set rrange [ * : * ] noreverse writeback\n");
  33.         fprintf(pipe, "set colorbox user\n");
  34.         fprintf(pipe, "set colorbox vertical origin screen 0.9, 0.2 size screen 0.02, 0.75 front  noinvert bdefault\n");
  35.         fprintf(pipe, "NO_ANIMATION = 1\n");
  36.         fprintf(pipe, "splot cos(u)*cos(v),cos(u)*sin(v),sin(u) notitle with lines lt 5\n");
  37.         fprintf(pipe, "pause mouse\n");
  38.         _pclose(pipe);
  39. }
复制代码
结果如下:

但是,以这种方式,如果要批量绘制自界说的数据,仍必要先存数据为文本,然后再读取,这不是我想要的直接绘制的效果。但是不慌,再做一些简单设置就可以实现了。

gnuplot-iostream.h

有git的直接clone到本地
  1. git clone https://github.com/dstahlke/gnuplot-iostream.git
复制代码
没有git可以下载代码或者直接在线复制 gnuplot-iostream.h
对 gnuplot-iostream.h 作如下修改
   新版的gnuplot-iostream.h中支持了C++17语法,将其改回C++11,跟上面的matplotlibcpp.h是同样类型的改动
  168-169行:
  1. static_assert( is_like_stl_container<std::vector<int>>);
  2. static_assert(!is_like_stl_container<int>);
复制代码
改为:
  1. static_assert(is_like_stl_container<std::vector<int>>, "Should be STL-like container");
  2. static_assert(!is_like_stl_container<int>, "int is not STL-like container");
复制代码
186行:
  1. static_assert(is_boost_tuple_nulltype<boost::tuples::null_type>);
复制代码
改为:
  1. static_assert(is_boost_tuple_nulltype<boost::tuples::null_type>, "Should be boost null type");
复制代码
197-200行:
  1. static_assert( is_boost_tuple<boost::tuple<int>>);
  2. static_assert( is_boost_tuple<boost::tuple<int, int>>);
  3. static_assert(!is_boost_tuple<std::tuple<int>>);
  4. static_assert(!is_boost_tuple<std::tuple<int, int>>);
复制代码
改为:
  1. static_assert(is_boost_tuple<boost::tuple<int>>, "Should be boost::tuple");
  2. static_assert(is_boost_tuple<boost::tuple<int, int>>, "Should be boost::tuple");
  3. static_assert(!is_boost_tuple<std::tuple<int>>, "std::tuple is not boost::tuple");
  4. static_assert(!is_boost_tuple<std::tuple<int, int>>, "std::tuple is not boost::tuple");
复制代码
2436-2437行
  1. static_assert( is_eigen_matrix<Eigen::MatrixXf>);
  2. static_assert(!is_eigen_matrix<int>);
复制代码
改为
  1. static_assert(is_eigen_matrix<Eigen::MatrixXf>, "Should be Eigen::Matrix");
  2. static_assert(!is_eigen_matrix<int>, "int is not an Eigen::Matrix");
复制代码
2442行
  1. static_assert(dont_treat_as_stl_container<Eigen::MatrixXf>);
复制代码
改为
  1. static_assert(dont_treat_as_stl_container<Eigen::MatrixXf>, "Eigen::Matrix should not be treated as an STL container");
复制代码
然后在上述gnuplot的安装目录D:\Program Files\Gnuplot下新建include文件夹,将改动后的gnuplot-iostream.h拷贝过去。
boost

   由于gnuplot的C++调用必要boost支持,这里也先容一下boost的设置,因为有预编译好的库,所以也比较easy。
  在boost官网的下载页面找到预编译好的Windows库:

根据所需情况选择对应版本。

这里我根据本身的情况选择较早的1.80.0 VS140版(注意和本身的情况对应)。

双击运行:

更改安装目录:



vs configure

创建一个vs项目,切换为Release x64平台,在属性中将【平台工具集】改为Visual Studio 2015 (v140),在属性管理器的【Release | 64】下中新增一个属性表【Gnupolt_Release_x64.props】
双击该属性表,开始设置
【通用属性】-【VC++目录】-【包罗目录】添加:
  1. D:\Program Files\Gnuplot\include
  2. D:\Program Files\boost_1_80_0\boost
复制代码
【通用属性】-【VC++目录】-【库目录】添加:
  1. D:\Program Files\boost_1_80_0\lib64-msvc-14.0
复制代码

应用,保存属性表。

test

添加C++测试代码,如:
  1. #include <cmath>
  2. #include <vector>
  3. #include <gnuplot-iostream.h>
  4. #define M_PI 3.141592653589793238432643
  5. int main()
  6. {
  7.     // 初始化 Gnuplot 对象
  8.     Gnuplot gp;
  9.     // 设置标题和样式
  10.     gp << "set title 'Sharper Heart Shape'\n";
  11.     gp << "set size ratio -1\n"; // 确保 x 和 y 轴比例一致
  12.     gp << "set nokey\n";         // 不显示图例
  13.     gp << "set samples 1000\n";  // 增加采样点使曲线更平滑
  14.     gp << "set xrange [-20:20]\n"; // 设置 x 轴范围
  15.     gp << "set yrange [-20:20]\n"; // 设置 y 轴范围
  16.     // 定义参数化方程的数据点
  17.     std::vector<std::pair<double, double>> points;
  18.     const int num_points = 1000; // 数据点数量
  19.     for (int i = 0; i <= num_points; ++i)
  20.     {
  21.         double t = 2 * M_PI * i / num_points; // 参数 t 从 0 到 2π
  22.         double x = 16 * pow(sin(t), 3);      // 使用新的 x 方程
  23.         double y = (13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t)); // 使用新的 y 方程
  24.         points.emplace_back(x, y);
  25.     }
  26.     // 绘制爱心形状
  27.     gp << "plot '-' with lines lc rgb 'red' lw 2 title 'Heart'\n";
  28.     for (const auto& point : points)
  29.     {
  30.         gp << point.first << " " << point.second << "\n";
  31.     }
  32.     gp << "e\n"; // 结束数据流
  33.     return 0;
  34. }
复制代码
运行,可得图片如下,设置完成。


MathGL

   必要特定的情况和复杂的设置,其发布的库也是Linux平台下的,在Windows必要和其他依赖库一起重新编译。
  同样在官网找到最新版下载入口,这里有两个版本,我两个版本都下载并设置了,很不幸都在测试阶段报错了,要么缺少头文件要么缺依赖库。固然可以拿源码本身重新cmake,但其必要依赖其他可视化库(背离了简单易行的初衷),本着世上无难事只要肯放弃的精神,停息MathGL的测试。

测试代码:
  1. #include <mgl2/mgl.h>
  2. int main()
  3. {
  4.         mglGraph gr;
  5.         gr.SetRange('x', -1, 1);
  6.         gr.SetRange('y', -1, 1);
  7.         gr.Axis();
  8.         gr.FPlot("sin(1.7*2*pi*x) + sin(1.9*2*pi*x)", "r-2");
  9.         gr.WriteFrame("test.png");
  10.         return 0;
  11. }
复制代码
mathgl-8.0.3-ucrt64 报错:
  1. 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. 1>D:\Program Files\MathGL2\include\mgl2\data.h(27,10): fatal error C1083: 无法打开包括文件: “armadillo”: No such file or directory
复制代码

打完收工。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表