diff --git a/source/matplot/backend/backend_interface.h b/source/matplot/backend/backend_interface.h index f499310f..21185211 100644 --- a/source/matplot/backend/backend_interface.h +++ b/source/matplot/backend/backend_interface.h @@ -41,6 +41,7 @@ namespace matplot { class MATPLOT_EXPORTS backend_interface { /// Virtual functions you can override to create any backend public: + virtual ~backend_interface() noexcept = default; /// \brief True if backend is in interactive mode /// One backends might support both interactive and /// non-interactive mode. @@ -213,7 +214,7 @@ namespace matplot { /// This is useful when tracing the gnuplot commands /// and when generating a gnuplot file. virtual void include_comment(const std::string &text); - }; + }; // class backend_interface } // namespace backend } // namespace matplot diff --git a/source/matplot/backend/gnuplot.cpp b/source/matplot/backend/gnuplot.cpp index 674e1ff2..b0251b3c 100644 --- a/source/matplot/backend/gnuplot.cpp +++ b/source/matplot/backend/gnuplot.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -317,13 +317,28 @@ namespace matplot::backend { } } + /// 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 = std::regex_replace(terminal_type, - std::regex("[^]*terminal type is ([^ ]+)[^]*"), "$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"; @@ -334,54 +349,45 @@ namespace matplot::backend { bool gnuplot::terminal_is_available(std::string_view term) { std::string msg = run_and_get_output("gnuplot -e \"set terminal " + - std::string(term.data()) + "\" 2>&1"); + 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() { - static std::tuple version{0, 0, 0}; - const bool dont_know_gnuplot_version_yet = - version == std::tuple({0, 0, 0}); - if (dont_know_gnuplot_version_yet) { - std::string version_str = - run_and_get_output("gnuplot --version 2>&1"); - std::string version_major = std::regex_replace( - version_str, - std::regex("[^]*gnuplot (\\d+)\\.\\d+ patchlevel \\d+ *"), - "$1"); - std::string version_minor = std::regex_replace( - version_str, - std::regex("[^]*gnuplot \\d+\\.(\\d+) patchlevel \\d+ *"), - "$1"); - std::string version_patch = std::regex_replace( - version_str, - std::regex("[^]*gnuplot \\d+\\.\\d+ patchlevel (\\d+) *"), - "$1"); - try { - std::get<0>(version) = std::stoi(version_major); - } catch (...) { - std::get<0>(version) = 0; - } - try { - std::get<1>(version) = std::stoi(version_minor); - } catch (...) { - std::get<1>(version) = 0; - } - try { - std::get<2>(version) = std::stoi(version_patch); - } catch (...) { - std::get<2>(version) = 0; - } - const bool still_dont_know_gnuplot_version = - version == std::tuple({0, 0, 0}); - if (still_dont_know_gnuplot_version) { - // assume it's 5.2.6 by convention - version = std::tuple({5, 2, 6}); + 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"}; diff --git a/source/matplot/backend/gnuplot.h b/source/matplot/backend/gnuplot.h index dc74abef..097e19a4 100644 --- a/source/matplot/backend/gnuplot.h +++ b/source/matplot/backend/gnuplot.h @@ -48,6 +48,9 @@ namespace matplot::backend { static std::string default_terminal_type(); static bool terminal_is_available(std::string_view); static std::tuple gnuplot_version(); + static bool gnuplot_includes_legends(); + static bool gnuplot_has_wall_option(); + static bool gnuplot_supports_keyentry(); static bool terminal_has_title_option(const std::string &t); static bool terminal_has_size_option(const std::string &t); static bool terminal_has_position_option(const std::string &t); diff --git a/source/matplot/core/axes_type.cpp b/source/matplot/core/axes_type.cpp index d7594a60..7c057502 100644 --- a/source/matplot/core/axes_type.cpp +++ b/source/matplot/core/axes_type.cpp @@ -426,7 +426,7 @@ namespace matplot { run_command("set polar"); } auto set_or_unset_axis = [this](class axis_type &ax, - std::string axis_name, + const std::string &axis_name, bool minor_ticks = false) { // cb is the only axis we don't unset if tics are empty // r-axis labels should still be handled even if axis is invisible since we use the grid @@ -694,10 +694,7 @@ namespace matplot { // Gnuplot version needs to be 5.2.6+ for keyentry bool ok = true; if (parent_->backend_->consumes_gnuplot_commands()) { - if (backend::gnuplot::gnuplot_version() < - std::tuple{5, 2, 6}) { - ok = false; - } + ok = backend::gnuplot::gnuplot_supports_keyentry(); } if (legend_ == nullptr || !legend_->visible() || !ok) { run_command("set key off"); @@ -916,9 +913,7 @@ namespace matplot { static bool msg_shown_once = false; // Gnuplot version needs to be 5.2.6+ for keyentry if (parent_->backend_->consumes_gnuplot_commands()) { - std::tuple v = - backend::gnuplot::gnuplot_version(); - if (v < std::tuple{5, 2, 6}) { + if (backend::gnuplot::gnuplot_includes_legends()) { if (!msg_shown_once) { std::cerr << "You need gnuplot 5.2.6+ to include legends" @@ -2745,9 +2740,9 @@ namespace matplot { } std::vector - axes_type::fplot(std::vector equations, - std::array x_range, - std::vector line_specs) { + axes_type::fplot(const std::vector &equations, + const std::array &x_range, + const std::vector &line_specs) { axes_silencer temp_silencer_{this}; std::vector res; auto it_line_specs = line_specs.begin(); @@ -2764,9 +2759,9 @@ namespace matplot { } std::vector - axes_type::fplot(std::vector equations, - std::vector x_range, - std::vector line_specs) { + axes_type::fplot(const std::vector& equations, + const std::vector& x_range, + const std::vector& line_specs) { return this->fplot(equations, to_array<2>(x_range), line_specs); } diff --git a/source/matplot/core/axes_type.h b/source/matplot/core/axes_type.h index 227003f1..caedf10e 100644 --- a/source/matplot/core/axes_type.h +++ b/source/matplot/core/axes_type.h @@ -979,15 +979,15 @@ namespace matplot { /// Lambda function line plot - list of functions std::vector - fplot(std::vector equations, - std::array x_range = {-5, 5}, - std::vector line_specs = {}); + fplot(const std::vector &equations, + const std::array &x_range = {-5, 5}, + const std::vector &line_specs = {}); /// Lambda function line plot - list of functions and line specs std::vector - fplot(std::vector equations, - std::vector x_range, - std::vector line_specs = {}); + fplot(const std::vector &equations, + const std::vector &x_range, + const std::vector &line_specs = {}); using implicit_function_type = std::function; diff --git a/source/matplot/core/figure_type.cpp b/source/matplot/core/figure_type.cpp index a4ff0903..a895d6ea 100644 --- a/source/matplot/core/figure_type.cpp +++ b/source/matplot/core/figure_type.cpp @@ -633,9 +633,7 @@ namespace matplot { void figure_type::run_window_color_command() { // In gnuplot 5.5 we have the wall function to set the axes color // with a rectangle workaround, which does not work well for 3d. - static const auto v = backend::gnuplot::gnuplot_version(); - const bool has_wall_option = - std::get<0>(v) > 5 || (std::get<0>(v) == 5 && std::get<1>(v) >= 5); + const bool has_wall_option = backend::gnuplot::gnuplot_has_wall_option(); // So we only plot the default background if it's not 3d or version is // higher than 5.5. Otherwise, gnuplot won't be able to set the axes // colors. @@ -895,4 +893,4 @@ namespace matplot { bool figure_type::should_close() { return backend_->should_close(); } -} // namespace matplot \ No newline at end of file +} // namespace matplot