C++调用GnuPlot一维绘图
这篇文章介绍了一个C++类Gnuplot,用于通过管道与Gnuplot程序交互,实现数据可视化。类的主要功能包括:1) 通过Windows管道创建与Gnuplot进程的通信;2) 提供多种绘图方法,如绘制一维数组(折线图)、多条曲线和二维数组(热力图);3) 支持设置图表标题、坐标轴标签;4) 支持将图表保存为PNG或PDF格式。示例代码展示了如何使用该类绘制正弦波曲线和热力图。该封装简化了C++程序调用Gnuplot进行数据可视化的过程。
gnuplot_i.hpp
#ifndef GNUPLOT_I_HPP#define GNUPLOT_I_HPP#include #include #include #include #include class Gnuplot {private: HANDLE hChildStd_IN_Rd = NULL; HANDLE hChildStd_IN_Wr = NULL; HANDLE hChildStd_OUT_Rd = NULL; HANDLE hChildStd_OUT_Wr = NULL; HANDLE hChildProcess = NULL; bool is_open;public: Gnuplot() : is_open(false) { // 创建匿名管道 //创建安全属性结构体,设置句柄可继承(子进程能使用父进程创建的管道句柄) SECURITY_ATTRIBUTES saAttr; saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; //创建输出管道(子进程输出 → 父进程读取) if (!CreatePipe(&hChildStd_OUT_Rd, &hChildStd_OUT_Wr, &saAttr, 0)) throw std::runtime_error(\"创建输出管道失败\"); // 创建输入管道(父进程写入 → 子进程输入) //hChildStd_IN_Rd:子进程用于读取输入的句柄(读端) //hChildStd_IN_Wr:父进程用于写入命令的句柄(写端) if (!CreatePipe(&hChildStd_IN_Rd, &hChildStd_IN_Wr, &saAttr, 0)) throw std::runtime_error(\"创建输入管道失败\"); // 设置子进程启动信息 STARTUPINFO siStartInfo; PROCESS_INFORMATION piProcInfo; ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); siStartInfo.cb = sizeof(STARTUPINFO); siStartInfo.hStdError = hChildStd_OUT_Wr; siStartInfo.hStdOutput = hChildStd_OUT_Wr; siStartInfo.hStdInput = hChildStd_IN_Rd; siStartInfo.dwFlags |= STARTF_USESTDHANDLES; // 准备Gnuplot命令行 std::wstring gnuplot_cmd = L\"gnuplot.exe -persistent\"; // 创建Gnuplot进程 if (!CreateProcess(NULL, const_cast(gnuplot_cmd.c_str()), NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo)) { throw std::runtime_error(\"启动Gnuplot进程失败\"); } // 关闭不需要的句柄 CloseHandle(piProcInfo.hProcess); CloseHandle(piProcInfo.hThread); CloseHandle(hChildStd_OUT_Wr); CloseHandle(hChildStd_IN_Rd); is_open = true; } ~Gnuplot() { close(); } void close() { if (is_open) { sendCommand(\"quit\\n\"); CloseHandle(hChildStd_IN_Wr); CloseHandle(hChildStd_OUT_Rd); is_open = false; } } // 保存图表到文件 Gnuplot& saveToFile(const std::string& filename, const std::string& format = \"png\", int width = 800, int height = 600) { if (format == \"png\") { sendCommand(\"set terminal pngcairo size \" + std::to_string(width) + \",\" + std::to_string(height)); } else if (format == \"pdf\") { sendCommand(\"set terminal pdfcairo size \" + std::to_string(width/72.0) + \",\" + std::to_string(height/72.0) + \" font \'Arial,10\'\"); } sendCommand(\"set output \'\" + filename + \"\'\"); sendCommand(\"replot\"); sendCommand(\"set output\"); // 恢复标准输出 return *this; } // 绘制一维数组(折线图) Gnuplot& plotArray(const std::vector& key,const std::vector& data, const std::string& title = \"Data Plot\") { if(key.size() != data.size()) throw std::runtime_error(\"key.size() != data.size()\"); sendCommand(\"plot \'-\' with lines title \'\" + title + \"\'\"); // 发送数据 std::string dataStr; for (size_t i = 0; i < data.size(); ++i) { dataStr += std::to_string(key[i]) + \" \" + std::to_string(data[i]) + \"\\n\"; } dataStr += \"e\\n\"; DWORD dwWritten; WriteFile(hChildStd_IN_Wr, dataStr.c_str(), dataStr.length(), &dwWritten, NULL); return *this; } // 绘制多条曲线 Gnuplot& plotArrays(const std::vector<std::vector>& keys, const std::vector<std::vector>& datasets, const std::vector& titles, const std::vector& styles = {}) { if (datasets.empty() || datasets.size() != titles.size() || keys.size() != titles.size()) { throw std::invalid_argument(\"数据集和标题数量不匹配\"); } // 构建plot命令 std::string cmd = \"plot \"; for (size_t i = 0; i < datasets.size(); ++i) { std::string style = (i < styles.size()) ? styles[i] : \"lines\"; cmd += \"\'-\' with \" + style + \" title \'\" + titles[i] + \"\'\"; if (i < datasets.size() - 1) { cmd += \", \"; } } sendCommand(cmd); // 依次发送每个数据集 for (int j = 0; j < datasets.size(); j++) { std::vector data = datasets[j]; std::vector key = keys[j]; for (size_t i = 0; i < data.size(); ++i) { sendCommand(std::to_string(key[i]) + \" \" + std::to_string(data[i])); } sendCommand(\"e\"); } return *this; } // 绘制二维数组(热力图) Gnuplot& plotHeatmap(const std::vector<std::vector>& data, const std::string& title = \"Heatmap\") { sendCommand(\"set view map\"); sendCommand(\"set pm3d\"); sendCommand(\"set palette rgbformulae 33,13,10\"); sendCommand(\"splot \'-\' matrix with pm3d title \'\" + title + \"\'\"); // 发送矩阵数据 std::string dataStr; for (const auto& row : data) { for (double val : row) { dataStr += std::to_string(val) + \" \"; } dataStr += \"\\n\"; } dataStr += \"e\\n\"; DWORD dwWritten; WriteFile(hChildStd_IN_Wr, dataStr.c_str(), dataStr.length(), &dwWritten, NULL); return *this; } Gnuplot& sendCommand(const std::string& cmd) { if (!is_open) throw std::runtime_error(\"Gnuplot连接已关闭\"); DWORD dwWritten; std::string cmd_with_nl = cmd + \"\\n\"; if (!WriteFile(hChildStd_IN_Wr, cmd_with_nl.c_str(),cmd_with_nl.length(), &dwWritten, NULL)) { throw std::runtime_error(\"向Gnuplot发送命令失败\"); } return *this; } Gnuplot& setTitle(const std::string& title) { return sendCommand(\"set title \\\"\" + escapeString(title) + \"\\\"\"); } Gnuplot& setXLabel(const std::string& label) { return sendCommand(\"set xlabel \\\"\" + escapeString(label) + \"\\\"\"); } Gnuplot& setYLabel(const std::string& label) { return sendCommand(\"set ylabel \\\"\" + escapeString(label) + \"\\\"\"); } // 转义双引号和反斜杠 static std::string escapeString(const std::string& str) { std::string result; for (char c : str) { if (c == \'\\\"\' || c == \'\\\\\') result += \'\\\\\'; result += c; } return result; }};#endif // GNUPLOT_I_HPP
main.cpp
#include \"gnuplot_i.hpp\"#include #include int main() { try { // 示例1:绘制一维数组(正弦波) std::vector<std::vector> Datas; std::vector<std::vector> keys; std::vector sinData; std::vector key; for (double x = 0; x < 2 * 3.14159; x += 0.1) { sinData.push_back(std::sin(x)); key.push_back(x*10); } std::vector sinData1; std::vector key1; for (double x = 0; x < 100; x += 1) { sinData1.push_back(x); key1.push_back(x); } keys.push_back(key); Datas.push_back(sinData); keys.push_back(key1); Datas.push_back(sinData1); std::vector titles; titles.push_back(\"曲线1\"); titles.push_back(\"曲线2\"); // 创建Gnuplot对象 Gnuplot gp; // 绘制一维数据 gp.setTitle(\"\") .setXLabel(\"X\") .setYLabel(\"Y\") .plotArrays(keys, Datas, titles); Gnuplot gp1; gp1.setTitle(\"\") .setXLabel(\"X\") .setYLabel(\"Y\") .plotArrays(keys, Datas, titles); std::cout << \"按Enter键退出...\" << std::endl; std::cin.get(); /* * * // 示例2:绘制二维数组(热力图) std::vector<std::vector> heatmapData(20, std::vector(20, 0.0)); for (int i = 0; i < 20; ++i) { for (int j = 0; j < 20; ++j) { heatmapData[i][j] = std::sin(i/5.0) * std::cos(j/5.0); } } std::cout << \"按Enter键继续查看热力图...\" << std::endl; std::cin.get(); // 绘制二维数据 gp.setTitle(\"热力图示例\") .setXLabel(\"X轴\") .setYLabel(\"Y轴\") .plotHeatmap(heatmapData, \"函数 f(x,y) = sin(x/5)*cos(y/5)\"); // 保存热力图到文件 gp.saveToFile(\"heatmap.png\", \"png\"); std::cout << \"图表已生成并保存到 heatmap.png\" << std::endl; std::cout << \"按Enter键退出...\" << std::endl; std::cin.get(); */ } catch (const std::exception& e) { std::cerr << \"错误: \" << e.what() << std::endl; return 1; } return 0;}