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 <iostream>
#include <string>
#include <vector>
#include <stdexcept>
#include <windows.h>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<LPWSTR>(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<double>& key,const std::vector<double>& 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<double>>& keys,const std::vector<std::vector<double>>& datasets,const std::vector<std::string>& titles,const std::vector<std::string>& 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<double> data = datasets[j];std::vector<double> 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<double>>& 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 <vector>
#include <cmath>int main() {try {// 示例1:绘制一维数组(正弦波)std::vector<std::vector<double>> Datas;std::vector<std::vector<double>> keys;std::vector<double> sinData;std::vector<double> 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<double> sinData1;std::vector<double> 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<std::string> 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<double>> heatmapData(20, std::vector<double>(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;
}