// // Created by Alan Freitas on 26/08/20. // #include "gnuplot.h" #include #include #include #include #include #include #include #ifdef __has_include #if __has_include() #include #else #define CXX_NO_FILESYSTEM #endif #else #define CXX_NO_FILESYSTEM #endif #ifdef MATPLOT_HAS_FBUFSIZE #include static size_t gnuplot_pipe_capacity(FILE *f) { size_t sz = __fbufsize(f); return sz != 0 ? sz : matplot::backend::gnuplot::pipe_capacity_worst_case; } #else static size_t gnuplot_pipe_capacity(FILE *) { return matplot::backend::gnuplot::pipe_capacity_worst_case; } #endif // MATPLOT_HAS_FBUFSIZE namespace matplot::backend { bool gnuplot::consumes_gnuplot_commands() { return true; } gnuplot::gnuplot() { // 1st option: terminal in GNUTERM environment variable #if defined(_MSC_VER) char *environment_terminal; size_t len; errno_t err = _dupenv_s(&environment_terminal, &len, "GNUTERM"); const bool env_found = err == 0 && environment_terminal != nullptr; #else char *environment_terminal = std::getenv("GNUTERM"); bool env_found = environment_terminal != nullptr; #endif if (env_found) { if (terminal_is_available(environment_terminal)) { terminal_ = environment_terminal; } #if defined(_WIN32) || defined(_WIN64) || defined(__MINGW32__) } else if (terminal_is_available("wxt")) { // 2nd option: wxt on windows, even if not default terminal_ = "wxt"; #endif } else if (terminal_is_available("qt")) { // 3rd option: qt terminal_ = "qt"; } else { // 4rd option: default terminal type terminal_ = default_terminal_type(); } // Open the gnuplot pipe_ int perr; if constexpr (windows_should_persist_by_default) { perr = pipe_.open("gnuplot --persist"); } else { perr = pipe_.open("gnuplot"); } // Check if everything is OK if (perr != 0 || !pipe_.opened()) { std::cerr << "Opening the gnuplot failed: "; std::cerr << pipe_.error() << std::endl; std::cerr << "Please install gnuplot 5.2.6+: http://www.gnuplot.info" << std::endl; } } gnuplot::~gnuplot() { if constexpr (dont_let_it_close_too_fast) { auto time_since_last_flush = std::chrono::high_resolution_clock::now() - last_flush_; if (time_since_last_flush < std::chrono::seconds(5)) { std::this_thread::sleep_for(std::chrono::seconds(5) - time_since_last_flush); } } flush_commands(); run_command("exit"); flush_commands(); } bool gnuplot::is_interactive() { return output_.empty(); } const std::string &gnuplot::output() { return output_; } const std::string &gnuplot::output_format() { return terminal_; } #ifdef STRING_VIEW_CONSTEXPR_BUG #define SV_CONSTEXPR #else #define SV_CONSTEXPR constexpr #endif bool gnuplot::output(const std::string &filename) { if (filename.empty()) { output_ = filename; terminal_ = default_terminal_type(); return true; } // look at the extension #ifndef CXX_NO_FILESYSTEM namespace fs = std::filesystem; fs::path p{filename}; std::string ext = p.extension().string(); #else std::string ext = filename.substr(filename.find_last_of('.')); #endif // check terminal for that extension SV_CONSTEXPR auto exts = extension_terminal(); auto it = std::find_if(exts.begin(), exts.end(), [&](const auto &e) { return e.first == ext; }); // if there is a terminal if (it != exts.end()) { // terminal name is the file format output(filename, std::string(it->second)); return true; } else { // set file format to dumb std::cerr << "No gnuplot terminal for " << ext << " files" << std::endl; std::cerr << "Setting terminal to \"dumb\"" << std::endl; output(filename, "dumb"); return false; } } bool gnuplot::output(const std::string &filename, const std::string &format) { // If filename is empty - format should be interactive // We don't check extension_terminal because that's only // for non-interactive terminal if (filename.empty()) { output_ = filename; terminal_ = format; return true; } // Check if file format is valid SV_CONSTEXPR auto exts = extension_terminal(); auto it = std::find_if(exts.begin(), exts.end(), [&](const auto &e) { return e.second == format; }); if (it == exts.end()) { std::cerr << format << " format does not exist for gnuplot backend" << std::endl; return false; } // Create file if it does not exist #ifndef CXX_NO_FILESYSTEM namespace fs = std::filesystem; fs::path p{filename}; if (!p.parent_path().empty() && !fs::exists(p.parent_path())) { fs::create_directory(p.parent_path()); if (!fs::exists(p.parent_path())) { std::cerr << "Could not find or create " << p.parent_path() << std::endl; return false; } } #endif output_ = filename; terminal_ = format; // Append extension if needed #ifndef CXX_NO_FILESYSTEM std::string ext = p.extension().string(); #else std::string ext = filename.substr(filename.find_last_of('.')); #endif if (ext.empty()) { output_ += it->first; } return true; } unsigned int gnuplot::width() { return position_[2]; } unsigned int gnuplot::height() { return position_[3]; } void gnuplot::width(unsigned int new_width) { position_[2] = new_width; if (terminal_has_position_option(terminal_)) { run_command("set terminal " + terminal_ + " position " + num2str(position_[0]) + "," + num2str(position_[1])); } if (terminal_ == "dumb") { run_command("set terminal dumb " + num2str(position_[2]) + " " + num2str(position_[3])); } else { if (terminal_has_size_option(terminal_)) { run_command("set terminal " + terminal_ + " size " + num2str(position_[2]) + "," + num2str(position_[3])); } } } void gnuplot::height(unsigned int new_height) { position_[3] = new_height; if (terminal_has_position_option(terminal_)) { run_command("set terminal " + terminal_ + " position " + num2str(position_[0]) + "," + num2str(position_[1])); } if (terminal_ == "dumb") { run_command("set terminal dumb " + num2str(position_[2]) + " " + num2str(position_[3])); } else { if (terminal_has_size_option(terminal_)) { run_command("set terminal " + terminal_ + " size " + num2str(position_[2]) + "," + num2str(position_[3])); } } } unsigned int gnuplot::position_x() { return position_[0]; } unsigned int gnuplot::position_y() { return position_[1]; } void gnuplot::position_x(unsigned int new_position_x) { position_[0] = new_position_x; if (terminal_has_position_option(terminal_)) { run_command("set terminal " + terminal_ + " position " + num2str(position_[0]) + "," + num2str(position_[1])); } if (terminal_has_size_option(terminal_)) { run_command("set terminal " + terminal_ + " size " + num2str(position_[2]) + "," + num2str(position_[3])); } } void gnuplot::position_y(unsigned int new_position_y) { position_[1] = new_position_y; if (terminal_has_position_option(terminal_)) { run_command("set terminal " + terminal_ + " position " + num2str(position_[0]) + "," + num2str(position_[1])); } if (terminal_has_size_option(terminal_)) { run_command("set terminal " + terminal_ + " size " + num2str(position_[2]) + "," + num2str(position_[3])); } } bool gnuplot::new_frame() { // always accept starting a new frame return true; } bool gnuplot::render_data() { return flush_commands(); } bool gnuplot::flush_commands() { if constexpr (dont_let_it_close_too_fast) { last_flush_ = std::chrono::high_resolution_clock::now(); } pipe_.flush("\n"); if constexpr (trace_commands) { std::cout << "\n\n\n\n" << std::endl; } return true; } bool gnuplot::supports_fonts() { return terminal_has_font_option(terminal_); } void gnuplot::run_command(const std::string &command) { if (!pipe_.opened()) { return; } size_t pipe_capacity = gnuplot_pipe_capacity(pipe_.file()); if (command.size() + bytes_in_pipe_ > pipe_capacity) { flush_commands(); bytes_in_pipe_ = 0; } if (!command.empty()) { pipe_.write(command); } // proc_write(&pipe_, "; "); pipe_.write("\n"); bytes_in_pipe_ += command.size(); if constexpr (trace_commands) { std::cout << command << std::endl; } } void gnuplot::include_comment(const std::string &comment) { if (include_comments_) { run_command("# " + comment); } } /// returns the next word in text after prefix terminated with white space. static std::string_view word_after(std::string_view text, std::string_view prefix) { auto res = text.substr(0,0); if (auto b = text.find(prefix); b != std::string_view::npos) { b += prefix.length(); while (b < text.length() && std::isspace(text[b])) ++b; // skip white space before word auto e = b; while (e < text.length() && !std::isspace(text[e])) ++e; // scan until white space or end res = text.substr(b, e-b); } return res; } std::string gnuplot::default_terminal_type() { static std::string terminal_type; const bool dont_know_term_type = terminal_type.empty(); if (dont_know_term_type) { terminal_type = run_and_get_output("gnuplot -e \"show terminal\" 2>&1"); terminal_type = word_after(terminal_type, "terminal type is "); const bool still_dont_know_term_type = terminal_type.empty(); if (still_dont_know_term_type) { terminal_type = "qt"; } } return terminal_type; } bool gnuplot::terminal_is_available(std::string_view term) { std::string msg = run_and_get_output("gnuplot -e \"set terminal " + std::string{term} + "\" 2>&1"); return msg.empty(); } template void convert_to(std::string_view text, T& value) { std::from_chars(text.data(), text.data() + text.length(), value); } std::tuple gnuplot::gnuplot_version() { constexpr auto version_zero = std::make_tuple(0, 0, 0); static auto version = version_zero; if (version == version_zero) { // unknown version const auto version_str = run_and_get_output("gnuplot --version 2>&1"); // gnuplot version_str example: "5.2 patchlevel 6" const auto major_minor = word_after(version_str, "gnuplot"); // "5.2" const auto minor = word_after(major_minor, "."); // "2" const auto patch = word_after(version_str, "patchlevel"); // "6" if (!major_minor.empty() && !minor.empty() && !patch.empty()) { convert_to(major_minor, std::get<0>(version)); convert_to(minor, std::get<1>(version)); convert_to(patch, std::get<2>(version)); } if (version == version_zero) // still unknown version = {5, 2, 6}; // assume by convention } return version; } bool gnuplot::gnuplot_includes_legends() { return gnuplot_version() >= std::make_tuple(5, 2, 6); } bool gnuplot::gnuplot_has_wall_option() { return gnuplot_version() >= std::make_tuple(5, 5, 0); } bool gnuplot::gnuplot_supports_keyentry() { return gnuplot_version() >= std::make_tuple(5, 2, 6); } bool gnuplot::terminal_has_title_option(const std::string &t) { SV_CONSTEXPR std::string_view whitelist[] = { "qt", "aqua", "caca", "canvas", "windows", "wxt", "x11"}; return std::find(std::begin(whitelist), std::end(whitelist), t) != std::end(whitelist); } bool gnuplot::terminal_has_size_option(const std::string &t) { // Terminals that have the size option *in the way we expect it to work* // This includes only the size option with {width, height} and not // the size option for cropping or scaling SV_CONSTEXPR std::string_view whitelist[] = { "qt", "aqua", "caca", "canvas", "eepic", "emf", "gif", "jpeg", "pbm", "png", "pngcairo", "sixelgd", "tkcanvas", "windows", "wxt", "svg"}; return std::find(std::begin(whitelist), std::end(whitelist), t) != std::end(whitelist); } bool gnuplot::terminal_has_position_option(const std::string &t) { SV_CONSTEXPR std::string_view whitelist[] = {"qt", "windows", "wxt"}; return std::find(std::begin(whitelist), std::end(whitelist), t) != std::end(whitelist); } bool gnuplot::terminal_has_enhanced_option(const std::string &t) { SV_CONSTEXPR std::string_view whitelist[] = { "canvas", "postscript", "qt", "aqua", "caca", "canvas", "dumb", "emf", "enhanced", "jpeg", "pdf", "pdfcairo", "pm", "png", "pngcairo", "postscript", "sixelgd", "tkcanvas", "windows", "wxt", "x11", "ext", "wxt"}; return std::find(std::begin(whitelist), std::end(whitelist), t) != std::end(whitelist); } bool gnuplot::terminal_has_color_option(const std::string &t) { SV_CONSTEXPR std::string_view whitelist[] = { "postscript", "aifm", "caca", "cairolatex", "context", "corel", "eepic", "emf", "epscairo", "epslatex", "fig", "lua tikz", "mif", "mp", "pbm", "pdf", "pdfcairo", "pngcairo", "postscript", "pslatex", "pstex", "tgif", "windows"}; return std::find(std::begin(whitelist), std::end(whitelist), t) != std::end(whitelist); } bool gnuplot::terminal_has_font_option(const std::string &t) { // This includes terminals that don't have the font option // and terminals for which we want to use only the default fonts // We prefer a blacklist because it's better to get a warning // in a false positive than remove the fonts in a false negative. SV_CONSTEXPR std::string_view blacklist[] = { "dxf", "eepic", "emtex", "hpgl", "latex", "mf", "pcl5", "pslatex", "pstex", "pstricks", "qms", "tek40xx", "tek410x", "texdraw", "tkcanvas", "vttek", "xterm", "jpeg", "dumb", "tpic", "sixelgd", "png", "lua", "jpeg", "epscairo", "epslatex", "canvas", "dumb", "dxy800a", "emxvga", "pdfcairo", "cgi", "gif", "gpic", "grass", "hp2623a", "hp2648", "hp500c", "hpgl", "pcl5", "hpljii", "hppj", "imagen", "linux", "lua", "lua tikz", "mf", "mif", "mp", "pbm", "pm", "qms", "regis", "svga", "texdraw", "tikz", "tpic", "vgagl", "vws", "pdf"}; return std::find(std::begin(blacklist), std::end(blacklist), t) == std::end(blacklist); } } // namespace matplot::backend