See More

#include "gnuplotpp/gnuplot_backend.hpp" #include "gnuplotpp/logging.hpp" #include "gnuplot_backend_internal.hpp" #include #include #include #include #include #include #include #ifdef GNUPLOTPP_HAS_FMT #include #endif namespace gnuplotpp { namespace { std::string shell_quote(const std::filesystem::path& p) { #ifdef _WIN32 return "\"" + p.string() + "\""; #else return "'" + p.string() + "'"; #endif } std::string gnuplot_quote(const std::filesystem::path& p) { return "'" + p.string() + "'"; } std::string msg_io(const std::string& prefix, const std::filesystem::path& path, const std::error_code& ec) { #ifdef GNUPLOTPP_HAS_FMT return fmt::format("{} '{}': {}", prefix, path.string(), ec.message()); #else std::ostringstream os; os << prefix << " '" << path.string() << "': " << ec.message(); return os.str(); #endif } std::string msg_text(const std::string& prefix, const std::string& detail) { #ifdef GNUPLOTPP_HAS_FMT return fmt::format("{}: {}", prefix, detail); #else return prefix + ": " + detail; #endif } void write_manifest(const Figure& fig, const std::filesystem::path& out_dir, const std::filesystem::path& script_path, const std::vector<:filesystem::path>& outputs) { const auto manifest_path = out_dir / "manifest.json"; std::ofstream os(manifest_path); if (!os.is_open()) { gnuplotpp::log::Warn("failed to write manifest: ", manifest_path.string()); return; } os << "{\n"; os << " \"preset\": " << static_cast(fig.spec().preset) << ",\n"; os << " \"palette\": " << static_cast(fig.spec().palette) << ",\n"; os << " \"text_mode\": " << static_cast(fig.spec().text_mode) << ",\n"; os << " \"size_in\": {\"w\": " << fig.spec().size.w << ", \"h\": " << fig.spec().size.h << "},\n"; os << " \"script\": \"" << script_path.string() << "\",\n"; os << " \"outputs\": [\n"; for (std::size_t i = 0; i < outputs.size(); ++i) { os << " \"" << outputs[i].string() << "\""; if (i + 1 < outputs.size()) { os << ","; } os << "\n"; } os << " ]\n"; os << "}\n"; } } // namespace GnuplotBackend::GnuplotBackend(std::string executable) : executable_(std::move(executable)) {} RenderResult GnuplotBackend::render(const Figure& fig, const std::filesystem::path& out_dir) { RenderResult result; result.status = RenderStatus::Success; std::error_code ec; std::filesystem::create_directories(out_dir / "tmp", ec); if (ec) { result.ok = false; result.status = RenderStatus::IoError; result.message = msg_io("failed to create output directories", out_dir / "tmp", ec); gnuplotpp::log::Error(result.message); return result; } const auto& all_axes = fig.all_axes(); std::vector<:vector>> data_files(all_axes.size()); for (std::size_t axis_idx = 0; axis_idx < all_axes.size(); ++axis_idx) { const auto& axis = all_axes[axis_idx]; data_files[axis_idx].reserve(axis.series().size()); for (std::size_t series_idx = 0; series_idx < axis.series().size(); ++series_idx) { const auto data_path = out_dir / "tmp" / ("ax" + std::to_string(axis_idx) + "_series" + std::to_string(series_idx) + ".dat"); data_files[axis_idx].push_back(data_path); const auto& series = axis.series()[series_idx]; std::ofstream data_os(data_path); if (!data_os.is_open()) { result.ok = false; result.status = RenderStatus::IoError; result.message = msg_text("failed to open data file for writing", data_path.string()); gnuplotpp::log::Error(result.message); return result; } data_os << std::scientific << std::setprecision(16); for (std::size_t i = 0; i < series.x.size(); ++i) { data_os << series.x[i] << " " << series.y[i]; if (series.spec.type == SeriesType::Band && i < series.y2.size()) { data_os << " " << series.y2[i]; } if (series.spec.type == SeriesType::ErrorBars && i < series.yerr_low.size() && i < series.yerr_high.size()) { data_os << " " << series.yerr_low[i] << " " << series.yerr_high[i]; } if (series.spec.type == SeriesType::Heatmap && i < series.z.size()) { data_os << " " << series.z[i]; } data_os << "\n"; } if (!data_os.good()) { result.ok = false; result.status = RenderStatus::IoError; result.message = msg_text("failed while writing data file", data_path.string()); gnuplotpp::log::Error(result.message); return result; } } } const auto script_path = out_dir / "tmp" / "figure.gp"; result.script_path = script_path; std::ofstream script_os(script_path); if (!script_os.is_open()) { result.ok = false; result.status = RenderStatus::IoError; result.message = msg_text("failed to open gnuplot script for writing", script_path.string()); gnuplotpp::log::Error(result.message); return result; } script_os << "set encoding utf8\n"; const bool any_series_opacity = detail::has_series_opacity(fig); for (const auto format : fig.spec().formats) { if (any_series_opacity && !detail::format_supports_line_alpha(format) && fig.spec().export_policy.warn_line_alpha_on_vector) { gnuplotpp::log::Warn("line opacity requested but ", detail::format_name(format), " terminal may ignore alpha; prefer PNG for true line transparency"); } if (fig.spec().text_mode == TextMode::LaTeX && format == OutputFormat::Png) { gnuplotpp::log::Warn("LaTeX text mode requested; PNG output falls back to enhanced text"); } const auto output_path = out_dir / ("figure." + detail::extension_for(format)); result.outputs.push_back(output_path); script_os << detail::terminal_for(format, fig.spec()) << "\n"; script_os << "set output " << gnuplot_quote(output_path) << "\n"; detail::emit_plot_body(script_os, fig, data_files, format); script_os << "set output\n"; } if (!script_os.good()) { result.ok = false; result.status = RenderStatus::IoError; result.message = msg_text("failed while writing gnuplot script", script_path.string()); gnuplotpp::log::Error(result.message); return result; } script_os.close(); if (!script_os) { result.ok = false; result.status = RenderStatus::IoError; result.message = msg_text("failed to finalize gnuplot script", script_path.string()); gnuplotpp::log::Error(result.message); return result; } if (fig.spec().interactive_preview) { const auto preview_path = out_dir / "tmp" / "interactive_preview.gp"; std::ofstream preview_os(preview_path); if (preview_os.is_open()) { preview_os << "set encoding utf8\n"; preview_os << "set terminal qt persist\n"; detail::emit_plot_body(preview_os, fig, data_files, OutputFormat::Png); preview_os << "pause mouse close\n"; } else { gnuplotpp::log::Warn("failed to write interactive preview script: ", preview_path.string()); } } const std::string check_cmd = #ifdef _WIN32 "where " + executable_ + " >NUL 2>&1"; #else "command -v " + executable_ + " >/dev/null 2>&1"; #endif if (std::system(check_cmd.c_str()) != 0) { result.ok = false; result.status = RenderStatus::ExternalToolMissing; result.message = msg_text("gnuplot executable not found; generated script/data only", executable_); gnuplotpp::log::Error(result.message); return result; } const auto log_path = out_dir / "tmp" / "gnuplot.log"; const std::string render_cmd = shell_quote(std::filesystem::path(executable_)) + " " + shell_quote(script_path) + " >" + shell_quote(log_path) + " 2>&1"; const int rc = std::system(render_cmd.c_str()); if (rc != 0) { result.ok = false; result.status = RenderStatus::ExternalToolFailure; #ifdef GNUPLOTPP_HAS_FMT result.message = fmt::format("gnuplot failed (exit={}); inspect {}", rc, log_path.string()); #else result.message = "gnuplot failed; inspect gnuplot.log in output tmp directory"; #endif gnuplotpp::log::Error(result.message); return result; } for (const auto& out : result.outputs) { std::error_code fsec; const auto size = std::filesystem::file_size(out, fsec); if (fsec || size == 0) { result.ok = false; result.status = RenderStatus::ExternalToolFailure; result.message = msg_text("gnuplot completed but output is missing/empty", out.string()); gnuplotpp::log::Error(result.message); return result; } } if (fig.spec().write_manifest) { write_manifest(fig, out_dir, result.script_path, result.outputs); } result.ok = true; result.status = RenderStatus::Success; result.message = "render completed"; gnuplotpp::log::Info(result.message); return result; } std::shared_ptr make_gnuplot_backend(std::string executable) { return std::make_shared(std::move(executable)); } } // namespace gnuplotpp