From b203af70d796d00f3d5ed99f3f61281aad68c1cd Mon Sep 17 00:00:00 2001 From: Amadeus Date: Thu, 10 Mar 2022 14:58:04 -0500 Subject: [PATCH 01/15] examples: fix segfault on exit with python3 --- examples/animation.cpp | 59 ++++++++++++++++++++---------------- examples/bar.cpp | 5 +++ examples/basic.cpp | 12 ++++++-- examples/basic.png | Bin 37402 -> 37470 bytes examples/colorbar.cpp | 7 +++++ examples/contour.cpp | 7 +++++ examples/fill.cpp | 7 +++++ examples/fill_inbetween.cpp | 39 ++++++++++++++---------- examples/imshow.cpp | 7 +++++ examples/lines3d.cpp | 9 +++++- examples/minimal.cpp | 6 ++++ examples/modern.cpp | 55 ++++++++++++++++++--------------- examples/nonblock.cpp | 7 +++++ examples/quiver.cpp | 9 +++++- examples/spy.cpp | 5 +++ examples/subplot2grid.cpp | 9 +++++- examples/surface.cpp | 7 +++++ examples/update.cpp | 7 +++++ examples/xkcd.cpp | 7 +++++ 19 files changed, 193 insertions(+), 71 deletions(-) diff --git a/examples/animation.cpp b/examples/animation.cpp index d979430..320c0e5 100644 --- a/examples/animation.cpp +++ b/examples/animation.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o animation $(python-config --includes) animation.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include #include "../matplotlibcpp.h" @@ -6,31 +10,34 @@ namespace plt = matplotlibcpp; int main() { - int n = 1000; - std::vector x, y, z; - - for(int i=0; i x, y, z; + + for(int i=0; i @@ -14,5 +18,6 @@ int main(int argc, char **argv) { plt::bar(test_data); plt::show(); + plt::detail::_interpreter::kill(); return (0); } diff --git a/examples/basic.cpp b/examples/basic.cpp index 2dc34c7..a8facb7 100644 --- a/examples/basic.cpp +++ b/examples/basic.cpp @@ -1,3 +1,8 @@ +// +// g++ -g -Wall -o basic -I/usr/include/python3.9 basic.cpp -lpython3.9 +// g++ -g -Wall -o basic $(python-config --includes) basic.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include #include @@ -5,7 +10,7 @@ namespace plt = matplotlibcpp; -int main() +int main() { // Prepare data. int n = 5000; @@ -15,7 +20,7 @@ int main() y.at(i) = sin(2*M_PI*i/360.0); z.at(i) = log(i); } - + // Set the size of output image = 1200x780 pixels plt::figure_size(1200, 780); @@ -41,4 +46,7 @@ int main() const char* filename = "./basic.png"; std::cout << "Saving result to " << filename << std::endl;; plt::save(filename); + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/basic.png b/examples/basic.png index 4d87ff01a24d2368ac80b73e14587e9bd58f7bf9..6b83650a0863f8d7ebe9f2da5a68ab396c46e279 100644 GIT binary patch literal 37470 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 BFYN#T literal 37402 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%0AEFGg;{EZPhaF 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 PVaZA #include @@ -29,4 +33,7 @@ int main() plt::show(); plt::close(); Py_DECREF(mat); + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/contour.cpp b/examples/contour.cpp index 9289d0a..11ab67c 100644 --- a/examples/contour.cpp +++ b/examples/contour.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o contour $(python-config --includes) contour.cpp $(python-config --ldflags --embed) +// + #include "../matplotlibcpp.h" #include @@ -21,4 +25,7 @@ int main() plt::contour(x, y, z); plt::show(); + + plt::detail::_interpreter::kill(); + return (0); } diff --git a/examples/fill.cpp b/examples/fill.cpp index 6059b47..8bcb2d7 100644 --- a/examples/fill.cpp +++ b/examples/fill.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o fill $(python-config --includes) fill.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include "../matplotlibcpp.h" #include @@ -32,4 +36,7 @@ int main() { plt::fill(x1, y1, {}); } plt::show(); + + plt::detail::_interpreter::kill(); + return (0); } diff --git a/examples/fill_inbetween.cpp b/examples/fill_inbetween.cpp index 788d008..6a99883 100644 --- a/examples/fill_inbetween.cpp +++ b/examples/fill_inbetween.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o fill_inbetween $(python-config --includes) fill_inbetween.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include "../matplotlibcpp.h" #include @@ -7,22 +11,25 @@ using namespace std; namespace plt = matplotlibcpp; int main() { - // Prepare data. - int n = 5000; - std::vector x(n), y(n), z(n), w(n, 2); - for (int i = 0; i < n; ++i) { - x.at(i) = i * i; - y.at(i) = sin(2 * M_PI * i / 360.0); - z.at(i) = log(i); - } + // Prepare data. + int n = 5000; + std::vector x(n), y(n), z(n), w(n, 2); + for (int i = 0; i < n; ++i) { + x.at(i) = i * i; + y.at(i) = sin(2 * M_PI * i / 360.0); + z.at(i) = log(i); + } + + // Prepare keywords to pass to PolyCollection. See + // https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.fill_between.html + std::map keywords; + keywords["alpha"] = "0.4"; + keywords["color"] = "grey"; + keywords["hatch"] = "-"; - // Prepare keywords to pass to PolyCollection. See - // https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.fill_between.html - std::map keywords; - keywords["alpha"] = "0.4"; - keywords["color"] = "grey"; - keywords["hatch"] = "-"; + plt::fill_between(x, y, z, keywords); + plt::show(); - plt::fill_between(x, y, z, keywords); - plt::show(); + plt::detail::_interpreter::kill(); + return (0); } diff --git a/examples/imshow.cpp b/examples/imshow.cpp index b11661e..2ed631b 100644 --- a/examples/imshow.cpp +++ b/examples/imshow.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o imshow $(python-config --includes) imshow.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include #include @@ -26,4 +30,7 @@ int main() // Show plots plt::save("imshow.png"); std::cout << "Result saved to 'imshow.png'.\n"; + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/lines3d.cpp b/examples/lines3d.cpp index fd4610d..84ef657 100644 --- a/examples/lines3d.cpp +++ b/examples/lines3d.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o lines3d $(python-config --includes) lines3d.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include "../matplotlibcpp.h" #include @@ -9,7 +13,7 @@ int main() std::vector x, y, z; double theta, r; double z_inc = 4.0/99.0; double theta_inc = (8.0 * M_PI)/99.0; - + for (double i = 0; i < 100; i += 1) { theta = -4.0 * M_PI + theta_inc*i; z.push_back(-2.0 + z_inc*i); @@ -27,4 +31,7 @@ int main() plt::set_zlabel("z label"); // set_zlabel rather than just zlabel, in accordance with the Axes3D method plt::legend(); plt::show(); + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/minimal.cpp b/examples/minimal.cpp index fbe1e1c..fd76a42 100644 --- a/examples/minimal.cpp +++ b/examples/minimal.cpp @@ -1,3 +1,6 @@ +// +// g++ -g -Wall -o minimal $(python-config --includes) minimal.cpp $(python-config --ldflags --embed) +// #include "../matplotlibcpp.h" namespace plt = matplotlibcpp; @@ -5,4 +8,7 @@ namespace plt = matplotlibcpp; int main() { plt::plot({1,3,2,4}); plt::show(); + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/modern.cpp b/examples/modern.cpp index 871ef2b..27011d8 100644 --- a/examples/modern.cpp +++ b/examples/modern.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o modern $(python-config --includes) modern.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include #include "../matplotlibcpp.h" @@ -5,29 +9,32 @@ using namespace std; namespace plt = matplotlibcpp; -int main() +int main() { - // plot(y) - the x-coordinates are implicitly set to [0,1,...,n) - //plt::plot({1,2,3,4}); - - // Prepare data for parametric plot. - int n = 5000; // number of data points - vector x(n),y(n); - for(int i=0; i x(n),y(n); + for(int i=0; i #include "../matplotlibcpp.h" @@ -43,4 +47,7 @@ int main() cout << "matplotlibcpp::show() is working in an non-blocking mode" << endl; getchar(); + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/quiver.cpp b/examples/quiver.cpp index ea3c3ec..2a8ef96 100644 --- a/examples/quiver.cpp +++ b/examples/quiver.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o quiver $(python-config --includes) quiver.cpp $(python-config --ldflags --embed) +// + #include "../matplotlibcpp.h" namespace plt = matplotlibcpp; @@ -17,4 +21,7 @@ int main() plt::quiver(x, y, u, v); plt::show(); -} \ No newline at end of file + + plt::detail::_interpreter::kill(); + return 0; +} diff --git a/examples/spy.cpp b/examples/spy.cpp index 6027a48..286957f 100644 --- a/examples/spy.cpp +++ b/examples/spy.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o spy $(python-config --includes) spy.cpp $(python-config --ldflags --embed) +// + #include "../matplotlibcpp.h" #include @@ -26,5 +30,6 @@ int main() plt::spy(matrix, 5, {{"marker", "o"}}); plt::show(); + plt::detail::_interpreter::kill(); return 0; } diff --git a/examples/subplot2grid.cpp b/examples/subplot2grid.cpp index f590e51..1869852 100644 --- a/examples/subplot2grid.cpp +++ b/examples/subplot2grid.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o subplot2grid $(python-config --includes) subplot2grid.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include #include "../matplotlibcpp.h" @@ -40,5 +44,8 @@ int main() // Show plots - plt::show(); + plt::show(); + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/surface.cpp b/examples/surface.cpp index 4865f06..beec66a 100644 --- a/examples/surface.cpp +++ b/examples/surface.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o surface $(python-config --includes) surface.cpp $(python-config --ldflags --embed) +// + #include "../matplotlibcpp.h" #include @@ -21,4 +25,7 @@ int main() plt::plot_surface(x, y, z); plt::show(); + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/update.cpp b/examples/update.cpp index 64f4906..5a34c1c 100644 --- a/examples/update.cpp +++ b/examples/update.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o update $(python-config --includes) update.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include #include "../matplotlibcpp.h" @@ -57,4 +61,7 @@ int main() plt::pause(0.1); } } + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/xkcd.cpp b/examples/xkcd.cpp index fa41cfc..38e48d2 100644 --- a/examples/xkcd.cpp +++ b/examples/xkcd.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o xkcd $(python-config --includes) xkcd.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include #include "../matplotlibcpp.h" @@ -18,5 +22,8 @@ int main() { plt::plot(t, x); plt::title("AN ORDINARY SIN WAVE"); plt::show(); + + plt::detail::_interpreter::kill(); + return 0; } From 13ed23f01e5cdbccb939d7b2366e048d1e53b324 Mon Sep 17 00:00:00 2001 From: Amadeus Date: Sat, 12 Mar 2022 11:02:55 -0500 Subject: [PATCH 02/15] matplotlibcpp.h: use decltype() in for loop, to avoid annoying type mismatch warning --- matplotlibcpp.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index d95d46a..012a095 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1058,7 +1058,7 @@ template return res; } - + template bool scatter(const std::vector& x, @@ -1069,9 +1069,9 @@ bool scatter(const std::vector& x, const long fig_number=0) { detail::_interpreter::get(); - // Same as with plot_surface: We lazily load the modules here the first time - // this function is called because I'm not sure that we can assume "matplotlib - // installed" implies "mpl_toolkits installed" on all platforms, and we don't + // Same as with plot_surface: We lazily load the modules here the first time + // this function is called because I'm not sure that we can assume "matplotlib + // installed" implies "mpl_toolkits installed" on all platforms, and we don't // want to require it for people who don't need 3d plots. static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; if (!mpl_toolkitsmod) { @@ -1468,7 +1468,7 @@ bool quiver(const std::vector& x, const std::vector& y, cons Py_DECREF(axis3d); if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } } - + //assert sizes match up assert(x.size() == y.size() && x.size() == u.size() && u.size() == w.size() && x.size() == z.size() && x.size() == v.size() && u.size() == v.size()); @@ -1496,7 +1496,7 @@ bool quiver(const std::vector& x, const std::vector& y, cons { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); } - + //get figure gca to enable 3d projection PyObject *fig = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, @@ -1516,7 +1516,7 @@ bool quiver(const std::vector& x, const std::vector& y, cons Py_INCREF(axis); Py_DECREF(gca); Py_DECREF(gca_kwargs); - + //plot our boys bravely, plot them strongly, plot them with a wink and clap PyObject *plot3 = PyObject_GetAttrString(axis, "quiver"); if (!plot3) throw std::runtime_error("No 3D line plot"); @@ -2655,7 +2655,7 @@ inline void rcparams(const std::map& keywords = {}) { PyDict_SetItemString(kwargs, it->first.c_str(), PyLong_FromLong(std::stoi(it->second.c_str()))); else PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); } - + PyObject * update = PyObject_GetAttrString(detail::_interpreter::get().s_python_function_rcparams, "update"); PyObject * res = PyObject_Call(update, args, kwargs); if(!res) throw std::runtime_error("Call to rcParams.update() failed."); @@ -2818,7 +2818,7 @@ struct plot_impl PyObject* pystring = PyString_FromString(format.c_str()); auto itx = begin(x), ity = begin(y); - for(size_t i = 0; i < xs; ++i) { + for(decltype(xs) i = 0; i < xs; ++i) { PyList_SetItem(xlist, i, PyFloat_FromDouble(*itx++)); PyList_SetItem(ylist, i, PyFloat_FromDouble(*ity++)); } From 62558776fe35eac6591848f80080d53827609b05 Mon Sep 17 00:00:00 2001 From: Amadeus Date: Sat, 12 Mar 2022 11:00:26 -0500 Subject: [PATCH 03/15] examples: use std::span (C++20) to plot 2D C-style arrays --- CMakeLists.txt | 6 +++++- examples/span.cpp | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 examples/span.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bb2decd..953cf28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,6 @@ include(GNUInstallDirs) set(PACKAGE_NAME matplotlib_cpp) set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/${PACKAGE_NAME}/cmake) - # Library target add_library(matplotlib_cpp INTERFACE) target_include_directories(matplotlib_cpp @@ -100,6 +99,11 @@ if(Python3_NumPy_FOUND) add_executable(spy examples/spy.cpp) target_link_libraries(spy PRIVATE matplotlib_cpp) set_target_properties(spy PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + + add_executable(span examples/span.cpp) + target_link_libraries(span PRIVATE matplotlib_cpp) + target_compile_features(span PRIVATE cxx_std_20) + set_target_properties(span PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") endif() diff --git a/examples/span.cpp b/examples/span.cpp new file mode 100644 index 0000000..26ca0cd --- /dev/null +++ b/examples/span.cpp @@ -0,0 +1,34 @@ +// +// g++ -std=c++20 -g -Wall -o span $(python-config --includes) span.cpp $(python-config --ldflags --embed) +// +#include "../matplotlibcpp.h" + +#include + +using namespace std; +namespace plt = matplotlibcpp; + +int main() +{ + // C-style arrays with multiple rows + time_t t[]={1, 2, 3, 4}; + int data[]={ + 3, 1, 4, 5, + 5, 4, 1, 3, + 3, 3, 3, 3 + }; + + size_t n=sizeof(t)/sizeof(decltype(t[0])); + size_t m=sizeof(data)/sizeof(decltype(data[0])); + + // Use std::span to pass data chunk to plot(), without copying it. + // Unfortunately, plt::plot() makes an internal copy of both x and y + // before passing them to python. + for (size_t offset=0; offset Date: Mon, 14 Mar 2022 09:31:45 -0400 Subject: [PATCH 04/15] matplotlib.h: add support for contiguous_range (C++20) and ranges.cpp example --- CMakeLists.txt | 6 + examples/ranges.cpp | 105 +++++++++++++++ matplotlibcpp.h | 304 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 362 insertions(+), 53 deletions(-) create mode 100644 examples/ranges.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 953cf28..0a33f2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,12 @@ if(Python3_NumPy_FOUND) target_link_libraries(span PRIVATE matplotlib_cpp) target_compile_features(span PRIVATE cxx_std_20) set_target_properties(span PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + + add_executable(ranges examples/ranges.cpp) + target_link_libraries(ranges PRIVATE matplotlib_cpp) + target_compile_features(ranges PRIVATE cxx_std_20) + set_target_properties(ranges PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + endif() diff --git a/examples/ranges.cpp b/examples/ranges.cpp new file mode 100644 index 0000000..ded45fc --- /dev/null +++ b/examples/ranges.cpp @@ -0,0 +1,105 @@ +// +// g++ -std=c++20 -g -Wall -o ranges $(python-config --includes) ranges.cpp $(python-config --ldflags --embed) +// +// g++ -std=c++17 -g -Wall -o ranges $(python-config --includes) ranges.cpp $(python-config --ldflags --embed) +// +#include "../matplotlibcpp.h" + +#include +#include +#include +#include +#include +#include + +#define UNUSED(x) (void)(x) + +using namespace std; +namespace plt = matplotlibcpp; + +int main() +{ + plt::detail::_interpreter::get(); + + +#if __cplusplus >= CPP20 + + // C-style arrays with multiple rows. + + // Care with column-major vs row-major! + // C and Python are row-major, but usually a time series is column-major + // and we want to plot the columns. + // In the example below, these columns are [3,1,4,5] and [5,4,1,3], so + // the data must be stored like this: + time_t t[]={1, 2, 3, 4}; + double data [] = { + 3, 5, + 1, 4, + 4, 1, + 5, 3 + }; + + // Use std::span() to convert to a contiguous range (O(1)). + // Data won't be copied, but passed as a pointer to Python. + plt::plot(span(t, 4), span(data, 8)); + plt::grid(true); + plt::title("C-arrays, with multiple columns"); + plt::show(); + +#else + + cerr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << ": " + << "No support for contiguous ranges." << endl; + +#endif + + // vectors + + // Vectors are also contiguous ranges. + // In C++20, as with span, plot resolves to plot(contiguous_range). + // In C++ < 20 plot resolves to plot(vector). + vector x={1, 2, 3, 4, 5}; + vector y={0, 1, 0, -1, 0}; + plt::plot(x, y); + plt::grid(true); + plt::title("vectors"); + plt::show(); + + + // lists + + // By contrast, lists are non-contiguous (but iterable) containers, so + // plot resolves to plot(iterable). + list u { 1, 2, 3, 4, 5}; + list v { 0, -1, 0, 1, 0}; + plt::plot(u, v, ""); + plt::grid(true); + plt::title("Lists (non-contiguous ranges)"); + plt::show(); + + + // All together now +#if __cplusplus >= CPP20 + + // If span is not last, plot resolves to plot(iterable), which copies data. + // That's because in the dispatch plot() we have plot_impl() && plot() + // (i.e. plot_impl() comes first), and we only have iterable and + // callable plot_impl(). That sends us to the iterable plot_impl(), + // rather than to plot(contiguous_range). + // + // TODO: have 3 tags plot_impl(): iterable, callable and contiguous range. + plt::plot(span(t, 4), span(data, 8), "", x, y, "b", u, v, "r"); + + // This resolves to plot(contiguous_range) and does not copy data. + // plt::plot(x, y, "b", u, v, "r", span(t, 4), span(data, 8)); +#else + plt::plot(x, y, "b", u, v, "r"); +#endif + plt::grid(true); + plt::title("Variadic templates recursion"); + plt::show(); + + plt::detail::_interpreter::kill(); + + return 0; +} diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 012a095..d419b43 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -14,6 +14,8 @@ #include // requires c++11 support #include #include // std::stod +#include +#include #ifndef WITHOUT_NUMPY # define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION @@ -39,6 +41,7 @@ # define PyString_FromString PyUnicode_FromString #endif +#define CPP20 202002L namespace matplotlibcpp { namespace detail { @@ -438,10 +441,124 @@ PyObject* get_listlist(const std::vector>& ll) /// Plot a line through the given x and y data points.. /// /// See: https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.pyplot.plot.html + +// If a container (range) has ::data() that converts to ::value and it has +// ::size() that is convertible to pointer differences, then it's contiguous, +// in which case we need not copy the data. +// https://stackoverflow.com/questions/42851957/contiguous-iterator-detection +// +// TODO: get rid of the plot(vector) specializations, they unnecessarily +// copy the data. +// TODO: provide contiguous and non-contiguous implementations for ranges, +// using iterator_tag. +// https://stackoverflow.com/questions/4688177/how-does-iterator-category-in-c-work +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1474r0.pdf +// + +#if __cplusplus >= CPP20 + +// Support for contiguous ranges, e.g. vector, span, etc. In this case +// we can pass the data pointer directly to python, without copying. +// +// Non-contiguous (but iterable) arrays (e.g. lists), call a different plot, +// one which has to copy the items into a contiguous C-style array before +// passing them to python. +template +bool plot(const ContainerX& x, const ContainerY& y, const std::string& fmt="") +{ + assert(y.size()%x.size() == 0 && "length of y must be a multiple of length of x!"); + + detail::_interpreter::get(); + + NPY_TYPES xtype=detail::select_npy_type::type; + NPY_TYPES ytype=detail::select_npy_type::type; + + npy_intp xsize=x.size(); + npy_intp yrows=xsize, ycols=y.size()/x.size(); + npy_intp ysize[]={yrows, ycols}; // ysize[0] must equal xsize + PyObject* xarray = PyArray_SimpleNewFromData(1, &xsize, xtype, (void*)(x.data())); + PyObject* yarray = PyArray_SimpleNewFromData(2, ysize, ytype, (void*)(y.data())); + + PyObject* pystring = PyString_FromString(fmt.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; +} + +template +bool plot(const ContainerX& x, const ContainerY& y, + const std::map& keywords) +{ + assert(y.size()%x.size() == 0 && "length of y must be a multiple of length of x!"); + + detail::_interpreter::get(); + + NPY_TYPES xtype=detail::select_npy_type::type; + NPY_TYPES ytype=detail::select_npy_type::type; + + npy_intp xsize=x.size(); + npy_intp yrows=xsize, ycols=y.size()/x.size(); + npy_intp ysize[]={yrows, ycols}; // ysize[0] must equal xsize + PyObject* xarray = PyArray_SimpleNewFromData(1, &xsize, xtype, (void*)(x.data())); + PyObject* yarray = PyArray_SimpleNewFromData(2, ysize, ytype, (void*)(y.data())); + + // construct positional args + PyObject* args = PyTuple_New(2); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + { + PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + + return res; +} + +template +bool plot(const ContainerY& y, const std::string& format = "") +{ + std::vector x(y.size()); + std::iota(x.begin(), x.end(), 0); + return plot(x,y,format); +} + +template +bool plot(const ContainerY& y, const std::map& keywords) +{ + std::vector x(y.size()); + std::iota(x.begin(), x.end(), 0); + return plot(x,y,keywords); +} + +#else + +// For the less fortunate ones who can't or won't use C++ 20, we stick with +// vectors, since there's no ranges concept before that. + template bool plot(const std::vector &x, const std::vector &y, const std::map& keywords) { - assert(x.size() == y.size()); + assert(y.size()%x.size() == 0 && "length of y must be a multiple of length of x!"); detail::_interpreter::get(); @@ -470,6 +587,49 @@ bool plot(const std::vector &x, const std::vector &y, const st return res; } +template +bool plot(const std::vector& x, const std::vector& y, const std::string& s = "") +{ + assert(y.size()%x.size() == 0 && "length of y must be a multiple of length of x!"); + + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(s.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; +} + +template +bool plot(const std::vector& y, const std::string& format = "") +{ + std::vector x(y.size()); + for(size_t i=0; i +bool plot(const std::vector& y, const std::map& keywords) +{ + std::vector x(y.size()); + for(size_t i=0; i& y, long bins=10, s return res; } -template -bool plot(const std::vector& x, const std::vector& y, const std::string& s = "") -{ - assert(x.size() == y.size()); - - detail::_interpreter::get(); - - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); - - PyObject* pystring = PyString_FromString(s.c_str()); - - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); - - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); - - Py_DECREF(plot_args); - if(res) Py_DECREF(res); - - return res; -} - template bool contour(const std::vector& x, const std::vector& y, const std::vector& z, @@ -1806,22 +1941,6 @@ bool named_loglog(const std::string& name, const std::vector& x, const return res; } -template -bool plot(const std::vector& y, const std::string& format = "") -{ - std::vector x(y.size()); - for(size_t i=0; i -bool plot(const std::vector& y, const std::map& keywords) -{ - std::vector x(y.size()); - for(size_t i=0; i bool stem(const std::vector& y, const std::string& format = "") { @@ -2796,6 +2915,8 @@ struct is_callable template struct plot_impl { }; +#ifdef WITHOUT_NUMPY + template<> struct plot_impl { @@ -2811,8 +2932,10 @@ struct plot_impl auto xs = distance(begin(x), end(x)); auto ys = distance(begin(y), end(y)); - assert(xs == ys && "x and y data must have the same number of elements!"); + assert(xs == ys && "x and y must have the same number of elements!"); + + // No PyArray, must use lists, and they must have the same length. PyObject* xlist = PyList_New(xs); PyObject* ylist = PyList_New(ys); PyObject* pystring = PyString_FromString(format.c_str()); @@ -2821,11 +2944,77 @@ struct plot_impl for(decltype(xs) i = 0; i < xs; ++i) { PyList_SetItem(xlist, i, PyFloat_FromDouble(*itx++)); PyList_SetItem(ylist, i, PyFloat_FromDouble(*ity++)); - } + } + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; + } +}; + +#else + +template<> +struct plot_impl +{ + template + bool operator()(const IterableX& x, const IterableY& y, const std::string& format) + { + // pyassert(PyArray_API, "NumPy needed"); + + detail::_interpreter::get(); + + // 2-phase lookup for distance, begin, end + using std::distance; + using std::begin; + using std::end; + + auto xs = distance(begin(x), end(x)); + auto ys = distance(begin(y), end(y)); + + assert(ys%xs == 0 && "length of y must be a multiple of length of x!"); + + typedef typename IterableX::value_type NumberX; + typedef typename IterableY::value_type NumberY; + + NPY_TYPES xtype=detail::select_npy_type::type; + NPY_TYPES ytype=detail::select_npy_type::type; + + npy_intp xsize=xs; + npy_intp yrows=xsize, ycols=ys/yrows; + npy_intp ysize[]={yrows, ycols}; // ysize[0] must equal xsize + + PyObject* xarray = + PyArray_New(&PyArray_Type, + 1, &xsize, xtype, nullptr, nullptr, + 0, NPY_ARRAY_DEFAULT, nullptr); + PyObject* yarray = + PyArray_New(&PyArray_Type, + 2, ysize, ytype, nullptr, nullptr, + 0, NPY_ARRAY_DEFAULT, nullptr); + PyObject* pystring = PyString_FromString(format.c_str()); + + // fill the data + auto itx = begin(x), ity = begin(y); + NumberX* xdata = (NumberX*) PyArray_DATA((PyArrayObject*)xarray); + for(decltype(xs) i = 0; i < xs; ++i) + xdata[i]=*itx++; + + NumberY* ydata = (NumberY*) PyArray_DATA((PyArrayObject*)yarray); + for(decltype(ys) i = 0; i < ys; ++i) + ydata[i]=*ity++; PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xlist); - PyTuple_SetItem(plot_args, 1, ylist); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); PyTuple_SetItem(plot_args, 2, pystring); PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); @@ -2837,6 +3026,9 @@ struct plot_impl } }; +#endif + + template<> struct plot_impl { @@ -2869,16 +3061,22 @@ bool plot(const A& a, const B& b, const std::string& format, Args... args) * This group of plot() functions is needed to support initializer lists, i.e. calling * plot( {1,2,3,4} ) */ -inline bool plot(const std::vector& x, const std::vector& y, const std::string& format = "") { - return plot(x,y,format); +inline bool plot(const std::vector& x, const std::vector& y, const std::string& format = "") +{ +#if __cplusplus >= CPP20 + return plot, std::vector>(x, y, format); +#else + return plot(x, y, format); +#endif } -inline bool plot(const std::vector& y, const std::string& format = "") { +inline bool plot(const std::vector& y, const std::string& format = "") +{ +#if __cplusplus >= CPP20 + return plot>(y,format); +#else return plot(y,format); -} - -inline bool plot(const std::vector& x, const std::vector& y, const std::map& keywords) { - return plot(x,y,keywords); +#endif } /* From b0f57e5a937e8b5b3ea93c700347f01a5a3e4fbf Mon Sep 17 00:00:00 2001 From: Amadeus Date: Mon, 14 Mar 2022 12:39:59 -0400 Subject: [PATCH 05/15] specify column-major mode flag (NPY_ARRAY_FPARRAY) in the contiguous range functions, for multicolumn plot --- examples/ranges.cpp | 31 ++++++++++++++++-------- matplotlibcpp.h | 58 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/examples/ranges.cpp b/examples/ranges.cpp index ded45fc..0ef0714 100644 --- a/examples/ranges.cpp +++ b/examples/ranges.cpp @@ -21,23 +21,21 @@ int main() { plt::detail::_interpreter::get(); + // C-style arrays with multiple rows. #if __cplusplus >= CPP20 - // C-style arrays with multiple rows. + time_t t[]={1, 2, 3, 4}; // Care with column-major vs row-major! // C and Python are row-major, but usually a time series is column-major // and we want to plot the columns. // In the example below, these columns are [3,1,4,5] and [5,4,1,3], so // the data must be stored like this: - time_t t[]={1, 2, 3, 4}; double data [] = { - 3, 5, - 1, 4, - 4, 1, - 5, 3 - }; + 3, 1, 4, 5, + 5, 4, 1, 3 + }; // contiguous data, column major! // Use std::span() to convert to a contiguous range (O(1)). // Data won't be copied, but passed as a pointer to Python. @@ -49,7 +47,7 @@ int main() #else cerr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << ": " - << "No support for contiguous ranges." << endl; + << "No support for C-style arrays in C++ <= 17" << endl; #endif @@ -89,16 +87,29 @@ int main() // // TODO: have 3 tags plot_impl(): iterable, callable and contiguous range. plt::plot(span(t, 4), span(data, 8), "", x, y, "b", u, v, "r"); + plt::grid(true); + plt::title("Variadic templates recursion, span first (copy)"); + plt::show(); // This resolves to plot(contiguous_range) and does not copy data. - // plt::plot(x, y, "b", u, v, "r", span(t, 4), span(data, 8)); + plt::plot(x, y, "b", u, v, "r", span(t, 4), span(data, 8)); + plt::grid(true); + plt::title("Variadic templates recursion, span last (passthrough)"); + plt::show(); + #else + + // no C-arrays + cerr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << ": " + << "No support for C-style arrays in C++ <= 17" << endl; + plt::plot(x, y, "b", u, v, "r"); -#endif plt::grid(true); plt::title("Variadic templates recursion"); plt::show(); +#endif + plt::detail::_interpreter::kill(); return 0; diff --git a/matplotlibcpp.h b/matplotlibcpp.h index d419b43..8965c83 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -477,8 +477,46 @@ bool plot(const ContainerX& x, const ContainerY& y, const std::string& fmt="") npy_intp xsize=x.size(); npy_intp yrows=xsize, ycols=y.size()/x.size(); npy_intp ysize[]={yrows, ycols}; // ysize[0] must equal xsize - PyObject* xarray = PyArray_SimpleNewFromData(1, &xsize, xtype, (void*)(x.data())); - PyObject* yarray = PyArray_SimpleNewFromData(2, ysize, ytype, (void*)(y.data())); + + // We have 2 options to pass existing data buffers to PyObject: + // PyArray_SimpleFromData() - by default creates C-style (row major) array + // PyArray_New() - uses flags, so we can specify Fotran-style (col major) + // + // In python, + // a=np.array([[3,5], [1,4], [4,1], [5,3]]) + // is stored in row-major mode and has columns + // a[:,0]=[3,1,4,5] and a[:,1]=[5,4,1,3] + // Then, + // plt.plot(a) + // plots the columns of a. So in python the array is row-major, but + // plotted columnwise. + // + // For dataframes (and time series), however, it is more natural to assume + // that the data is stored contiguously in column-major mode, i.e. + // double a[] = [3, 1, 4, 5 + // 5, 4, 1, 3]; + // We let python know that is the case, by specifying the NPY_ARRAY_FARRAY + // flag. This rules out the usage of PyArray_SimpleFromData(). + // + // Of course, in the vector-only version of this plot() function all this + // is irrelevant, because that plot is 1-dimensional anyway. + // + // If there are real-world applications that need to assume that the + // C-data is in row-major mode, we should address that. + // + // TODO: + // To that end, perhaps we can introduce C++ tags cmajor_tag and rmajor_tag + // and define a custom concept from contiguous_range, by further + // conditioning on the storage tags. The caller then specifies the major + // storage mode when wrapping the C-style array into a range. + PyObject* xarray = + PyArray_New(&PyArray_Type, + 1, &xsize, xtype, nullptr, (void*) x.data(), + 0, NPY_ARRAY_FARRAY, nullptr); + PyObject* yarray = + PyArray_New(&PyArray_Type, + 2, ysize, ytype, nullptr, (void*) y.data(), + 0, NPY_ARRAY_FARRAY, nullptr); // column major by design! PyObject* pystring = PyString_FromString(fmt.c_str()); @@ -510,8 +548,16 @@ bool plot(const ContainerX& x, const ContainerY& y, npy_intp xsize=x.size(); npy_intp yrows=xsize, ycols=y.size()/x.size(); npy_intp ysize[]={yrows, ycols}; // ysize[0] must equal xsize - PyObject* xarray = PyArray_SimpleNewFromData(1, &xsize, xtype, (void*)(x.data())); - PyObject* yarray = PyArray_SimpleNewFromData(2, ysize, ytype, (void*)(y.data())); + + // Same comments as above. + PyObject* xarray = + PyArray_New(&PyArray_Type, + 1, &xsize, xtype, nullptr, (void*) x.data(), + 0, NPY_ARRAY_FARRAY, nullptr); + PyObject* yarray = + PyArray_New(&PyArray_Type, + 2, ysize, ytype, nullptr, (void*) y.data(), + 0, NPY_ARRAY_FARRAY, nullptr); // column major by design! // construct positional args PyObject* args = PyTuple_New(2); @@ -2995,11 +3041,11 @@ struct plot_impl PyObject* xarray = PyArray_New(&PyArray_Type, 1, &xsize, xtype, nullptr, nullptr, - 0, NPY_ARRAY_DEFAULT, nullptr); + 0, NPY_ARRAY_FARRAY, nullptr); PyObject* yarray = PyArray_New(&PyArray_Type, 2, ysize, ytype, nullptr, nullptr, - 0, NPY_ARRAY_DEFAULT, nullptr); + 0, NPY_ARRAY_FARRAY, nullptr); // column major! PyObject* pystring = PyString_FromString(format.c_str()); // fill the data From a08b67a7b8bd00a6aafbe941ceba0e23ff437f42 Mon Sep 17 00:00:00 2001 From: Amadeus Date: Mon, 14 Mar 2022 23:14:16 -0400 Subject: [PATCH 06/15] examples: update span.cpp after introducing support for contiguous ranges --- examples/span.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/examples/span.cpp b/examples/span.cpp index 26ca0cd..a54b4b1 100644 --- a/examples/span.cpp +++ b/examples/span.cpp @@ -18,14 +18,7 @@ int main() 3, 3, 3, 3 }; - size_t n=sizeof(t)/sizeof(decltype(t[0])); - size_t m=sizeof(data)/sizeof(decltype(data[0])); - - // Use std::span to pass data chunk to plot(), without copying it. - // Unfortunately, plt::plot() makes an internal copy of both x and y - // before passing them to python. - for (size_t offset=0; offset Date: Thu, 24 Mar 2022 12:42:07 -0400 Subject: [PATCH 07/15] timeseries plot functionality --- CMakeLists.txt | 5 ++ datetime_utils.h | 137 ++++++++++++++++++++++++++++++++++++++++ examples/timeseries.cpp | 97 ++++++++++++++++++++++++++++ matplotlibcpp.h | 31 ++++++++- 4 files changed, 267 insertions(+), 3 deletions(-) create mode 100644 datetime_utils.h create mode 100644 examples/timeseries.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a33f2d..21e0dab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,6 +110,11 @@ if(Python3_NumPy_FOUND) target_compile_features(ranges PRIVATE cxx_std_20) set_target_properties(ranges PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + add_executable(timeseries examples/timeseries.cpp) + target_link_libraries(timeseries PRIVATE matplotlib_cpp) + target_compile_features(timeseries PRIVATE cxx_std_20) + set_target_properties(timeseries PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + endif() diff --git a/datetime_utils.h b/datetime_utils.h new file mode 100644 index 0000000..99a81dc --- /dev/null +++ b/datetime_utils.h @@ -0,0 +1,137 @@ +#pragma once + +#include "matplotlibcpp.h" + +#include +#include + +#include +#include +#include +#include + +// Convenience functions for converting C/C++ time objects to datetime.datetime +// objects. These are outside the matplotlibcpp namespace because they do not +// exist in matplotlib.pyplot. +template +PyObject* toPyDateTime(const TimePoint& t, int dummy=0) +{ + using namespace std::chrono; + auto tsec=time_point_cast(t); + auto us=duration_cast(t-tsec); + + time_t tt=system_clock::to_time_t(t); + PyObject* obj=toPyDateTime(tt, us.count()); + + return obj; +} + +template <> +PyObject* toPyDateTime(const time_t& t, int us) +{ + tm tm {}; + gmtime_r(&t,&tm); // compatible with matlab, inverse of datenum. + + if (!PyDateTimeAPI) { + PyDateTime_IMPORT; + } + + PyObject* obj=PyDateTime_FromDateAndTime(tm.tm_year+1900, + tm.tm_mon+1, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + us); + if (obj) { + PyDateTime_Check(obj); + Py_INCREF(obj); + } + + return obj; +} + +template +PyObject* toPyDateTimeList(const Time_t* t, size_t nt) +{ + PyObject* tlist=PyList_New(nt); + if (tlist==nullptr) + return nullptr; + + // Py_INCREF(tlist); + + if (!PyDateTimeAPI) { + PyDateTime_IMPORT; + } + + for (size_t i=0; i +class DateTimeList +{ +public: + + DateTimeList(const Time_t* t, size_t nt) { + tlist=(PyListObject*) toPyDateTimeList(t, nt); + } + + ~DateTimeList() { if (tlist) Py_DECREF((PyObject*) tlist); } + + PyListObject* get() const { return tlist; } + size_t size() const { return tlist ? PyList_Size((PyObject*)tlist) : 0; } + +private: + mutable PyListObject* tlist=nullptr; +}; + + + +namespace matplotlibcpp { + +// special purpose function to plot against python datetime objects. +template +bool plot(const DateTimeList& t, const ContainerY& y, + const std::string& fmt="") +{ + detail::_interpreter::get(); + + // DECREF decrements the ref counts of all objects in the plot_args tuple, + // In particular, it decreasesthe ref count of the time array x. + // We want to maintain that unchanged though, so we can reuse it. + PyListObject* tarray=t.get(); + Py_INCREF(tarray); + + NPY_TYPES ytype=detail::select_npy_type::type; + + npy_intp tsize=PyList_Size((PyObject*)tarray); + assert(y.size()%tsize == 0 && "length of y must be a multiple of length of x!"); + + npy_intp yrows=tsize, ycols=y.size()/yrows; + npy_intp ysize[]={yrows, ycols}; // ysize[0] must equal tsize + + PyObject* yarray = PyArray_New(&PyArray_Type, + 2, ysize, ytype, nullptr, (void*) y.data(), + 0, NPY_ARRAY_FARRAY, nullptr); // col major + + PyObject* pystring = PyString_FromString(fmt.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, (PyObject*)tarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return true; +} + +} diff --git a/examples/timeseries.cpp b/examples/timeseries.cpp new file mode 100644 index 0000000..d6e018e --- /dev/null +++ b/examples/timeseries.cpp @@ -0,0 +1,97 @@ +// +// g++ -std=c++20 -g -Wall -o timeseries $(python-config --includes) timeseries.cpp $(python-config --ldflags --embed) +// g++ -std=c++20 -g -Wall -fsanitize=address -o timeseries $(python-config --includes) timeseries.cpp $(python-config --ldflags --embed) -lasan +// +#include "../matplotlibcpp.h" +#include "../datetime_utils.h" + +#include +#include +#include +#include +#include + +using namespace std; +namespace plt = matplotlibcpp; + +// In python2.7 there was a dedicated function PyString_AsString, but no more. +string tostring(PyObject* obj) +{ + string out; + PyObject* repr=PyObject_Repr(obj); // unicode object + if (repr) { + PyObject* str=PyUnicode_AsEncodedString(repr, 0, 0); + if (str) { + const char* bytes=PyBytes_AS_STRING(str); + out=bytes; + Py_DECREF(str); + } + + Py_DECREF(repr); + } + + return out; +} + +int main() +{ + using namespace std::chrono; + using clk=std::chrono::high_resolution_clock; + + plt::detail::_interpreter::get(); // initialize everything + + // Test toPyDateTime functions. + PyObject* now=toPyDateTime(time(0)); + cout << tostring(now) << endl; + Py_DECREF(now); + + now=toPyDateTime(clk::now()); + cout << tostring(now) << endl; + Py_DECREF(now); + + + // Time series plot + + // We have a time array (e.g. from a csv file): + size_t n=10; + vector tvec; + time_t tstart=time(0); + for (size_t i=0; i tarray(tvec.data(), tvec.size()); + + // Create 2-column data and plot it against tarray: + vector data(2*n); + for (size_t i=0; i<2*n; i++) data[i]=2.0*i+5; + + plt::plot(tarray, data); + plt::xticks({{"rotation", "20"}}); + plt::title("Linear"); + plt::grid(true); + plt::show(); + + // Modify some data and plot again, reusing the time array: + for (size_t i=0; i(); + plt::detail::_interpreter::kill(); + + return 0; +} diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 8965c83..4ce69fb 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -3,6 +3,7 @@ // Python headers must be included before any system headers, since // they define _POSIX_C_SOURCE #include +#include #include #include @@ -11,11 +12,10 @@ #include #include #include -#include // requires c++11 support #include -#include // std::stod -#include #include +#include +#include #ifndef WITHOUT_NUMPY # define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION @@ -596,6 +596,7 @@ bool plot(const ContainerY& y, const std::map& keyword return plot(x,y,keywords); } + #else // For the less fortunate ones who can't or won't use C++ 20, we stick with @@ -2307,6 +2308,27 @@ inline void xticks(const std::vector &ticks, const std::map& keywords) +{ + detail::_interpreter::get(); + + PyObject* args = PyTuple_New(0); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for (const auto& [k, v] : keywords) + PyDict_SetItemString(kwargs, k.c_str(), PyString_FromString(v.c_str())); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_xticks, args, kwargs); + + Py_DECREF(kwargs); + if(!res) throw std::runtime_error("Call to xticks() failed"); + + Py_DECREF(res); +} + + template inline void yticks(const std::vector &ticks, const std::vector &labels = {}, const std::map& keywords = {}) { @@ -3227,4 +3249,7 @@ class Plot PyObject* set_data_fct = nullptr; }; + } // end namespace matplotlibcpp + + From 153b3aa53abf890952a74e097c0c655121f2f404 Mon Sep 17 00:00:00 2001 From: Amadeus Date: Sat, 26 Mar 2022 14:27:26 -0400 Subject: [PATCH 08/15] datetime_utils.h: include --- datetime_utils.h | 1 + 1 file changed, 1 insertion(+) diff --git a/datetime_utils.h b/datetime_utils.h index 99a81dc..a5dc410 100644 --- a/datetime_utils.h +++ b/datetime_utils.h @@ -9,6 +9,7 @@ #include #include #include +#include // Convenience functions for converting C/C++ time objects to datetime.datetime // objects. These are outside the matplotlibcpp namespace because they do not From 231138afd7170bb19d5325084dcc2c7e286100c8 Mon Sep 17 00:00:00 2001 From: Amadeus Date: Sat, 26 Mar 2022 16:25:04 -0400 Subject: [PATCH 09/15] style: from now on use Stroupstrup clang format from https://github.com/SoultatosStefanos/strstyle.git * the .clang-format file is committed here in the local directory * clang-format --style=file -i matplotlibcpp.h --- .clang-format | 83 + datetime_utils.h | 149 +- matplotlibcpp.h | 5580 ++++++++++++++++++++++++---------------------- 3 files changed, 3105 insertions(+), 2707 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..6ff7d38 --- /dev/null +++ b/.clang-format @@ -0,0 +1,83 @@ +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignConsecutiveMacros: AcrossEmptyLinesAndComments +AlignEscapedNewlines: Right +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: No +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterFunction: true + AfterControlStatement: false + SplitEmptyFunction: false + AfterEnum: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: true + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: All +BreakBeforeConceptDeclarations: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 80 +CompactNamespaces: false +Cpp11BracedListStyle: true +DerivePointerAlignment: false +EmptyLineBeforeAccessModifier: Never +FixNamespaceComments: true +IncludeBlocks: Merge +IndentCaseLabels: false +IndentExternBlock: Indent +IndentRequires: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +NamespaceIndentation: All +PointerAlignment: Left +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: false +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: Never +SpaceBeforeRangeBasedForLoopColon: false +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +UseTab: Never diff --git a/datetime_utils.h b/datetime_utils.h index a5dc410..7b7b54f 100644 --- a/datetime_utils.h +++ b/datetime_utils.h @@ -1,138 +1,127 @@ #pragma once #include "matplotlibcpp.h" - #include -#include - -#include +#include #include +#include #include #include -#include +#include // Convenience functions for converting C/C++ time objects to datetime.datetime // objects. These are outside the matplotlibcpp namespace because they do not // exist in matplotlib.pyplot. template -PyObject* toPyDateTime(const TimePoint& t, int dummy=0) +PyObject* toPyDateTime(const TimePoint& t, int dummy = 0) { using namespace std::chrono; - auto tsec=time_point_cast(t); - auto us=duration_cast(t-tsec); + auto tsec = time_point_cast(t); + auto us = duration_cast(t - tsec); - time_t tt=system_clock::to_time_t(t); - PyObject* obj=toPyDateTime(tt, us.count()); + time_t tt = system_clock::to_time_t(t); + PyObject* obj = toPyDateTime(tt, us.count()); return obj; } -template <> -PyObject* toPyDateTime(const time_t& t, int us) +template<> PyObject* toPyDateTime(const time_t& t, int us) { - tm tm {}; - gmtime_r(&t,&tm); // compatible with matlab, inverse of datenum. + tm tm{}; + gmtime_r(&t, &tm); // compatible with matlab, inverse of datenum. - if (!PyDateTimeAPI) { - PyDateTime_IMPORT; - } + if(!PyDateTimeAPI) { PyDateTime_IMPORT; } - PyObject* obj=PyDateTime_FromDateAndTime(tm.tm_year+1900, - tm.tm_mon+1, - tm.tm_mday, - tm.tm_hour, - tm.tm_min, - tm.tm_sec, - us); - if (obj) { - PyDateTime_Check(obj); - Py_INCREF(obj); + PyObject* obj = PyDateTime_FromDateAndTime(tm.tm_year + 1900, tm.tm_mon + 1, + tm.tm_mday, tm.tm_hour, + tm.tm_min, tm.tm_sec, us); + if(obj) { + PyDateTime_Check(obj); + Py_INCREF(obj); } return obj; } -template -PyObject* toPyDateTimeList(const Time_t* t, size_t nt) +template PyObject* toPyDateTimeList(const Time_t* t, size_t nt) { - PyObject* tlist=PyList_New(nt); - if (tlist==nullptr) - return nullptr; + PyObject* tlist = PyList_New(nt); + if(tlist == nullptr) return nullptr; // Py_INCREF(tlist); - if (!PyDateTimeAPI) { - PyDateTime_IMPORT; - } + if(!PyDateTimeAPI) { PyDateTime_IMPORT; } - for (size_t i=0; i -class DateTimeList -{ +template class DateTimeList { public: - - DateTimeList(const Time_t* t, size_t nt) { - tlist=(PyListObject*) toPyDateTimeList(t, nt); + DateTimeList(const Time_t* t, size_t nt) + { + tlist = (PyListObject*)toPyDateTimeList(t, nt); } - ~DateTimeList() { if (tlist) Py_DECREF((PyObject*) tlist); } + ~DateTimeList() + { + if(tlist) Py_DECREF((PyObject*)tlist); + } PyListObject* get() const { return tlist; } size_t size() const { return tlist ? PyList_Size((PyObject*)tlist) : 0; } - private: - mutable PyListObject* tlist=nullptr; + mutable PyListObject* tlist = nullptr; }; - - -namespace matplotlibcpp { - -// special purpose function to plot against python datetime objects. -template -bool plot(const DateTimeList& t, const ContainerY& y, - const std::string& fmt="") +namespace matplotlibcpp { - detail::_interpreter::get(); - // DECREF decrements the ref counts of all objects in the plot_args tuple, - // In particular, it decreasesthe ref count of the time array x. - // We want to maintain that unchanged though, so we can reuse it. - PyListObject* tarray=t.get(); - Py_INCREF(tarray); + // special purpose function to plot against python datetime objects. + template + bool plot(const DateTimeList& t, const ContainerY& y, + const std::string& fmt = "") + { + detail::_interpreter::get(); - NPY_TYPES ytype=detail::select_npy_type::type; + // DECREF decrements the ref counts of all objects in the plot_args + // tuple, In particular, it decreasesthe ref count of the time array x. + // We want to maintain that unchanged though, so we can reuse it. + PyListObject* tarray = t.get(); + Py_INCREF(tarray); - npy_intp tsize=PyList_Size((PyObject*)tarray); - assert(y.size()%tsize == 0 && "length of y must be a multiple of length of x!"); + NPY_TYPES ytype + = detail::select_npy_type::type; - npy_intp yrows=tsize, ycols=y.size()/yrows; - npy_intp ysize[]={yrows, ycols}; // ysize[0] must equal tsize + npy_intp tsize = PyList_Size((PyObject*)tarray); + assert(y.size() % tsize == 0 + && "length of y must be a multiple of length of x!"); - PyObject* yarray = PyArray_New(&PyArray_Type, - 2, ysize, ytype, nullptr, (void*) y.data(), - 0, NPY_ARRAY_FARRAY, nullptr); // col major + npy_intp yrows = tsize, ycols = y.size() / yrows; + npy_intp ysize[] = {yrows, ycols}; // ysize[0] must equal tsize - PyObject* pystring = PyString_FromString(fmt.c_str()); + PyObject* yarray = PyArray_New(&PyArray_Type, 2, ysize, ytype, nullptr, + (void*)y.data(), 0, NPY_ARRAY_FARRAY, + nullptr); // col major - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, (PyObject*)tarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + PyObject* pystring = PyString_FromString(fmt.c_str()); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, (PyObject*)tarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - Py_DECREF(plot_args); - if(res) Py_DECREF(res); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_plot, plot_args); - return true; -} + Py_DECREF(plot_args); + if(res) Py_DECREF(res); -} + return true; + } + +} // namespace matplotlibcpp diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 4ce69fb..c40247d 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -3,3253 +3,3579 @@ // Python headers must be included before any system headers, since // they define _POSIX_C_SOURCE #include +#include +#include +#include #include - -#include +#include +#include #include -#include #include -#include -#include -#include -#include #include -#include +#include #include +#include #ifndef WITHOUT_NUMPY -# define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION -# include +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION +#include -# ifdef WITH_OPENCV -# include -# endif // WITH_OPENCV +#ifdef WITH_OPENCV +#include +#endif // WITH_OPENCV /* * A bunch of constants were removed in OpenCV 4 in favour of enum classes, so * define the ones we need here. */ -# if CV_MAJOR_VERSION > 3 -# define CV_BGR2RGB cv::COLOR_BGR2RGB -# define CV_BGRA2RGBA cv::COLOR_BGRA2RGBA -# endif +#if CV_MAJOR_VERSION > 3 +#define CV_BGR2RGB cv::COLOR_BGR2RGB +#define CV_BGRA2RGBA cv::COLOR_BGRA2RGBA +#endif #endif // WITHOUT_NUMPY #if PY_MAJOR_VERSION >= 3 -# define PyString_FromString PyUnicode_FromString -# define PyInt_FromLong PyLong_FromLong -# define PyString_FromString PyUnicode_FromString +#define PyString_FromString PyUnicode_FromString +#define PyInt_FromLong PyLong_FromLong +#define PyString_FromString PyUnicode_FromString #endif #define CPP20 202002L namespace matplotlibcpp { -namespace detail { - -static std::string s_backend; - -struct _interpreter { - PyObject* s_python_function_arrow; - PyObject *s_python_function_show; - PyObject *s_python_function_close; - PyObject *s_python_function_draw; - PyObject *s_python_function_pause; - PyObject *s_python_function_save; - PyObject *s_python_function_figure; - PyObject *s_python_function_fignum_exists; - PyObject *s_python_function_plot; - PyObject *s_python_function_quiver; - PyObject* s_python_function_contour; - PyObject *s_python_function_semilogx; - PyObject *s_python_function_semilogy; - PyObject *s_python_function_loglog; - PyObject *s_python_function_fill; - PyObject *s_python_function_fill_between; - PyObject *s_python_function_hist; - PyObject *s_python_function_imshow; - PyObject *s_python_function_scatter; - PyObject *s_python_function_boxplot; - PyObject *s_python_function_subplot; - PyObject *s_python_function_subplot2grid; - PyObject *s_python_function_legend; - PyObject *s_python_function_xlim; - PyObject *s_python_function_ion; - PyObject *s_python_function_ginput; - PyObject *s_python_function_ylim; - PyObject *s_python_function_title; - PyObject *s_python_function_axis; - PyObject *s_python_function_axhline; - PyObject *s_python_function_axvline; - PyObject *s_python_function_axvspan; - PyObject *s_python_function_xlabel; - PyObject *s_python_function_ylabel; - PyObject *s_python_function_gca; - PyObject *s_python_function_xticks; - PyObject *s_python_function_yticks; - PyObject* s_python_function_margins; - PyObject *s_python_function_tick_params; - PyObject *s_python_function_grid; - PyObject* s_python_function_cla; - PyObject *s_python_function_clf; - PyObject *s_python_function_errorbar; - PyObject *s_python_function_annotate; - PyObject *s_python_function_tight_layout; - PyObject *s_python_colormap; - PyObject *s_python_empty_tuple; - PyObject *s_python_function_stem; - PyObject *s_python_function_xkcd; - PyObject *s_python_function_text; - PyObject *s_python_function_suptitle; - PyObject *s_python_function_bar; - PyObject *s_python_function_barh; - PyObject *s_python_function_colorbar; - PyObject *s_python_function_subplots_adjust; - PyObject *s_python_function_rcparams; - PyObject *s_python_function_spy; - - /* For now, _interpreter is implemented as a singleton since its currently not possible to have - multiple independent embedded python interpreters without patching the python source code - or starting a separate process for each. [1] - Furthermore, many python objects expect that they are destructed in the same thread as they - were constructed. [2] So for advanced usage, a `kill()` function is provided so that library - users can manually ensure that the interpreter is constructed and destroyed within the - same thread. - - 1: http://bytes.com/topic/python/answers/793370-multiple-independent-python-interpreters-c-c-program - 2: https://github.com/lava/matplotlib-cpp/pull/202#issue-436220256 - */ - - static _interpreter& get() { - return interkeeper(false); - } - - static _interpreter& kill() { - return interkeeper(true); - } - - // Stores the actual singleton object referenced by `get()` and `kill()`. - static _interpreter& interkeeper(bool should_kill) { - static _interpreter ctx; - if (should_kill) - ctx.~_interpreter(); - return ctx; - } - - PyObject* safe_import(PyObject* module, std::string fname) { - PyObject* fn = PyObject_GetAttrString(module, fname.c_str()); - - if (!fn) - throw std::runtime_error(std::string("Couldn't find required function: ") + fname); - - if (!PyFunction_Check(fn)) - throw std::runtime_error(fname + std::string(" is unexpectedly not a PyFunction.")); - - return fn; - } - -private: - + namespace detail { + + static std::string s_backend; + + struct _interpreter { + PyObject* s_python_function_arrow; + PyObject* s_python_function_show; + PyObject* s_python_function_close; + PyObject* s_python_function_draw; + PyObject* s_python_function_pause; + PyObject* s_python_function_save; + PyObject* s_python_function_figure; + PyObject* s_python_function_fignum_exists; + PyObject* s_python_function_plot; + PyObject* s_python_function_quiver; + PyObject* s_python_function_contour; + PyObject* s_python_function_semilogx; + PyObject* s_python_function_semilogy; + PyObject* s_python_function_loglog; + PyObject* s_python_function_fill; + PyObject* s_python_function_fill_between; + PyObject* s_python_function_hist; + PyObject* s_python_function_imshow; + PyObject* s_python_function_scatter; + PyObject* s_python_function_boxplot; + PyObject* s_python_function_subplot; + PyObject* s_python_function_subplot2grid; + PyObject* s_python_function_legend; + PyObject* s_python_function_xlim; + PyObject* s_python_function_ion; + PyObject* s_python_function_ginput; + PyObject* s_python_function_ylim; + PyObject* s_python_function_title; + PyObject* s_python_function_axis; + PyObject* s_python_function_axhline; + PyObject* s_python_function_axvline; + PyObject* s_python_function_axvspan; + PyObject* s_python_function_xlabel; + PyObject* s_python_function_ylabel; + PyObject* s_python_function_gca; + PyObject* s_python_function_xticks; + PyObject* s_python_function_yticks; + PyObject* s_python_function_margins; + PyObject* s_python_function_tick_params; + PyObject* s_python_function_grid; + PyObject* s_python_function_cla; + PyObject* s_python_function_clf; + PyObject* s_python_function_errorbar; + PyObject* s_python_function_annotate; + PyObject* s_python_function_tight_layout; + PyObject* s_python_colormap; + PyObject* s_python_empty_tuple; + PyObject* s_python_function_stem; + PyObject* s_python_function_xkcd; + PyObject* s_python_function_text; + PyObject* s_python_function_suptitle; + PyObject* s_python_function_bar; + PyObject* s_python_function_barh; + PyObject* s_python_function_colorbar; + PyObject* s_python_function_subplots_adjust; + PyObject* s_python_function_rcparams; + PyObject* s_python_function_spy; + + /* For now, _interpreter is implemented as a singleton since its + currently not possible to have multiple independent embedded + python interpreters without patching the python source code or + starting a separate process for each. [1] Furthermore, many + python objects expect that they are destructed in the same thread + as they were constructed. [2] So for advanced usage, a `kill()` + function is provided so that library users can manually ensure + that the interpreter is constructed and destroyed within the same + thread. + + 1: + http://bytes.com/topic/python/answers/793370-multiple-independent-python-interpreters-c-c-program + 2: + https://github.com/lava/matplotlib-cpp/pull/202#issue-436220256 + */ + + static _interpreter& get() { return interkeeper(false); } + + static _interpreter& kill() { return interkeeper(true); } + + // Stores the actual singleton object referenced by `get()` and + // `kill()`. + static _interpreter& interkeeper(bool should_kill) + { + static _interpreter ctx; + if(should_kill) ctx.~_interpreter(); + return ctx; + } + + PyObject* safe_import(PyObject* module, std::string fname) + { + PyObject* fn = PyObject_GetAttrString(module, fname.c_str()); + + if(!fn) + throw std::runtime_error( + std::string("Couldn't find required function: ") + + fname); + + if(!PyFunction_Check(fn)) + throw std::runtime_error( + fname + + std::string(" is unexpectedly not a PyFunction.")); + + return fn; + } + private: #ifndef WITHOUT_NUMPY -# if PY_MAJOR_VERSION >= 3 +#if PY_MAJOR_VERSION >= 3 - void *import_numpy() { - import_array(); // initialize C-API - return NULL; - } + void* import_numpy() + { + import_array(); // initialize C-API + return NULL; + } -# else +#else - void import_numpy() { - import_array(); // initialize C-API - } + void import_numpy() + { + import_array(); // initialize C-API + } -# endif +#endif #endif - _interpreter() { - - // optional but recommended + _interpreter() + { + // optional but recommended #if PY_MAJOR_VERSION >= 3 - wchar_t name[] = L"plotting"; + wchar_t name[] = L"plotting"; #else - char name[] = "plotting"; + char name[] = "plotting"; #endif - Py_SetProgramName(name); - Py_Initialize(); + Py_SetProgramName(name); + Py_Initialize(); - wchar_t const *dummy_args[] = {L"Python", NULL}; // const is needed because literals must not be modified - wchar_t const **argv = dummy_args; - int argc = sizeof(dummy_args)/sizeof(dummy_args[0])-1; + wchar_t const* dummy_args[] + = {L"Python", NULL}; // const is needed because literals + // must not be modified + wchar_t const** argv = dummy_args; + int argc = sizeof(dummy_args) / sizeof(dummy_args[0]) - 1; #if PY_MAJOR_VERSION >= 3 - PySys_SetArgv(argc, const_cast(argv)); + PySys_SetArgv(argc, const_cast(argv)); #else - PySys_SetArgv(argc, (char **)(argv)); + PySys_SetArgv(argc, (char**)(argv)); #endif #ifndef WITHOUT_NUMPY - import_numpy(); // initialize numpy C-API + import_numpy(); // initialize numpy C-API #endif - PyObject* matplotlibname = PyString_FromString("matplotlib"); - PyObject* pyplotname = PyString_FromString("matplotlib.pyplot"); - PyObject* cmname = PyString_FromString("matplotlib.cm"); - PyObject* pylabname = PyString_FromString("pylab"); - if (!pyplotname || !pylabname || !matplotlibname || !cmname) { - throw std::runtime_error("couldnt create string"); - } - - PyObject* matplotlib = PyImport_Import(matplotlibname); - - Py_DECREF(matplotlibname); - if (!matplotlib) { - PyErr_Print(); - throw std::runtime_error("Error loading module matplotlib!"); - } - - // matplotlib.use() must be called *before* pylab, matplotlib.pyplot, - // or matplotlib.backends is imported for the first time - if (!s_backend.empty()) { - PyObject_CallMethod(matplotlib, const_cast("use"), const_cast("s"), s_backend.c_str()); - } - - - - PyObject* pymod = PyImport_Import(pyplotname); - Py_DECREF(pyplotname); - if (!pymod) { throw std::runtime_error("Error loading module matplotlib.pyplot!"); } - - s_python_colormap = PyImport_Import(cmname); - Py_DECREF(cmname); - if (!s_python_colormap) { throw std::runtime_error("Error loading module matplotlib.cm!"); } - - PyObject* pylabmod = PyImport_Import(pylabname); - Py_DECREF(pylabname); - if (!pylabmod) { throw std::runtime_error("Error loading module pylab!"); } - - s_python_function_arrow = safe_import(pymod, "arrow"); - s_python_function_show = safe_import(pymod, "show"); - s_python_function_close = safe_import(pymod, "close"); - s_python_function_draw = safe_import(pymod, "draw"); - s_python_function_pause = safe_import(pymod, "pause"); - s_python_function_figure = safe_import(pymod, "figure"); - s_python_function_fignum_exists = safe_import(pymod, "fignum_exists"); - s_python_function_plot = safe_import(pymod, "plot"); - s_python_function_quiver = safe_import(pymod, "quiver"); - s_python_function_contour = safe_import(pymod, "contour"); - s_python_function_semilogx = safe_import(pymod, "semilogx"); - s_python_function_semilogy = safe_import(pymod, "semilogy"); - s_python_function_loglog = safe_import(pymod, "loglog"); - s_python_function_fill = safe_import(pymod, "fill"); - s_python_function_fill_between = safe_import(pymod, "fill_between"); - s_python_function_hist = safe_import(pymod,"hist"); - s_python_function_scatter = safe_import(pymod,"scatter"); - s_python_function_boxplot = safe_import(pymod,"boxplot"); - s_python_function_subplot = safe_import(pymod, "subplot"); - s_python_function_subplot2grid = safe_import(pymod, "subplot2grid"); - s_python_function_legend = safe_import(pymod, "legend"); - s_python_function_xlim = safe_import(pymod, "xlim"); - s_python_function_ylim = safe_import(pymod, "ylim"); - s_python_function_title = safe_import(pymod, "title"); - s_python_function_axis = safe_import(pymod, "axis"); - s_python_function_axhline = safe_import(pymod, "axhline"); - s_python_function_axvline = safe_import(pymod, "axvline"); - s_python_function_axvspan = safe_import(pymod, "axvspan"); - s_python_function_xlabel = safe_import(pymod, "xlabel"); - s_python_function_ylabel = safe_import(pymod, "ylabel"); - s_python_function_gca = safe_import(pymod, "gca"); - s_python_function_xticks = safe_import(pymod, "xticks"); - s_python_function_yticks = safe_import(pymod, "yticks"); - s_python_function_margins = safe_import(pymod, "margins"); - s_python_function_tick_params = safe_import(pymod, "tick_params"); - s_python_function_grid = safe_import(pymod, "grid"); - s_python_function_ion = safe_import(pymod, "ion"); - s_python_function_ginput = safe_import(pymod, "ginput"); - s_python_function_save = safe_import(pylabmod, "savefig"); - s_python_function_annotate = safe_import(pymod,"annotate"); - s_python_function_cla = safe_import(pymod, "cla"); - s_python_function_clf = safe_import(pymod, "clf"); - s_python_function_errorbar = safe_import(pymod, "errorbar"); - s_python_function_tight_layout = safe_import(pymod, "tight_layout"); - s_python_function_stem = safe_import(pymod, "stem"); - s_python_function_xkcd = safe_import(pymod, "xkcd"); - s_python_function_text = safe_import(pymod, "text"); - s_python_function_suptitle = safe_import(pymod, "suptitle"); - s_python_function_bar = safe_import(pymod,"bar"); - s_python_function_barh = safe_import(pymod, "barh"); - s_python_function_colorbar = PyObject_GetAttrString(pymod, "colorbar"); - s_python_function_subplots_adjust = safe_import(pymod,"subplots_adjust"); - s_python_function_rcparams = PyObject_GetAttrString(pymod, "rcParams"); - s_python_function_spy = PyObject_GetAttrString(pymod, "spy"); + PyObject* matplotlibname = PyString_FromString("matplotlib"); + PyObject* pyplotname = PyString_FromString("matplotlib.pyplot"); + PyObject* cmname = PyString_FromString("matplotlib.cm"); + PyObject* pylabname = PyString_FromString("pylab"); + if(!pyplotname || !pylabname || !matplotlibname || !cmname) { + throw std::runtime_error("couldnt create string"); + } + + PyObject* matplotlib = PyImport_Import(matplotlibname); + + Py_DECREF(matplotlibname); + if(!matplotlib) { + PyErr_Print(); + throw std::runtime_error( + "Error loading module matplotlib!"); + } + + // matplotlib.use() must be called *before* pylab, + // matplotlib.pyplot, or matplotlib.backends is imported for the + // first time + if(!s_backend.empty()) { + PyObject_CallMethod(matplotlib, const_cast("use"), + const_cast("s"), + s_backend.c_str()); + } + + PyObject* pymod = PyImport_Import(pyplotname); + Py_DECREF(pyplotname); + if(!pymod) { + throw std::runtime_error( + "Error loading module matplotlib.pyplot!"); + } + + s_python_colormap = PyImport_Import(cmname); + Py_DECREF(cmname); + if(!s_python_colormap) { + throw std::runtime_error( + "Error loading module matplotlib.cm!"); + } + + PyObject* pylabmod = PyImport_Import(pylabname); + Py_DECREF(pylabname); + if(!pylabmod) { + throw std::runtime_error("Error loading module pylab!"); + } + + s_python_function_arrow = safe_import(pymod, "arrow"); + s_python_function_show = safe_import(pymod, "show"); + s_python_function_close = safe_import(pymod, "close"); + s_python_function_draw = safe_import(pymod, "draw"); + s_python_function_pause = safe_import(pymod, "pause"); + s_python_function_figure = safe_import(pymod, "figure"); + s_python_function_fignum_exists + = safe_import(pymod, "fignum_exists"); + s_python_function_plot = safe_import(pymod, "plot"); + s_python_function_quiver = safe_import(pymod, "quiver"); + s_python_function_contour = safe_import(pymod, "contour"); + s_python_function_semilogx = safe_import(pymod, "semilogx"); + s_python_function_semilogy = safe_import(pymod, "semilogy"); + s_python_function_loglog = safe_import(pymod, "loglog"); + s_python_function_fill = safe_import(pymod, "fill"); + s_python_function_fill_between + = safe_import(pymod, "fill_between"); + s_python_function_hist = safe_import(pymod, "hist"); + s_python_function_scatter = safe_import(pymod, "scatter"); + s_python_function_boxplot = safe_import(pymod, "boxplot"); + s_python_function_subplot = safe_import(pymod, "subplot"); + s_python_function_subplot2grid + = safe_import(pymod, "subplot2grid"); + s_python_function_legend = safe_import(pymod, "legend"); + s_python_function_xlim = safe_import(pymod, "xlim"); + s_python_function_ylim = safe_import(pymod, "ylim"); + s_python_function_title = safe_import(pymod, "title"); + s_python_function_axis = safe_import(pymod, "axis"); + s_python_function_axhline = safe_import(pymod, "axhline"); + s_python_function_axvline = safe_import(pymod, "axvline"); + s_python_function_axvspan = safe_import(pymod, "axvspan"); + s_python_function_xlabel = safe_import(pymod, "xlabel"); + s_python_function_ylabel = safe_import(pymod, "ylabel"); + s_python_function_gca = safe_import(pymod, "gca"); + s_python_function_xticks = safe_import(pymod, "xticks"); + s_python_function_yticks = safe_import(pymod, "yticks"); + s_python_function_margins = safe_import(pymod, "margins"); + s_python_function_tick_params + = safe_import(pymod, "tick_params"); + s_python_function_grid = safe_import(pymod, "grid"); + s_python_function_ion = safe_import(pymod, "ion"); + s_python_function_ginput = safe_import(pymod, "ginput"); + s_python_function_save = safe_import(pylabmod, "savefig"); + s_python_function_annotate = safe_import(pymod, "annotate"); + s_python_function_cla = safe_import(pymod, "cla"); + s_python_function_clf = safe_import(pymod, "clf"); + s_python_function_errorbar = safe_import(pymod, "errorbar"); + s_python_function_tight_layout + = safe_import(pymod, "tight_layout"); + s_python_function_stem = safe_import(pymod, "stem"); + s_python_function_xkcd = safe_import(pymod, "xkcd"); + s_python_function_text = safe_import(pymod, "text"); + s_python_function_suptitle = safe_import(pymod, "suptitle"); + s_python_function_bar = safe_import(pymod, "bar"); + s_python_function_barh = safe_import(pymod, "barh"); + s_python_function_colorbar + = PyObject_GetAttrString(pymod, "colorbar"); + s_python_function_subplots_adjust + = safe_import(pymod, "subplots_adjust"); + s_python_function_rcparams + = PyObject_GetAttrString(pymod, "rcParams"); + s_python_function_spy = PyObject_GetAttrString(pymod, "spy"); #ifndef WITHOUT_NUMPY - s_python_function_imshow = safe_import(pymod, "imshow"); + s_python_function_imshow = safe_import(pymod, "imshow"); #endif - s_python_empty_tuple = PyTuple_New(0); - } + s_python_empty_tuple = PyTuple_New(0); + } + + ~_interpreter() { Py_Finalize(); } + }; + + } // end namespace detail + + /// Select the backend + /// + /// **NOTE:** This must be called before the first plot command to have + /// any effect. + /// + /// Mainly useful to select the non-interactive 'Agg' backend when running + /// matplotlibcpp in headless mode, for example on a machine with no + /// display. + /// + /// See also: + /// https://matplotlib.org/2.0.2/api/matplotlib_configuration_api.html#matplotlib.use + inline void backend(const std::string& name) { detail::s_backend = name; } + + inline bool annotate(std::string annotation, double x, double y) + { + detail::_interpreter::get(); - ~_interpreter() { - Py_Finalize(); - } -}; + PyObject* xy = PyTuple_New(2); + PyObject* str = PyString_FromString(annotation.c_str()); + + PyTuple_SetItem(xy, 0, PyFloat_FromDouble(x)); + PyTuple_SetItem(xy, 1, PyFloat_FromDouble(y)); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "xy", xy); -} // end namespace detail + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, str); -/// Select the backend -/// -/// **NOTE:** This must be called before the first plot command to have -/// any effect. -/// -/// Mainly useful to select the non-interactive 'Agg' backend when running -/// matplotlibcpp in headless mode, for example on a machine with no display. -/// -/// See also: https://matplotlib.org/2.0.2/api/matplotlib_configuration_api.html#matplotlib.use -inline void backend(const std::string& name) -{ - detail::s_backend = name; -} + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_annotate, args, + kwargs); -inline bool annotate(std::string annotation, double x, double y) -{ - detail::_interpreter::get(); + Py_DECREF(args); + Py_DECREF(kwargs); - PyObject * xy = PyTuple_New(2); - PyObject * str = PyString_FromString(annotation.c_str()); + if(res) Py_DECREF(res); - PyTuple_SetItem(xy,0,PyFloat_FromDouble(x)); - PyTuple_SetItem(xy,1,PyFloat_FromDouble(y)); + return res; + } - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "xy", xy); + namespace detail { - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, str); +#ifndef WITHOUT_NUMPY + // Type selector for numpy array conversion + template struct select_npy_type { + const static NPY_TYPES type = NPY_NOTYPE; + }; // Default + template<> struct select_npy_type { + const static NPY_TYPES type = NPY_DOUBLE; + }; + template<> struct select_npy_type { + const static NPY_TYPES type = NPY_FLOAT; + }; + template<> struct select_npy_type { + const static NPY_TYPES type = NPY_BOOL; + }; + template<> struct select_npy_type { + const static NPY_TYPES type = NPY_INT8; + }; + template<> struct select_npy_type { + const static NPY_TYPES type = NPY_SHORT; + }; + template<> struct select_npy_type { + const static NPY_TYPES type = NPY_INT; + }; + template<> struct select_npy_type { + const static NPY_TYPES type = NPY_INT64; + }; + template<> struct select_npy_type { + const static NPY_TYPES type = NPY_UINT8; + }; + template<> struct select_npy_type { + const static NPY_TYPES type = NPY_USHORT; + }; + template<> struct select_npy_type { + const static NPY_TYPES type = NPY_ULONG; + }; + template<> struct select_npy_type { + const static NPY_TYPES type = NPY_UINT64; + }; + + // Sanity checks; comment them out or change the numpy type below if + // you're compiling on a platform where they don't apply + static_assert(sizeof(long long) == 8); + template<> struct select_npy_type { + const static NPY_TYPES type = NPY_INT64; + }; + static_assert(sizeof(unsigned long long) == 8); + template<> struct select_npy_type { + const static NPY_TYPES type = NPY_UINT64; + }; + + template + PyObject* get_array(const std::vector& v) + { + npy_intp vsize = v.size(); + NPY_TYPES type = select_npy_type::type; + if(type == NPY_NOTYPE) { + size_t memsize = v.size() * sizeof(double); + double* dp = static_cast(::malloc(memsize)); + for(size_t i = 0; i < v.size(); ++i) dp[i] = v[i]; + PyObject* varray + = PyArray_SimpleNewFromData(1, &vsize, NPY_DOUBLE, dp); + PyArray_UpdateFlags(reinterpret_cast(varray), + NPY_ARRAY_OWNDATA); + return varray; + } + + PyObject* varray + = PyArray_SimpleNewFromData(1, &vsize, type, (void*)(v.data())); + return varray; + } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_annotate, args, kwargs); + template + PyObject* get_2darray(const std::vector<::std::vector>& v) + { + if(v.size() < 1) + throw std::runtime_error("get_2d_array v too small"); - Py_DECREF(args); - Py_DECREF(kwargs); + npy_intp vsize[2] = {static_cast(v.size()), + static_cast(v[0].size())}; - if(res) Py_DECREF(res); + PyArrayObject* varray + = (PyArrayObject*)PyArray_SimpleNew(2, vsize, NPY_DOUBLE); - return res; -} + double* vd_begin = static_cast(PyArray_DATA(varray)); -namespace detail { + for(const ::std::vector& v_row: v) { + if(v_row.size() != static_cast(vsize[1])) + throw std::runtime_error("Missmatched array size"); + std::copy(v_row.begin(), v_row.end(), vd_begin); + vd_begin += vsize[1]; + } -#ifndef WITHOUT_NUMPY -// Type selector for numpy array conversion -template struct select_npy_type { const static NPY_TYPES type = NPY_NOTYPE; }; //Default -template <> struct select_npy_type { const static NPY_TYPES type = NPY_DOUBLE; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_FLOAT; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_BOOL; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT8; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_SHORT; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT64; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT8; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_USHORT; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_ULONG; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; - -// Sanity checks; comment them out or change the numpy type below if you're compiling on -// a platform where they don't apply -static_assert(sizeof(long long) == 8); -template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT64; }; -static_assert(sizeof(unsigned long long) == 8); -template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; - -template -PyObject* get_array(const std::vector& v) -{ - npy_intp vsize = v.size(); - NPY_TYPES type = select_npy_type::type; - if (type == NPY_NOTYPE) { - size_t memsize = v.size()*sizeof(double); - double* dp = static_cast(::malloc(memsize)); - for (size_t i=0; i(varray), NPY_ARRAY_OWNDATA); - return varray; - } - - PyObject* varray = PyArray_SimpleNewFromData(1, &vsize, type, (void*)(v.data())); - return varray; -} - - -template -PyObject* get_2darray(const std::vector<::std::vector>& v) -{ - if (v.size() < 1) throw std::runtime_error("get_2d_array v too small"); - - npy_intp vsize[2] = {static_cast(v.size()), - static_cast(v[0].size())}; - - PyArrayObject *varray = - (PyArrayObject *)PyArray_SimpleNew(2, vsize, NPY_DOUBLE); - - double *vd_begin = static_cast(PyArray_DATA(varray)); - - for (const ::std::vector &v_row : v) { - if (v_row.size() != static_cast(vsize[1])) - throw std::runtime_error("Missmatched array size"); - std::copy(v_row.begin(), v_row.end(), vd_begin); - vd_begin += vsize[1]; - } - - return reinterpret_cast(varray); -} + return reinterpret_cast(varray); + } #else // fallback if we don't have numpy: copy every element of the given vector -template -PyObject* get_array(const std::vector& v) -{ - PyObject* list = PyList_New(v.size()); - for(size_t i = 0; i < v.size(); ++i) { - PyList_SetItem(list, i, PyFloat_FromDouble(v.at(i))); - } - return list; -} + template + PyObject* get_array(const std::vector& v) + { + PyObject* list = PyList_New(v.size()); + for(size_t i = 0; i < v.size(); ++i) { + PyList_SetItem(list, i, PyFloat_FromDouble(v.at(i))); + } + return list; + } #endif // WITHOUT_NUMPY -// sometimes, for labels and such, we need string arrays -inline PyObject * get_array(const std::vector& strings) -{ - PyObject* list = PyList_New(strings.size()); - for (std::size_t i = 0; i < strings.size(); ++i) { - PyList_SetItem(list, i, PyString_FromString(strings[i].c_str())); - } - return list; -} - -// not all matplotlib need 2d arrays, some prefer lists of lists -template -PyObject* get_listlist(const std::vector>& ll) -{ - PyObject* listlist = PyList_New(ll.size()); - for (std::size_t i = 0; i < ll.size(); ++i) { - PyList_SetItem(listlist, i, get_array(ll[i])); - } - return listlist; -} - -} // namespace detail - -/// Plot a line through the given x and y data points.. -/// -/// See: https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.pyplot.plot.html - -// If a container (range) has ::data() that converts to ::value and it has -// ::size() that is convertible to pointer differences, then it's contiguous, -// in which case we need not copy the data. -// https://stackoverflow.com/questions/42851957/contiguous-iterator-detection -// -// TODO: get rid of the plot(vector) specializations, they unnecessarily -// copy the data. -// TODO: provide contiguous and non-contiguous implementations for ranges, -// using iterator_tag. -// https://stackoverflow.com/questions/4688177/how-does-iterator-category-in-c-work -// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1474r0.pdf -// + // sometimes, for labels and such, we need string arrays + inline PyObject* get_array(const std::vector& strings) + { + PyObject* list = PyList_New(strings.size()); + for(std::size_t i = 0; i < strings.size(); ++i) { + PyList_SetItem(list, i, + PyString_FromString(strings[i].c_str())); + } + return list; + } + + // not all matplotlib need 2d arrays, some prefer lists of lists + template + PyObject* get_listlist(const std::vector>& ll) + { + PyObject* listlist = PyList_New(ll.size()); + for(std::size_t i = 0; i < ll.size(); ++i) { + PyList_SetItem(listlist, i, get_array(ll[i])); + } + return listlist; + } -#if __cplusplus >= CPP20 + } // namespace detail -// Support for contiguous ranges, e.g. vector, span, etc. In this case -// we can pass the data pointer directly to python, without copying. -// -// Non-contiguous (but iterable) arrays (e.g. lists), call a different plot, -// one which has to copy the items into a contiguous C-style array before -// passing them to python. -template -bool plot(const ContainerX& x, const ContainerY& y, const std::string& fmt="") -{ - assert(y.size()%x.size() == 0 && "length of y must be a multiple of length of x!"); - - detail::_interpreter::get(); - - NPY_TYPES xtype=detail::select_npy_type::type; - NPY_TYPES ytype=detail::select_npy_type::type; - - npy_intp xsize=x.size(); - npy_intp yrows=xsize, ycols=y.size()/x.size(); - npy_intp ysize[]={yrows, ycols}; // ysize[0] must equal xsize - - // We have 2 options to pass existing data buffers to PyObject: - // PyArray_SimpleFromData() - by default creates C-style (row major) array - // PyArray_New() - uses flags, so we can specify Fotran-style (col major) - // - // In python, - // a=np.array([[3,5], [1,4], [4,1], [5,3]]) - // is stored in row-major mode and has columns - // a[:,0]=[3,1,4,5] and a[:,1]=[5,4,1,3] - // Then, - // plt.plot(a) - // plots the columns of a. So in python the array is row-major, but - // plotted columnwise. - // - // For dataframes (and time series), however, it is more natural to assume - // that the data is stored contiguously in column-major mode, i.e. - // double a[] = [3, 1, 4, 5 - // 5, 4, 1, 3]; - // We let python know that is the case, by specifying the NPY_ARRAY_FARRAY - // flag. This rules out the usage of PyArray_SimpleFromData(). - // - // Of course, in the vector-only version of this plot() function all this - // is irrelevant, because that plot is 1-dimensional anyway. + /// Plot a line through the given x and y data points.. + /// + /// See: + /// https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.pyplot.plot.html + + // If a container (range) has ::data() that converts to ::value and it has + // ::size() that is convertible to pointer differences, then it's + // contiguous, in which case we need not copy the data. + // https://stackoverflow.com/questions/42851957/contiguous-iterator-detection // - // If there are real-world applications that need to assume that the - // C-data is in row-major mode, we should address that. + // TODO: get rid of the plot(vector) specializations, they unnecessarily + // copy the data. + // TODO: provide contiguous and non-contiguous implementations for ranges, + // using iterator_tag. + // https://stackoverflow.com/questions/4688177/how-does-iterator-category-in-c-work + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1474r0.pdf // - // TODO: - // To that end, perhaps we can introduce C++ tags cmajor_tag and rmajor_tag - // and define a custom concept from contiguous_range, by further - // conditioning on the storage tags. The caller then specifies the major - // storage mode when wrapping the C-style array into a range. - PyObject* xarray = - PyArray_New(&PyArray_Type, - 1, &xsize, xtype, nullptr, (void*) x.data(), - 0, NPY_ARRAY_FARRAY, nullptr); - PyObject* yarray = - PyArray_New(&PyArray_Type, - 2, ysize, ytype, nullptr, (void*) y.data(), - 0, NPY_ARRAY_FARRAY, nullptr); // column major by design! - - PyObject* pystring = PyString_FromString(fmt.c_str()); - - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); - - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); - - Py_DECREF(plot_args); - if(res) Py_DECREF(res); - - return res; -} - -template -bool plot(const ContainerX& x, const ContainerY& y, - const std::map& keywords) -{ - assert(y.size()%x.size() == 0 && "length of y must be a multiple of length of x!"); - - detail::_interpreter::get(); - - NPY_TYPES xtype=detail::select_npy_type::type; - NPY_TYPES ytype=detail::select_npy_type::type; - - npy_intp xsize=x.size(); - npy_intp yrows=xsize, ycols=y.size()/x.size(); - npy_intp ysize[]={yrows, ycols}; // ysize[0] must equal xsize - - // Same comments as above. - PyObject* xarray = - PyArray_New(&PyArray_Type, - 1, &xsize, xtype, nullptr, (void*) x.data(), - 0, NPY_ARRAY_FARRAY, nullptr); - PyObject* yarray = - PyArray_New(&PyArray_Type, - 2, ysize, ytype, nullptr, (void*) y.data(), - 0, NPY_ARRAY_FARRAY, nullptr); // column major by design! - - // construct positional args - PyObject* args = PyTuple_New(2); - PyTuple_SetItem(args, 0, xarray); - PyTuple_SetItem(args, 1, yarray); - - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) - { - PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); - } - - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, args, kwargs); - - Py_DECREF(args); - Py_DECREF(kwargs); - if(res) Py_DECREF(res); - - return res; -} - -template -bool plot(const ContainerY& y, const std::string& format = "") -{ - std::vector x(y.size()); - std::iota(x.begin(), x.end(), 0); - return plot(x,y,format); -} - -template -bool plot(const ContainerY& y, const std::map& keywords) -{ - std::vector x(y.size()); - std::iota(x.begin(), x.end(), 0); - return plot(x,y,keywords); -} +#if __cplusplus >= CPP20 -#else + // Support for contiguous ranges, e.g. vector, span, etc. In this case + // we can pass the data pointer directly to python, without copying. + // + // Non-contiguous (but iterable) arrays (e.g. lists), call a different plot, + // one which has to copy the items into a contiguous C-style array before + // passing them to python. + template + bool plot(const ContainerX& x, const ContainerY& y, + const std::string& fmt = "") + { + assert(y.size() % x.size() == 0 + && "length of y must be a multiple of length of x!"); -// For the less fortunate ones who can't or won't use C++ 20, we stick with -// vectors, since there's no ranges concept before that. + detail::_interpreter::get(); -template -bool plot(const std::vector &x, const std::vector &y, const std::map& keywords) -{ - assert(y.size()%x.size() == 0 && "length of y must be a multiple of length of x!"); + NPY_TYPES xtype + = detail::select_npy_type::type; + NPY_TYPES ytype + = detail::select_npy_type::type; + + npy_intp xsize = x.size(); + npy_intp yrows = xsize, ycols = y.size() / x.size(); + npy_intp ysize[] = {yrows, ycols}; // ysize[0] must equal xsize + + // We have 2 options to pass existing data buffers to PyObject: + // PyArray_SimpleFromData() - by default creates C-style (row major) + // array PyArray_New() - uses flags, so we can specify Fotran-style (col + // major) + // + // In python, + // a=np.array([[3,5], [1,4], [4,1], [5,3]]) + // is stored in row-major mode and has columns + // a[:,0]=[3,1,4,5] and a[:,1]=[5,4,1,3] + // Then, + // plt.plot(a) + // plots the columns of a. So in python the array is row-major, but + // plotted columnwise. + // + // For dataframes (and time series), however, it is more natural to + // assume that the data is stored contiguously in column-major mode, + // i.e. double a[] = [3, 1, 4, 5 + // 5, 4, 1, 3]; + // We let python know that is the case, by specifying the + // NPY_ARRAY_FARRAY flag. This rules out the usage of + // PyArray_SimpleFromData(). + // + // Of course, in the vector-only version of this plot() function all + // this is irrelevant, because that plot is 1-dimensional anyway. + // + // If there are real-world applications that need to assume that the + // C-data is in row-major mode, we should address that. + // + // TODO: + // To that end, perhaps we can introduce C++ tags cmajor_tag and + // rmajor_tag and define a custom concept from contiguous_range, by + // further conditioning on the storage tags. The caller then specifies + // the major storage mode when wrapping the C-style array into a range. + PyObject* xarray + = PyArray_New(&PyArray_Type, 1, &xsize, xtype, nullptr, + (void*)x.data(), 0, NPY_ARRAY_FARRAY, nullptr); + PyObject* yarray = PyArray_New(&PyArray_Type, 2, ysize, ytype, nullptr, + (void*)y.data(), 0, NPY_ARRAY_FARRAY, + nullptr); // column major by design! + + PyObject* pystring = PyString_FromString(fmt.c_str()); - detail::_interpreter::get(); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - // using numpy arrays - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_plot, plot_args); - // construct positional args - PyObject* args = PyTuple_New(2); - PyTuple_SetItem(args, 0, xarray); - PyTuple_SetItem(args, 1, yarray); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) - { - PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + return res; } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, args, kwargs); - - Py_DECREF(args); - Py_DECREF(kwargs); - if(res) Py_DECREF(res); + template + bool plot(const ContainerX& x, const ContainerY& y, + const std::map& keywords) + { + assert(y.size() % x.size() == 0 + && "length of y must be a multiple of length of x!"); - return res; -} + detail::_interpreter::get(); -template -bool plot(const std::vector& x, const std::vector& y, const std::string& s = "") -{ - assert(y.size()%x.size() == 0 && "length of y must be a multiple of length of x!"); + NPY_TYPES xtype + = detail::select_npy_type::type; + NPY_TYPES ytype + = detail::select_npy_type::type; - detail::_interpreter::get(); + npy_intp xsize = x.size(); + npy_intp yrows = xsize, ycols = y.size() / x.size(); + npy_intp ysize[] = {yrows, ycols}; // ysize[0] must equal xsize - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + // Same comments as above. + PyObject* xarray + = PyArray_New(&PyArray_Type, 1, &xsize, xtype, nullptr, + (void*)x.data(), 0, NPY_ARRAY_FARRAY, nullptr); + PyObject* yarray = PyArray_New(&PyArray_Type, 2, ysize, ytype, nullptr, + (void*)y.data(), 0, NPY_ARRAY_FARRAY, + nullptr); // column major by design! - PyObject* pystring = PyString_FromString(s.c_str()); + // construct positional args + PyObject* args = PyTuple_New(2); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_plot, args, kwargs); - Py_DECREF(plot_args); - if(res) Py_DECREF(res); + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); - return res; -} + return res; + } -template -bool plot(const std::vector& y, const std::string& format = "") -{ - std::vector x(y.size()); - for(size_t i=0; i + bool plot(const ContainerY& y, const std::string& format = "") + { + std::vector x(y.size()); + std::iota(x.begin(), x.end(), 0); + return plot(x, y, format); + } -template -bool plot(const std::vector& y, const std::map& keywords) -{ - std::vector x(y.size()); - for(size_t i=0; i + bool plot(const ContainerY& y, + const std::map& keywords) + { + std::vector x(y.size()); + std::iota(x.begin(), x.end(), 0); + return plot(x, y, keywords); + } -#endif +#else -// TODO - it should be possible to make this work by implementing -// a non-numpy alternative for `detail::get_2darray()`. -#ifndef WITHOUT_NUMPY -template -void plot_surface(const std::vector<::std::vector> &x, - const std::vector<::std::vector> &y, - const std::vector<::std::vector> &z, - const std::map &keywords = - std::map(), - const long fig_number=0) -{ - detail::_interpreter::get(); - - // We lazily load the modules here the first time this function is called - // because I'm not sure that we can assume "matplotlib installed" implies - // "mpl_toolkits installed" on all platforms, and we don't want to require - // it for people who don't need 3d plots. - static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; - if (!mpl_toolkitsmod) { - detail::_interpreter::get(); - - PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); - PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); - if (!mpl_toolkits || !axis3d) { throw std::runtime_error("couldnt create string"); } - - mpl_toolkitsmod = PyImport_Import(mpl_toolkits); - Py_DECREF(mpl_toolkits); - if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } - - axis3dmod = PyImport_Import(axis3d); - Py_DECREF(axis3d); - if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } - } - - assert(x.size() == y.size()); - assert(y.size() == z.size()); - - // using numpy arrays - PyObject *xarray = detail::get_2darray(x); - PyObject *yarray = detail::get_2darray(y); - PyObject *zarray = detail::get_2darray(z); - - // construct positional args - PyObject *args = PyTuple_New(3); - PyTuple_SetItem(args, 0, xarray); - PyTuple_SetItem(args, 1, yarray); - PyTuple_SetItem(args, 2, zarray); - - // Build up the kw args. - PyObject *kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "rstride", PyInt_FromLong(1)); - PyDict_SetItemString(kwargs, "cstride", PyInt_FromLong(1)); - - PyObject *python_colormap_coolwarm = PyObject_GetAttrString( - detail::_interpreter::get().s_python_colormap, "coolwarm"); - - PyDict_SetItemString(kwargs, "cmap", python_colormap_coolwarm); - - for (std::map::const_iterator it = keywords.begin(); - it != keywords.end(); ++it) { - if (it->first == "linewidth" || it->first == "alpha") { - PyDict_SetItemString(kwargs, it->first.c_str(), - PyFloat_FromDouble(std::stod(it->second))); - } else { - PyDict_SetItemString(kwargs, it->first.c_str(), - PyString_FromString(it->second.c_str())); - } - } - - PyObject *fig_args = PyTuple_New(1); - PyObject* fig = nullptr; - PyTuple_SetItem(fig_args, 0, PyLong_FromLong(fig_number)); - PyObject *fig_exists = - PyObject_CallObject( - detail::_interpreter::get().s_python_function_fignum_exists, fig_args); - if (!PyObject_IsTrue(fig_exists)) { - fig = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, - detail::_interpreter::get().s_python_empty_tuple); - } else { - fig = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, - fig_args); - } - Py_DECREF(fig_exists); - if (!fig) throw std::runtime_error("Call to figure() failed."); - - PyObject *gca_kwargs = PyDict_New(); - PyDict_SetItemString(gca_kwargs, "projection", PyString_FromString("3d")); - - PyObject *gca = PyObject_GetAttrString(fig, "gca"); - if (!gca) throw std::runtime_error("No gca"); - Py_INCREF(gca); - PyObject *axis = PyObject_Call( - gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); - - if (!axis) throw std::runtime_error("No axis"); - Py_INCREF(axis); - - Py_DECREF(gca); - Py_DECREF(gca_kwargs); - - PyObject *plot_surface = PyObject_GetAttrString(axis, "plot_surface"); - if (!plot_surface) throw std::runtime_error("No surface"); - Py_INCREF(plot_surface); - PyObject *res = PyObject_Call(plot_surface, args, kwargs); - if (!res) throw std::runtime_error("failed surface"); - Py_DECREF(plot_surface); - - Py_DECREF(axis); - Py_DECREF(args); - Py_DECREF(kwargs); - if (res) Py_DECREF(res); -} - -template -void contour(const std::vector<::std::vector> &x, - const std::vector<::std::vector> &y, - const std::vector<::std::vector> &z, - const std::map &keywords = {}) -{ - detail::_interpreter::get(); - - // using numpy arrays - PyObject *xarray = detail::get_2darray(x); - PyObject *yarray = detail::get_2darray(y); - PyObject *zarray = detail::get_2darray(z); - - // construct positional args - PyObject *args = PyTuple_New(3); - PyTuple_SetItem(args, 0, xarray); - PyTuple_SetItem(args, 1, yarray); - PyTuple_SetItem(args, 2, zarray); - - // Build up the kw args. - PyObject *kwargs = PyDict_New(); - - PyObject *python_colormap_coolwarm = PyObject_GetAttrString( - detail::_interpreter::get().s_python_colormap, "coolwarm"); - - PyDict_SetItemString(kwargs, "cmap", python_colormap_coolwarm); - - for (std::map::const_iterator it = keywords.begin(); - it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), - PyString_FromString(it->second.c_str())); - } - - PyObject *res = PyObject_Call(detail::_interpreter::get().s_python_function_contour, args, kwargs); - if (!res) - throw std::runtime_error("failed contour"); - - Py_DECREF(args); - Py_DECREF(kwargs); - if (res) Py_DECREF(res); -} - -template -void spy(const std::vector<::std::vector> &x, - const double markersize = -1, // -1 for default matplotlib size - const std::map &keywords = {}) -{ - detail::_interpreter::get(); - - PyObject *xarray = detail::get_2darray(x); - - PyObject *kwargs = PyDict_New(); - if (markersize != -1) { - PyDict_SetItemString(kwargs, "markersize", PyFloat_FromDouble(markersize)); - } - for (std::map::const_iterator it = keywords.begin(); - it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), - PyString_FromString(it->second.c_str())); - } - - PyObject *plot_args = PyTuple_New(1); - PyTuple_SetItem(plot_args, 0, xarray); - - PyObject *res = PyObject_Call( - detail::_interpreter::get().s_python_function_spy, plot_args, kwargs); - - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if (res) Py_DECREF(res); -} -#endif // WITHOUT_NUMPY + // For the less fortunate ones who can't or won't use C++ 20, we stick with + // vectors, since there's no ranges concept before that. -template -void plot3(const std::vector &x, - const std::vector &y, - const std::vector &z, - const std::map &keywords = - std::map(), - const long fig_number=0) -{ - detail::_interpreter::get(); - - // Same as with plot_surface: We lazily load the modules here the first time - // this function is called because I'm not sure that we can assume "matplotlib - // installed" implies "mpl_toolkits installed" on all platforms, and we don't - // want to require it for people who don't need 3d plots. - static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; - if (!mpl_toolkitsmod) { - detail::_interpreter::get(); - - PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); - PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); - if (!mpl_toolkits || !axis3d) { throw std::runtime_error("couldnt create string"); } - - mpl_toolkitsmod = PyImport_Import(mpl_toolkits); - Py_DECREF(mpl_toolkits); - if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } - - axis3dmod = PyImport_Import(axis3d); - Py_DECREF(axis3d); - if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } - } - - assert(x.size() == y.size()); - assert(y.size() == z.size()); - - PyObject *xarray = detail::get_array(x); - PyObject *yarray = detail::get_array(y); - PyObject *zarray = detail::get_array(z); - - // construct positional args - PyObject *args = PyTuple_New(3); - PyTuple_SetItem(args, 0, xarray); - PyTuple_SetItem(args, 1, yarray); - PyTuple_SetItem(args, 2, zarray); - - // Build up the kw args. - PyObject *kwargs = PyDict_New(); - - for (std::map::const_iterator it = keywords.begin(); - it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), - PyString_FromString(it->second.c_str())); - } - - PyObject *fig_args = PyTuple_New(1); - PyObject* fig = nullptr; - PyTuple_SetItem(fig_args, 0, PyLong_FromLong(fig_number)); - PyObject *fig_exists = - PyObject_CallObject(detail::_interpreter::get().s_python_function_fignum_exists, fig_args); - if (!PyObject_IsTrue(fig_exists)) { - fig = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, - detail::_interpreter::get().s_python_empty_tuple); - } else { - fig = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, - fig_args); - } - if (!fig) throw std::runtime_error("Call to figure() failed."); - - PyObject *gca_kwargs = PyDict_New(); - PyDict_SetItemString(gca_kwargs, "projection", PyString_FromString("3d")); - - PyObject *gca = PyObject_GetAttrString(fig, "gca"); - if (!gca) throw std::runtime_error("No gca"); - Py_INCREF(gca); - PyObject *axis = PyObject_Call( - gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); - - if (!axis) throw std::runtime_error("No axis"); - Py_INCREF(axis); - - Py_DECREF(gca); - Py_DECREF(gca_kwargs); - - PyObject *plot3 = PyObject_GetAttrString(axis, "plot"); - if (!plot3) throw std::runtime_error("No 3D line plot"); - Py_INCREF(plot3); - PyObject *res = PyObject_Call(plot3, args, kwargs); - if (!res) throw std::runtime_error("Failed 3D line plot"); - Py_DECREF(plot3); - - Py_DECREF(axis); - Py_DECREF(args); - Py_DECREF(kwargs); - if (res) Py_DECREF(res); -} - -template -bool stem(const std::vector &x, const std::vector &y, const std::map& keywords) -{ - assert(x.size() == y.size()); - - detail::_interpreter::get(); - - // using numpy arrays - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); - - // construct positional args - PyObject* args = PyTuple_New(2); - PyTuple_SetItem(args, 0, xarray); - PyTuple_SetItem(args, 1, yarray); - - // construct keyword args - PyObject* kwargs = PyDict_New(); - for (std::map::const_iterator it = - keywords.begin(); it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), - PyString_FromString(it->second.c_str())); - } - - PyObject* res = PyObject_Call( - detail::_interpreter::get().s_python_function_stem, args, kwargs); + template + bool plot(const std::vector& x, const std::vector& y, + const std::map& keywords) + { + assert(y.size() % x.size() == 0 + && "length of y must be a multiple of length of x!"); - Py_DECREF(args); - Py_DECREF(kwargs); - if (res) - Py_DECREF(res); + detail::_interpreter::get(); - return res; -} + // using numpy arrays + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); -template< typename Numeric > -bool fill(const std::vector& x, const std::vector& y, const std::map& keywords) -{ - assert(x.size() == y.size()); + // construct positional args + PyObject* args = PyTuple_New(2); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); - detail::_interpreter::get(); + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } - // using numpy arrays - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_plot, args, kwargs); - // construct positional args - PyObject* args = PyTuple_New(2); - PyTuple_SetItem(args, 0, xarray); - PyTuple_SetItem(args, 1, yarray); + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); - // construct keyword args - PyObject* kwargs = PyDict_New(); - for (auto it = keywords.begin(); it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + return res; } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_fill, args, kwargs); + template + bool plot(const std::vector& x, const std::vector& y, + const std::string& s = "") + { + assert(y.size() % x.size() == 0 + && "length of y must be a multiple of length of x!"); - Py_DECREF(args); - Py_DECREF(kwargs); + detail::_interpreter::get(); - if (res) Py_DECREF(res); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - return res; -} + PyObject* pystring = PyString_FromString(s.c_str()); -template< typename Numeric > -bool fill_between(const std::vector& x, const std::vector& y1, const std::vector& y2, const std::map& keywords) -{ - assert(x.size() == y1.size()); - assert(x.size() == y2.size()); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - detail::_interpreter::get(); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_plot, plot_args); - // using numpy arrays - PyObject* xarray = detail::get_array(x); - PyObject* y1array = detail::get_array(y1); - PyObject* y2array = detail::get_array(y2); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - // construct positional args - PyObject* args = PyTuple_New(3); - PyTuple_SetItem(args, 0, xarray); - PyTuple_SetItem(args, 1, y1array); - PyTuple_SetItem(args, 2, y2array); + return res; + } - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + template + bool plot(const std::vector& y, const std::string& format = "") + { + std::vector x(y.size()); + for(size_t i = 0; i < x.size(); ++i) x.at(i) = i; + return plot(x, y, format); } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_fill_between, args, kwargs); + template + bool plot(const std::vector& y, + const std::map& keywords) + { + std::vector x(y.size()); + for(size_t i = 0; i < x.size(); ++i) x.at(i) = i; + return plot(x, y, keywords); + } - Py_DECREF(args); - Py_DECREF(kwargs); - if(res) Py_DECREF(res); +#endif - return res; -} +// TODO - it should be possible to make this work by implementing +// a non-numpy alternative for `detail::get_2darray()`. +#ifndef WITHOUT_NUMPY + template + void plot_surface(const std::vector<::std::vector>& x, + const std::vector<::std::vector>& y, + const std::vector<::std::vector>& z, + const std::map& keywords + = std::map(), + const long fig_number = 0) + { + detail::_interpreter::get(); -template -bool arrow(Numeric x, Numeric y, Numeric end_x, Numeric end_y, const std::string& fc = "r", - const std::string ec = "k", Numeric head_length = 0.25, Numeric head_width = 0.1625) { - PyObject* obj_x = PyFloat_FromDouble(x); - PyObject* obj_y = PyFloat_FromDouble(y); - PyObject* obj_end_x = PyFloat_FromDouble(end_x); - PyObject* obj_end_y = PyFloat_FromDouble(end_y); + // We lazily load the modules here the first time this function is + // called because I'm not sure that we can assume "matplotlib installed" + // implies "mpl_toolkits installed" on all platforms, and we don't want + // to require it for people who don't need 3d plots. + static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; + if(!mpl_toolkitsmod) { + detail::_interpreter::get(); + + PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); + PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); + if(!mpl_toolkits || !axis3d) { + throw std::runtime_error("couldnt create string"); + } + + mpl_toolkitsmod = PyImport_Import(mpl_toolkits); + Py_DECREF(mpl_toolkits); + if(!mpl_toolkitsmod) { + throw std::runtime_error("Error loading module mpl_toolkits!"); + } + + axis3dmod = PyImport_Import(axis3d); + Py_DECREF(axis3d); + if(!axis3dmod) { + throw std::runtime_error( + "Error loading module mpl_toolkits.mplot3d!"); + } + } - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "fc", PyString_FromString(fc.c_str())); - PyDict_SetItemString(kwargs, "ec", PyString_FromString(ec.c_str())); - PyDict_SetItemString(kwargs, "head_width", PyFloat_FromDouble(head_width)); - PyDict_SetItemString(kwargs, "head_length", PyFloat_FromDouble(head_length)); + assert(x.size() == y.size()); + assert(y.size() == z.size()); - PyObject* plot_args = PyTuple_New(4); - PyTuple_SetItem(plot_args, 0, obj_x); - PyTuple_SetItem(plot_args, 1, obj_y); - PyTuple_SetItem(plot_args, 2, obj_end_x); - PyTuple_SetItem(plot_args, 3, obj_end_y); + // using numpy arrays + PyObject* xarray = detail::get_2darray(x); + PyObject* yarray = detail::get_2darray(y); + PyObject* zarray = detail::get_2darray(z); - PyObject* res = - PyObject_Call(detail::_interpreter::get().s_python_function_arrow, plot_args, kwargs); + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + PyTuple_SetItem(args, 2, zarray); - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if (res) - Py_DECREF(res); + // Build up the kw args. + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "rstride", PyInt_FromLong(1)); + PyDict_SetItemString(kwargs, "cstride", PyInt_FromLong(1)); + + PyObject* python_colormap_coolwarm = PyObject_GetAttrString( + detail::_interpreter::get().s_python_colormap, "coolwarm"); + + PyDict_SetItemString(kwargs, "cmap", python_colormap_coolwarm); + + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + if(it->first == "linewidth" || it->first == "alpha") { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyFloat_FromDouble(std::stod(it->second))); + } + else { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } + } - return res; -} + PyObject* fig_args = PyTuple_New(1); + PyObject* fig = nullptr; + PyTuple_SetItem(fig_args, 0, PyLong_FromLong(fig_number)); + PyObject* fig_exists = PyObject_CallObject( + detail::_interpreter::get().s_python_function_fignum_exists, + fig_args); + if(!PyObject_IsTrue(fig_exists)) { + fig = PyObject_CallObject( + detail::_interpreter::get().s_python_function_figure, + detail::_interpreter::get().s_python_empty_tuple); + } + else { + fig = PyObject_CallObject( + detail::_interpreter::get().s_python_function_figure, fig_args); + } + Py_DECREF(fig_exists); + if(!fig) throw std::runtime_error("Call to figure() failed."); -template< typename Numeric> -bool hist(const std::vector& y, long bins=10,std::string color="b", - double alpha=1.0, bool cumulative=false) -{ - detail::_interpreter::get(); + PyObject* gca_kwargs = PyDict_New(); + PyDict_SetItemString(gca_kwargs, "projection", + PyString_FromString("3d")); - PyObject* yarray = detail::get_array(y); + PyObject* gca = PyObject_GetAttrString(fig, "gca"); + if(!gca) throw std::runtime_error("No gca"); + Py_INCREF(gca); + PyObject* axis = PyObject_Call( + gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "bins", PyLong_FromLong(bins)); - PyDict_SetItemString(kwargs, "color", PyString_FromString(color.c_str())); - PyDict_SetItemString(kwargs, "alpha", PyFloat_FromDouble(alpha)); - PyDict_SetItemString(kwargs, "cumulative", cumulative ? Py_True : Py_False); + if(!axis) throw std::runtime_error("No axis"); + Py_INCREF(axis); - PyObject* plot_args = PyTuple_New(1); + Py_DECREF(gca); + Py_DECREF(gca_kwargs); - PyTuple_SetItem(plot_args, 0, yarray); + PyObject* plot_surface = PyObject_GetAttrString(axis, "plot_surface"); + if(!plot_surface) throw std::runtime_error("No surface"); + Py_INCREF(plot_surface); + PyObject* res = PyObject_Call(plot_surface, args, kwargs); + if(!res) throw std::runtime_error("failed surface"); + Py_DECREF(plot_surface); + Py_DECREF(axis); + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_hist, plot_args, kwargs); + template + void contour(const std::vector<::std::vector>& x, + const std::vector<::std::vector>& y, + const std::vector<::std::vector>& z, + const std::map& keywords = {}) + { + detail::_interpreter::get(); + // using numpy arrays + PyObject* xarray = detail::get_2darray(x); + PyObject* yarray = detail::get_2darray(y); + PyObject* zarray = detail::get_2darray(z); - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if(res) Py_DECREF(res); + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + PyTuple_SetItem(args, 2, zarray); - return res; -} + // Build up the kw args. + PyObject* kwargs = PyDict_New(); -#ifndef WITHOUT_NUMPY -namespace detail { + PyObject* python_colormap_coolwarm = PyObject_GetAttrString( + detail::_interpreter::get().s_python_colormap, "coolwarm"); -inline void imshow(void *ptr, const NPY_TYPES type, const int rows, const int columns, const int colors, const std::map &keywords, PyObject** out) -{ - assert(type == NPY_UINT8 || type == NPY_FLOAT); - assert(colors == 1 || colors == 3 || colors == 4); + PyDict_SetItemString(kwargs, "cmap", python_colormap_coolwarm); - detail::_interpreter::get(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } - // construct args - npy_intp dims[3] = { rows, columns, colors }; - PyObject *args = PyTuple_New(1); - PyTuple_SetItem(args, 0, PyArray_SimpleNewFromData(colors == 1 ? 2 : 3, dims, type, ptr)); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_contour, args, + kwargs); + if(!res) throw std::runtime_error("failed contour"); - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) - { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); } - PyObject *res = PyObject_Call(detail::_interpreter::get().s_python_function_imshow, args, kwargs); - Py_DECREF(args); - Py_DECREF(kwargs); - if (!res) - throw std::runtime_error("Call to imshow() failed"); - if (out) - *out = res; - else - Py_DECREF(res); -} - -} // namespace detail - -inline void imshow(const unsigned char *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}, PyObject** out = nullptr) -{ - detail::imshow((void *) ptr, NPY_UINT8, rows, columns, colors, keywords, out); -} - -inline void imshow(const float *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}, PyObject** out = nullptr) -{ - detail::imshow((void *) ptr, NPY_FLOAT, rows, columns, colors, keywords, out); -} + template + void spy(const std::vector<::std::vector>& x, + const double markersize = -1, // -1 for default matplotlib size + const std::map& keywords = {}) + { + detail::_interpreter::get(); -#ifdef WITH_OPENCV -void imshow(const cv::Mat &image, const std::map &keywords = {}) -{ - // Convert underlying type of matrix, if needed - cv::Mat image2; - NPY_TYPES npy_type = NPY_UINT8; - switch (image.type() & CV_MAT_DEPTH_MASK) { - case CV_8U: - image2 = image; - break; - case CV_32F: - image2 = image; - npy_type = NPY_FLOAT; - break; - default: - image.convertTo(image2, CV_MAKETYPE(CV_8U, image.channels())); - } - - // If color image, convert from BGR to RGB - switch (image2.channels()) { - case 3: - cv::cvtColor(image2, image2, CV_BGR2RGB); - break; - case 4: - cv::cvtColor(image2, image2, CV_BGRA2RGBA); - } - - detail::imshow(image2.data, npy_type, image2.rows, image2.cols, image2.channels(), keywords); -} -#endif // WITH_OPENCV -#endif // WITHOUT_NUMPY + PyObject* xarray = detail::get_2darray(x); -template -bool scatter(const std::vector& x, - const std::vector& y, - const double s=1.0, // The marker size in points**2 - const std::map & keywords = {}) -{ - detail::_interpreter::get(); + PyObject* kwargs = PyDict_New(); + if(markersize != -1) { + PyDict_SetItemString(kwargs, "markersize", + PyFloat_FromDouble(markersize)); + } + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } - assert(x.size() == y.size()); + PyObject* plot_args = PyTuple_New(1); + PyTuple_SetItem(plot_args, 0, xarray); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + PyObject* res + = PyObject_Call(detail::_interpreter::get().s_python_function_spy, + plot_args, kwargs); - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "s", PyLong_FromLong(s)); - for (const auto& it : keywords) - { - PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); } +#endif // WITHOUT_NUMPY - PyObject* plot_args = PyTuple_New(2); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_scatter, plot_args, kwargs); - - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if(res) Py_DECREF(res); - - return res; -} - -template - bool scatter_colored(const std::vector& x, - const std::vector& y, - const std::vector& colors, - const double s=1.0, // The marker size in points**2 - const std::map & keywords = {}) + template + void plot3(const std::vector& x, const std::vector& y, + const std::vector& z, + const std::map& keywords + = std::map(), + const long fig_number = 0) { detail::_interpreter::get(); + // Same as with plot_surface: We lazily load the modules here the first + // time this function is called because I'm not sure that we can assume + // "matplotlib installed" implies "mpl_toolkits installed" on all + // platforms, and we don't want to require it for people who don't need + // 3d plots. + static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; + if(!mpl_toolkitsmod) { + detail::_interpreter::get(); + + PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); + PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); + if(!mpl_toolkits || !axis3d) { + throw std::runtime_error("couldnt create string"); + } + + mpl_toolkitsmod = PyImport_Import(mpl_toolkits); + Py_DECREF(mpl_toolkits); + if(!mpl_toolkitsmod) { + throw std::runtime_error("Error loading module mpl_toolkits!"); + } + + axis3dmod = PyImport_Import(axis3d); + Py_DECREF(axis3d); + if(!axis3dmod) { + throw std::runtime_error( + "Error loading module mpl_toolkits.mplot3d!"); + } + } + assert(x.size() == y.size()); + assert(y.size() == z.size()); PyObject* xarray = detail::get_array(x); PyObject* yarray = detail::get_array(y); - PyObject* colors_array = detail::get_array(colors); + PyObject* zarray = detail::get_array(z); + + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + PyTuple_SetItem(args, 2, zarray); + // Build up the kw args. PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "s", PyLong_FromLong(s)); - PyDict_SetItemString(kwargs, "c", colors_array); - for (const auto& it : keywords) - { - PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); } - PyObject* plot_args = PyTuple_New(2); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_scatter, plot_args, kwargs); - - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if(res) Py_DECREF(res); - - return res; - } + PyObject* fig_args = PyTuple_New(1); + PyObject* fig = nullptr; + PyTuple_SetItem(fig_args, 0, PyLong_FromLong(fig_number)); + PyObject* fig_exists = PyObject_CallObject( + detail::_interpreter::get().s_python_function_fignum_exists, + fig_args); + if(!PyObject_IsTrue(fig_exists)) { + fig = PyObject_CallObject( + detail::_interpreter::get().s_python_function_figure, + detail::_interpreter::get().s_python_empty_tuple); + } + else { + fig = PyObject_CallObject( + detail::_interpreter::get().s_python_function_figure, fig_args); + } + if(!fig) throw std::runtime_error("Call to figure() failed."); + PyObject* gca_kwargs = PyDict_New(); + PyDict_SetItemString(gca_kwargs, "projection", + PyString_FromString("3d")); -template -bool scatter(const std::vector& x, - const std::vector& y, - const std::vector& z, - const double s=1.0, // The marker size in points**2 - const std::map & keywords = {}, - const long fig_number=0) { - detail::_interpreter::get(); - - // Same as with plot_surface: We lazily load the modules here the first time - // this function is called because I'm not sure that we can assume "matplotlib - // installed" implies "mpl_toolkits installed" on all platforms, and we don't - // want to require it for people who don't need 3d plots. - static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; - if (!mpl_toolkitsmod) { - detail::_interpreter::get(); - - PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); - PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); - if (!mpl_toolkits || !axis3d) { throw std::runtime_error("couldnt create string"); } - - mpl_toolkitsmod = PyImport_Import(mpl_toolkits); - Py_DECREF(mpl_toolkits); - if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } - - axis3dmod = PyImport_Import(axis3d); - Py_DECREF(axis3d); - if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } - } - - assert(x.size() == y.size()); - assert(y.size() == z.size()); - - PyObject *xarray = detail::get_array(x); - PyObject *yarray = detail::get_array(y); - PyObject *zarray = detail::get_array(z); - - // construct positional args - PyObject *args = PyTuple_New(3); - PyTuple_SetItem(args, 0, xarray); - PyTuple_SetItem(args, 1, yarray); - PyTuple_SetItem(args, 2, zarray); - - // Build up the kw args. - PyObject *kwargs = PyDict_New(); - - for (std::map::const_iterator it = keywords.begin(); - it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), - PyString_FromString(it->second.c_str())); - } - PyObject *fig_args = PyTuple_New(1); - PyObject* fig = nullptr; - PyTuple_SetItem(fig_args, 0, PyLong_FromLong(fig_number)); - PyObject *fig_exists = - PyObject_CallObject(detail::_interpreter::get().s_python_function_fignum_exists, fig_args); - if (!PyObject_IsTrue(fig_exists)) { - fig = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, - detail::_interpreter::get().s_python_empty_tuple); - } else { - fig = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, - fig_args); - } - Py_DECREF(fig_exists); - if (!fig) throw std::runtime_error("Call to figure() failed."); - - PyObject *gca_kwargs = PyDict_New(); - PyDict_SetItemString(gca_kwargs, "projection", PyString_FromString("3d")); - - PyObject *gca = PyObject_GetAttrString(fig, "gca"); - if (!gca) throw std::runtime_error("No gca"); - Py_INCREF(gca); - PyObject *axis = PyObject_Call( - gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); + PyObject* gca = PyObject_GetAttrString(fig, "gca"); + if(!gca) throw std::runtime_error("No gca"); + Py_INCREF(gca); + PyObject* axis = PyObject_Call( + gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); - if (!axis) throw std::runtime_error("No axis"); - Py_INCREF(axis); + if(!axis) throw std::runtime_error("No axis"); + Py_INCREF(axis); - Py_DECREF(gca); - Py_DECREF(gca_kwargs); - - PyObject *plot3 = PyObject_GetAttrString(axis, "scatter"); - if (!plot3) throw std::runtime_error("No 3D line plot"); - Py_INCREF(plot3); - PyObject *res = PyObject_Call(plot3, args, kwargs); - if (!res) throw std::runtime_error("Failed 3D line plot"); - Py_DECREF(plot3); + Py_DECREF(gca); + Py_DECREF(gca_kwargs); - Py_DECREF(axis); - Py_DECREF(args); - Py_DECREF(kwargs); - Py_DECREF(fig); - if (res) Py_DECREF(res); - return res; + PyObject* plot3 = PyObject_GetAttrString(axis, "plot"); + if(!plot3) throw std::runtime_error("No 3D line plot"); + Py_INCREF(plot3); + PyObject* res = PyObject_Call(plot3, args, kwargs); + if(!res) throw std::runtime_error("Failed 3D line plot"); + Py_DECREF(plot3); -} - -template -bool boxplot(const std::vector>& data, - const std::vector& labels = {}, - const std::map & keywords = {}) -{ - detail::_interpreter::get(); - - PyObject* listlist = detail::get_listlist(data); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, listlist); - - PyObject* kwargs = PyDict_New(); - - // kwargs needs the labels, if there are (the correct number of) labels - if (!labels.empty() && labels.size() == data.size()) { - PyDict_SetItemString(kwargs, "labels", detail::get_array(labels)); - } + Py_DECREF(axis); + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + } - // take care of the remaining keywords - for (const auto& it : keywords) + template + bool stem(const std::vector& x, const std::vector& y, + const std::map& keywords) { - PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); - } + assert(x.size() == y.size()); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_boxplot, args, kwargs); + detail::_interpreter::get(); - Py_DECREF(args); - Py_DECREF(kwargs); + // using numpy arrays + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - if(res) Py_DECREF(res); + // construct positional args + PyObject* args = PyTuple_New(2); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); - return res; -} + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } -template -bool boxplot(const std::vector& data, - const std::map & keywords = {}) -{ - detail::_interpreter::get(); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_stem, args, kwargs); - PyObject* vector = detail::get_array(data); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, vector); + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); - PyObject* kwargs = PyDict_New(); - for (const auto& it : keywords) - { - PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); + return res; } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_boxplot, args, kwargs); - - Py_DECREF(args); - Py_DECREF(kwargs); + template + bool fill(const std::vector& x, const std::vector& y, + const std::map& keywords) + { + assert(x.size() == y.size()); - if(res) Py_DECREF(res); + detail::_interpreter::get(); - return res; -} + // using numpy arrays + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); -template -bool bar(const std::vector & x, - const std::vector & y, - std::string ec = "black", - std::string ls = "-", - double lw = 1.0, - const std::map & keywords = {}) -{ - detail::_interpreter::get(); + // construct positional args + PyObject* args = PyTuple_New(2); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); - PyObject * xarray = detail::get_array(x); - PyObject * yarray = detail::get_array(y); + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyUnicode_FromString(it->second.c_str())); + } - PyObject * kwargs = PyDict_New(); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_fill, args, kwargs); - PyDict_SetItemString(kwargs, "ec", PyString_FromString(ec.c_str())); - PyDict_SetItemString(kwargs, "ls", PyString_FromString(ls.c_str())); - PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); + Py_DECREF(args); + Py_DECREF(kwargs); - for (std::map::const_iterator it = - keywords.begin(); - it != keywords.end(); - ++it) { - PyDict_SetItemString( - kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); - } + if(res) Py_DECREF(res); - PyObject * plot_args = PyTuple_New(2); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); + return res; + } - PyObject * res = PyObject_Call( - detail::_interpreter::get().s_python_function_bar, plot_args, kwargs); + template + bool fill_between(const std::vector& x, + const std::vector& y1, + const std::vector& y2, + const std::map& keywords) + { + assert(x.size() == y1.size()); + assert(x.size() == y2.size()); - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if (res) Py_DECREF(res); + detail::_interpreter::get(); - return res; -} + // using numpy arrays + PyObject* xarray = detail::get_array(x); + PyObject* y1array = detail::get_array(y1); + PyObject* y2array = detail::get_array(y2); -template -bool bar(const std::vector & y, - std::string ec = "black", - std::string ls = "-", - double lw = 1.0, - const std::map & keywords = {}) -{ - using T = typename std::remove_reference::type::value_type; + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, y1array); + PyTuple_SetItem(args, 2, y2array); - detail::_interpreter::get(); + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyUnicode_FromString(it->second.c_str())); + } - std::vector x; - for (std::size_t i = 0; i < y.size(); i++) { x.push_back(i); } + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_fill_between, args, + kwargs); - return bar(x, y, ec, ls, lw, keywords); -} + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + return res; + } -template -bool barh(const std::vector &x, const std::vector &y, std::string ec = "black", std::string ls = "-", double lw = 1.0, const std::map &keywords = { }) { - PyObject *xarray = detail::get_array(x); - PyObject *yarray = detail::get_array(y); + template + bool arrow(Numeric x, Numeric y, Numeric end_x, Numeric end_y, + const std::string& fc = "r", const std::string ec = "k", + Numeric head_length = 0.25, Numeric head_width = 0.1625) + { + PyObject* obj_x = PyFloat_FromDouble(x); + PyObject* obj_y = PyFloat_FromDouble(y); + PyObject* obj_end_x = PyFloat_FromDouble(end_x); + PyObject* obj_end_y = PyFloat_FromDouble(end_y); - PyObject *kwargs = PyDict_New(); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "fc", PyString_FromString(fc.c_str())); + PyDict_SetItemString(kwargs, "ec", PyString_FromString(ec.c_str())); + PyDict_SetItemString(kwargs, "head_width", + PyFloat_FromDouble(head_width)); + PyDict_SetItemString(kwargs, "head_length", + PyFloat_FromDouble(head_length)); + + PyObject* plot_args = PyTuple_New(4); + PyTuple_SetItem(plot_args, 0, obj_x); + PyTuple_SetItem(plot_args, 1, obj_y); + PyTuple_SetItem(plot_args, 2, obj_end_x); + PyTuple_SetItem(plot_args, 3, obj_end_y); + + PyObject* res + = PyObject_Call(detail::_interpreter::get().s_python_function_arrow, + plot_args, kwargs); - PyDict_SetItemString(kwargs, "ec", PyString_FromString(ec.c_str())); - PyDict_SetItemString(kwargs, "ls", PyString_FromString(ls.c_str())); - PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); - for (std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + return res; } - PyObject *plot_args = PyTuple_New(2); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); + template + bool hist(const std::vector& y, long bins = 10, + std::string color = "b", double alpha = 1.0, + bool cumulative = false) + { + detail::_interpreter::get(); - PyObject *res = PyObject_Call(detail::_interpreter::get().s_python_function_barh, plot_args, kwargs); + PyObject* yarray = detail::get_array(y); - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if (res) Py_DECREF(res); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "bins", PyLong_FromLong(bins)); + PyDict_SetItemString(kwargs, "color", + PyString_FromString(color.c_str())); + PyDict_SetItemString(kwargs, "alpha", PyFloat_FromDouble(alpha)); + PyDict_SetItemString(kwargs, "cumulative", + cumulative ? Py_True : Py_False); - return res; -} + PyObject* plot_args = PyTuple_New(1); + PyTuple_SetItem(plot_args, 0, yarray); -inline bool subplots_adjust(const std::map& keywords = {}) -{ - detail::_interpreter::get(); + PyObject* res + = PyObject_Call(detail::_interpreter::get().s_python_function_hist, + plot_args, kwargs); - PyObject* kwargs = PyDict_New(); - for (std::map::const_iterator it = - keywords.begin(); it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), - PyFloat_FromDouble(it->second)); + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + + return res; } +#ifndef WITHOUT_NUMPY + namespace detail { - PyObject* plot_args = PyTuple_New(0); + inline void imshow(void* ptr, const NPY_TYPES type, const int rows, + const int columns, const int colors, + const std::map& keywords, + PyObject** out) + { + assert(type == NPY_UINT8 || type == NPY_FLOAT); + assert(colors == 1 || colors == 3 || colors == 4); + + detail::_interpreter::get(); + + // construct args + npy_intp dims[3] = {rows, columns, colors}; + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, + PyArray_SimpleNewFromData(colors == 1 ? 2 : 3, dims, + type, ptr)); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_imshow, args, + kwargs); + Py_DECREF(args); + Py_DECREF(kwargs); + if(!res) throw std::runtime_error("Call to imshow() failed"); + if(out) + *out = res; + else + Py_DECREF(res); + } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_subplots_adjust, plot_args, kwargs); + } // namespace detail - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if(res) Py_DECREF(res); + inline void imshow(const unsigned char* ptr, const int rows, + const int columns, const int colors, + const std::map& keywords = {}, + PyObject** out = nullptr) + { + detail::imshow((void*)ptr, NPY_UINT8, rows, columns, colors, keywords, + out); + } - return res; -} + inline void imshow(const float* ptr, const int rows, const int columns, + const int colors, + const std::map& keywords = {}, + PyObject** out = nullptr) + { + detail::imshow((void*)ptr, NPY_FLOAT, rows, columns, colors, keywords, + out); + } + +#ifdef WITH_OPENCV + void imshow(const cv::Mat& image, + const std::map& keywords = {}) + { + // Convert underlying type of matrix, if needed + cv::Mat image2; + NPY_TYPES npy_type = NPY_UINT8; + switch(image.type() & CV_MAT_DEPTH_MASK) { + case CV_8U: + image2 = image; + break; + case CV_32F: + image2 = image; + npy_type = NPY_FLOAT; + break; + default: + image.convertTo(image2, CV_MAKETYPE(CV_8U, image.channels())); + } + + // If color image, convert from BGR to RGB + switch(image2.channels()) { + case 3: + cv::cvtColor(image2, image2, CV_BGR2RGB); + break; + case 4: + cv::cvtColor(image2, image2, CV_BGRA2RGBA); + } + + detail::imshow(image2.data, npy_type, image2.rows, image2.cols, + image2.channels(), keywords); + } +#endif // WITH_OPENCV +#endif // WITHOUT_NUMPY + + template + bool scatter(const std::vector& x, const std::vector& y, + const double s = 1.0, // The marker size in points**2 + const std::map& keywords = {}) + { + detail::_interpreter::get(); + + assert(x.size() == y.size()); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "s", PyLong_FromLong(s)); + for(const auto& it: keywords) { + PyDict_SetItemString(kwargs, it.first.c_str(), + PyString_FromString(it.second.c_str())); + } + + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); -template< typename Numeric> -bool named_hist(std::string label,const std::vector& y, long bins=10, std::string color="b", double alpha=1.0) -{ - detail::_interpreter::get(); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_scatter, plot_args, + kwargs); - PyObject* yarray = detail::get_array(y); + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "label", PyString_FromString(label.c_str())); - PyDict_SetItemString(kwargs, "bins", PyLong_FromLong(bins)); - PyDict_SetItemString(kwargs, "color", PyString_FromString(color.c_str())); - PyDict_SetItemString(kwargs, "alpha", PyFloat_FromDouble(alpha)); + return res; + } + template bool + scatter_colored(const std::vector& x, + const std::vector& y, + const std::vector& colors, + const double s = 1.0, // The marker size in points**2 + const std::map& keywords = {}) + { + detail::_interpreter::get(); - PyObject* plot_args = PyTuple_New(1); - PyTuple_SetItem(plot_args, 0, yarray); + assert(x.size() == y.size()); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_hist, plot_args, kwargs); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + PyObject* colors_array = detail::get_array(colors); - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if(res) Py_DECREF(res); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "s", PyLong_FromLong(s)); + PyDict_SetItemString(kwargs, "c", colors_array); - return res; -} + for(const auto& it: keywords) { + PyDict_SetItemString(kwargs, it.first.c_str(), + PyString_FromString(it.second.c_str())); + } -template -bool contour(const std::vector& x, const std::vector& y, - const std::vector& z, - const std::map& keywords = {}) { - assert(x.size() == y.size() && x.size() == z.size()); + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); - PyObject* zarray = detail::get_array(z); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_scatter, plot_args, + kwargs); - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, zarray); + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); - // construct keyword args - PyObject* kwargs = PyDict_New(); - for (std::map::const_iterator it = keywords.begin(); - it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + return res; } - PyObject* res = - PyObject_Call(detail::_interpreter::get().s_python_function_contour, plot_args, kwargs); + template + bool scatter(const std::vector& x, const std::vector& y, + const std::vector& z, + const double s = 1.0, // The marker size in points**2 + const std::map& keywords = {}, + const long fig_number = 0) + { + detail::_interpreter::get(); - Py_DECREF(kwargs); - Py_DECREF(plot_args); - if (res) - Py_DECREF(res); + // Same as with plot_surface: We lazily load the modules here the first + // time this function is called because I'm not sure that we can assume + // "matplotlib installed" implies "mpl_toolkits installed" on all + // platforms, and we don't want to require it for people who don't need + // 3d plots. + static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; + if(!mpl_toolkitsmod) { + detail::_interpreter::get(); + + PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); + PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); + if(!mpl_toolkits || !axis3d) { + throw std::runtime_error("couldnt create string"); + } + + mpl_toolkitsmod = PyImport_Import(mpl_toolkits); + Py_DECREF(mpl_toolkits); + if(!mpl_toolkitsmod) { + throw std::runtime_error("Error loading module mpl_toolkits!"); + } + + axis3dmod = PyImport_Import(axis3d); + Py_DECREF(axis3d); + if(!axis3dmod) { + throw std::runtime_error( + "Error loading module mpl_toolkits.mplot3d!"); + } + } - return res; -} + assert(x.size() == y.size()); + assert(y.size() == z.size()); -template -bool quiver(const std::vector& x, const std::vector& y, const std::vector& u, const std::vector& w, const std::map& keywords = {}) -{ - assert(x.size() == y.size() && x.size() == u.size() && u.size() == w.size()); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + PyObject* zarray = detail::get_array(z); - detail::_interpreter::get(); + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + PyTuple_SetItem(args, 2, zarray); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); - PyObject* uarray = detail::get_array(u); - PyObject* warray = detail::get_array(w); + // Build up the kw args. + PyObject* kwargs = PyDict_New(); - PyObject* plot_args = PyTuple_New(4); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, uarray); - PyTuple_SetItem(plot_args, 3, warray); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } + PyObject* fig_args = PyTuple_New(1); + PyObject* fig = nullptr; + PyTuple_SetItem(fig_args, 0, PyLong_FromLong(fig_number)); + PyObject* fig_exists = PyObject_CallObject( + detail::_interpreter::get().s_python_function_fignum_exists, + fig_args); + if(!PyObject_IsTrue(fig_exists)) { + fig = PyObject_CallObject( + detail::_interpreter::get().s_python_function_figure, + detail::_interpreter::get().s_python_empty_tuple); + } + else { + fig = PyObject_CallObject( + detail::_interpreter::get().s_python_function_figure, fig_args); + } + Py_DECREF(fig_exists); + if(!fig) throw std::runtime_error("Call to figure() failed."); - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + PyObject* gca_kwargs = PyDict_New(); + PyDict_SetItemString(gca_kwargs, "projection", + PyString_FromString("3d")); + + PyObject* gca = PyObject_GetAttrString(fig, "gca"); + if(!gca) throw std::runtime_error("No gca"); + Py_INCREF(gca); + PyObject* axis = PyObject_Call( + gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); + + if(!axis) throw std::runtime_error("No axis"); + Py_INCREF(axis); + + Py_DECREF(gca); + Py_DECREF(gca_kwargs); + + PyObject* plot3 = PyObject_GetAttrString(axis, "scatter"); + if(!plot3) throw std::runtime_error("No 3D line plot"); + Py_INCREF(plot3); + PyObject* res = PyObject_Call(plot3, args, kwargs); + if(!res) throw std::runtime_error("Failed 3D line plot"); + Py_DECREF(plot3); + + Py_DECREF(axis); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(fig); + if(res) Py_DECREF(res); + return res; + } + + template + bool boxplot(const std::vector>& data, + const std::vector& labels = {}, + const std::map& keywords = {}) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + detail::_interpreter::get(); + + PyObject* listlist = detail::get_listlist(data); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, listlist); + + PyObject* kwargs = PyDict_New(); + + // kwargs needs the labels, if there are (the correct number of) labels + if(!labels.empty() && labels.size() == data.size()) { + PyDict_SetItemString(kwargs, "labels", detail::get_array(labels)); + } + + // take care of the remaining keywords + for(const auto& it: keywords) { + PyDict_SetItemString(kwargs, it.first.c_str(), + PyString_FromString(it.second.c_str())); + } + + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_boxplot, args, + kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + + if(res) Py_DECREF(res); + + return res; } - PyObject* res = PyObject_Call( - detail::_interpreter::get().s_python_function_quiver, plot_args, kwargs); + template + bool boxplot(const std::vector& data, + const std::map& keywords = {}) + { + detail::_interpreter::get(); - Py_DECREF(kwargs); - Py_DECREF(plot_args); - if (res) - Py_DECREF(res); + PyObject* vector = detail::get_array(data); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, vector); - return res; -} - -template -bool quiver(const std::vector& x, const std::vector& y, const std::vector& z, const std::vector& u, const std::vector& w, const std::vector& v, const std::map& keywords = {}) -{ - //set up 3d axes stuff - static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; - if (!mpl_toolkitsmod) { - detail::_interpreter::get(); - - PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); - PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); - if (!mpl_toolkits || !axis3d) { throw std::runtime_error("couldnt create string"); } - - mpl_toolkitsmod = PyImport_Import(mpl_toolkits); - Py_DECREF(mpl_toolkits); - if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } - - axis3dmod = PyImport_Import(axis3d); - Py_DECREF(axis3d); - if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } - } - - //assert sizes match up - assert(x.size() == y.size() && x.size() == u.size() && u.size() == w.size() && x.size() == z.size() && x.size() == v.size() && u.size() == v.size()); - - //set up parameters - detail::_interpreter::get(); - - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); - PyObject* zarray = detail::get_array(z); - PyObject* uarray = detail::get_array(u); - PyObject* warray = detail::get_array(w); - PyObject* varray = detail::get_array(v); - - PyObject* plot_args = PyTuple_New(6); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, zarray); - PyTuple_SetItem(plot_args, 3, uarray); - PyTuple_SetItem(plot_args, 4, warray); - PyTuple_SetItem(plot_args, 5, varray); - - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) - { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); - } - - //get figure gca to enable 3d projection - PyObject *fig = - PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, - detail::_interpreter::get().s_python_empty_tuple); - if (!fig) throw std::runtime_error("Call to figure() failed."); - - PyObject *gca_kwargs = PyDict_New(); - PyDict_SetItemString(gca_kwargs, "projection", PyString_FromString("3d")); - - PyObject *gca = PyObject_GetAttrString(fig, "gca"); - if (!gca) throw std::runtime_error("No gca"); - Py_INCREF(gca); - PyObject *axis = PyObject_Call( - gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); - - if (!axis) throw std::runtime_error("No axis"); - Py_INCREF(axis); - Py_DECREF(gca); - Py_DECREF(gca_kwargs); - - //plot our boys bravely, plot them strongly, plot them with a wink and clap - PyObject *plot3 = PyObject_GetAttrString(axis, "quiver"); - if (!plot3) throw std::runtime_error("No 3D line plot"); - Py_INCREF(plot3); - PyObject* res = PyObject_Call( - plot3, plot_args, kwargs); - if (!res) throw std::runtime_error("Failed 3D plot"); - Py_DECREF(plot3); - Py_DECREF(axis); - Py_DECREF(kwargs); - Py_DECREF(plot_args); - if (res) - Py_DECREF(res); - - return res; -} - -template -bool stem(const std::vector& x, const std::vector& y, const std::string& s = "") -{ - assert(x.size() == y.size()); - - detail::_interpreter::get(); - - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); - - PyObject* pystring = PyString_FromString(s.c_str()); - - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); - - PyObject* res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_stem, plot_args); + PyObject* kwargs = PyDict_New(); + for(const auto& it: keywords) { + PyDict_SetItemString(kwargs, it.first.c_str(), + PyString_FromString(it.second.c_str())); + } - Py_DECREF(plot_args); - if (res) - Py_DECREF(res); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_boxplot, args, + kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + + if(res) Py_DECREF(res); + + return res; + } + + template + bool bar(const std::vector& x, const std::vector& y, + std::string ec = "black", std::string ls = "-", double lw = 1.0, + const std::map& keywords = {}) + { + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* kwargs = PyDict_New(); + + PyDict_SetItemString(kwargs, "ec", PyString_FromString(ec.c_str())); + PyDict_SetItemString(kwargs, "ls", PyString_FromString(ls.c_str())); + PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); + + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyUnicode_FromString(it->second.c_str())); + } + + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + + PyObject* res + = PyObject_Call(detail::_interpreter::get().s_python_function_bar, + plot_args, kwargs); + + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + + return res; + } + + template + bool bar(const std::vector& y, std::string ec = "black", + std::string ls = "-", double lw = 1.0, + const std::map& keywords = {}) + { + using T = typename std::remove_reference::type::value_type; + + detail::_interpreter::get(); - return res; -} + std::vector x; + for(std::size_t i = 0; i < y.size(); i++) { x.push_back(i); } -template -bool semilogx(const std::vector& x, const std::vector& y, const std::string& s = "") -{ - assert(x.size() == y.size()); + return bar(x, y, ec, ls, lw, keywords); + } + + template + bool barh(const std::vector& x, const std::vector& y, + std::string ec = "black", std::string ls = "-", double lw = 1.0, + const std::map& keywords = {}) + { + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* kwargs = PyDict_New(); + + PyDict_SetItemString(kwargs, "ec", PyString_FromString(ec.c_str())); + PyDict_SetItemString(kwargs, "ls", PyString_FromString(ls.c_str())); + PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); + + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyUnicode_FromString(it->second.c_str())); + } + + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + + PyObject* res + = PyObject_Call(detail::_interpreter::get().s_python_function_barh, + plot_args, kwargs); + + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + + return res; + } + + inline bool subplots_adjust(const std::map& keywords + = {}) + { + detail::_interpreter::get(); + + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyFloat_FromDouble(it->second)); + } + + PyObject* plot_args = PyTuple_New(0); + + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_subplots_adjust, + plot_args, kwargs); + + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + + return res; + } - detail::_interpreter::get(); + template + bool named_hist(std::string label, const std::vector& y, + long bins = 10, std::string color = "b", double alpha = 1.0) + { + detail::_interpreter::get(); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + PyObject* yarray = detail::get_array(y); - PyObject* pystring = PyString_FromString(s.c_str()); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", + PyString_FromString(label.c_str())); + PyDict_SetItemString(kwargs, "bins", PyLong_FromLong(bins)); + PyDict_SetItemString(kwargs, "color", + PyString_FromString(color.c_str())); + PyDict_SetItemString(kwargs, "alpha", PyFloat_FromDouble(alpha)); - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + PyObject* plot_args = PyTuple_New(1); + PyTuple_SetItem(plot_args, 0, yarray); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_semilogx, plot_args); + PyObject* res + = PyObject_Call(detail::_interpreter::get().s_python_function_hist, + plot_args, kwargs); - Py_DECREF(plot_args); - if(res) Py_DECREF(res); + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); - return res; -} + return res; + } + + template + bool contour(const std::vector& x, const std::vector& y, + const std::vector& z, + const std::map& keywords = {}) + { + assert(x.size() == y.size() && x.size() == z.size()); -template -bool semilogy(const std::vector& x, const std::vector& y, const std::string& s = "") -{ - assert(x.size() == y.size()); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + PyObject* zarray = detail::get_array(z); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, zarray); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_contour, plot_args, + kwargs); + + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; + } + + template + bool quiver(const std::vector& x, const std::vector& y, + const std::vector& u, const std::vector& w, + const std::map& keywords = {}) + { + assert(x.size() == y.size() && x.size() == u.size() + && u.size() == w.size()); + + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + PyObject* uarray = detail::get_array(u); + PyObject* warray = detail::get_array(w); + + PyObject* plot_args = PyTuple_New(4); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, uarray); + PyTuple_SetItem(plot_args, 3, warray); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_quiver, plot_args, + kwargs); + + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; + } + + template + bool quiver(const std::vector& x, const std::vector& y, + const std::vector& z, const std::vector& u, + const std::vector& w, const std::vector& v, + const std::map& keywords = {}) + { + // set up 3d axes stuff + static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; + if(!mpl_toolkitsmod) { + detail::_interpreter::get(); + + PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); + PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); + if(!mpl_toolkits || !axis3d) { + throw std::runtime_error("couldnt create string"); + } + + mpl_toolkitsmod = PyImport_Import(mpl_toolkits); + Py_DECREF(mpl_toolkits); + if(!mpl_toolkitsmod) { + throw std::runtime_error("Error loading module mpl_toolkits!"); + } + + axis3dmod = PyImport_Import(axis3d); + Py_DECREF(axis3d); + if(!axis3dmod) { + throw std::runtime_error( + "Error loading module mpl_toolkits.mplot3d!"); + } + } + + // assert sizes match up + assert(x.size() == y.size() && x.size() == u.size() + && u.size() == w.size() && x.size() == z.size() + && x.size() == v.size() && u.size() == v.size()); + + // set up parameters + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + PyObject* zarray = detail::get_array(z); + PyObject* uarray = detail::get_array(u); + PyObject* warray = detail::get_array(w); + PyObject* varray = detail::get_array(v); + + PyObject* plot_args = PyTuple_New(6); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, zarray); + PyTuple_SetItem(plot_args, 3, uarray); + PyTuple_SetItem(plot_args, 4, warray); + PyTuple_SetItem(plot_args, 5, varray); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyUnicode_FromString(it->second.c_str())); + } + + // get figure gca to enable 3d projection + PyObject* fig = PyObject_CallObject( + detail::_interpreter::get().s_python_function_figure, + detail::_interpreter::get().s_python_empty_tuple); + if(!fig) throw std::runtime_error("Call to figure() failed."); + + PyObject* gca_kwargs = PyDict_New(); + PyDict_SetItemString(gca_kwargs, "projection", + PyString_FromString("3d")); + + PyObject* gca = PyObject_GetAttrString(fig, "gca"); + if(!gca) throw std::runtime_error("No gca"); + Py_INCREF(gca); + PyObject* axis = PyObject_Call( + gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); + + if(!axis) throw std::runtime_error("No axis"); + Py_INCREF(axis); + Py_DECREF(gca); + Py_DECREF(gca_kwargs); + + // plot our boys bravely, plot them strongly, plot them with a wink and + // clap + PyObject* plot3 = PyObject_GetAttrString(axis, "quiver"); + if(!plot3) throw std::runtime_error("No 3D line plot"); + Py_INCREF(plot3); + PyObject* res = PyObject_Call(plot3, plot_args, kwargs); + if(!res) throw std::runtime_error("Failed 3D plot"); + Py_DECREF(plot3); + Py_DECREF(axis); + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; + } + + template + bool stem(const std::vector& x, const std::vector& y, + const std::string& s = "") + { + assert(x.size() == y.size()); + + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(s.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_stem, plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; + } + + template + bool semilogx(const std::vector& x, + const std::vector& y, const std::string& s = "") + { + assert(x.size() == y.size()); + + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(s.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - detail::_interpreter::get(); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_semilogx, plot_args); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - PyObject* pystring = PyString_FromString(s.c_str()); + return res; + } - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + template + bool semilogy(const std::vector& x, + const std::vector& y, const std::string& s = "") + { + assert(x.size() == y.size()); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_semilogy, plot_args); + detail::_interpreter::get(); - Py_DECREF(plot_args); - if(res) Py_DECREF(res); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - return res; -} + PyObject* pystring = PyString_FromString(s.c_str()); -template -bool loglog(const std::vector& x, const std::vector& y, const std::string& s = "") -{ - assert(x.size() == y.size()); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - detail::_interpreter::get(); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_semilogy, plot_args); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - PyObject* pystring = PyString_FromString(s.c_str()); + return res; + } - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + template + bool loglog(const std::vector& x, const std::vector& y, + const std::string& s = "") + { + assert(x.size() == y.size()); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_loglog, plot_args); + detail::_interpreter::get(); - Py_DECREF(plot_args); - if(res) Py_DECREF(res); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - return res; -} + PyObject* pystring = PyString_FromString(s.c_str()); -template -bool errorbar(const std::vector &x, const std::vector &y, const std::vector &yerr, const std::map &keywords = {}) -{ - assert(x.size() == y.size()); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - detail::_interpreter::get(); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_loglog, plot_args); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); - PyObject* yerrarray = detail::get_array(yerr); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) - { - PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + return res; } - PyDict_SetItemString(kwargs, "yerr", yerrarray); + template + bool errorbar(const std::vector& x, + const std::vector& y, + const std::vector& yerr, + const std::map& keywords = {}) + { + assert(x.size() == y.size()); - PyObject *plot_args = PyTuple_New(2); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); + detail::_interpreter::get(); - PyObject *res = PyObject_Call(detail::_interpreter::get().s_python_function_errorbar, plot_args, kwargs); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + PyObject* yerrarray = detail::get_array(yerr); - Py_DECREF(kwargs); - Py_DECREF(plot_args); + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } - if (res) - Py_DECREF(res); - else - throw std::runtime_error("Call to errorbar() failed."); + PyDict_SetItemString(kwargs, "yerr", yerrarray); - return res; -} + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); -template -bool named_plot(const std::string& name, const std::vector& y, const std::string& format = "") -{ - detail::_interpreter::get(); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_errorbar, plot_args, + kwargs); - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + Py_DECREF(kwargs); + Py_DECREF(plot_args); - PyObject* yarray = detail::get_array(y); + if(res) + Py_DECREF(res); + else + throw std::runtime_error("Call to errorbar() failed."); - PyObject* pystring = PyString_FromString(format.c_str()); + return res; + } - PyObject* plot_args = PyTuple_New(2); + template bool named_plot(const std::string& name, + const std::vector& y, + const std::string& format = "") + { + detail::_interpreter::get(); - PyTuple_SetItem(plot_args, 0, yarray); - PyTuple_SetItem(plot_args, 1, pystring); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", + PyString_FromString(name.c_str())); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, plot_args, kwargs); + PyObject* yarray = detail::get_array(y); - Py_DECREF(kwargs); - Py_DECREF(plot_args); - if (res) Py_DECREF(res); + PyObject* pystring = PyString_FromString(format.c_str()); - return res; -} + PyObject* plot_args = PyTuple_New(2); -template -bool named_plot(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") -{ - detail::_interpreter::get(); + PyTuple_SetItem(plot_args, 0, yarray); + PyTuple_SetItem(plot_args, 1, pystring); - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + PyObject* res + = PyObject_Call(detail::_interpreter::get().s_python_function_plot, + plot_args, kwargs); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - PyObject* pystring = PyString_FromString(format.c_str()); + return res; + } - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + template + bool named_plot(const std::string& name, const std::vector& x, + const std::vector& y, + const std::string& format = "") + { + detail::_interpreter::get(); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, plot_args, kwargs); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", + PyString_FromString(name.c_str())); - Py_DECREF(kwargs); - Py_DECREF(plot_args); - if (res) Py_DECREF(res); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - return res; -} + PyObject* pystring = PyString_FromString(format.c_str()); -template -bool named_semilogx(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") -{ - detail::_interpreter::get(); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + PyObject* res + = PyObject_Call(detail::_interpreter::get().s_python_function_plot, + plot_args, kwargs); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - PyObject* pystring = PyString_FromString(format.c_str()); + return res; + } - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + template + bool named_semilogx(const std::string& name, const std::vector& x, + const std::vector& y, + const std::string& format = "") + { + detail::_interpreter::get(); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_semilogx, plot_args, kwargs); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", + PyString_FromString(name.c_str())); - Py_DECREF(kwargs); - Py_DECREF(plot_args); - if (res) Py_DECREF(res); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - return res; -} + PyObject* pystring = PyString_FromString(format.c_str()); -template -bool named_semilogy(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") -{ - detail::_interpreter::get(); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_semilogx, plot_args, + kwargs); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - PyObject* pystring = PyString_FromString(format.c_str()); + return res; + } - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + template + bool named_semilogy(const std::string& name, const std::vector& x, + const std::vector& y, + const std::string& format = "") + { + detail::_interpreter::get(); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_semilogy, plot_args, kwargs); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", + PyString_FromString(name.c_str())); - Py_DECREF(kwargs); - Py_DECREF(plot_args); - if (res) Py_DECREF(res); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - return res; -} + PyObject* pystring = PyString_FromString(format.c_str()); -template -bool named_loglog(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") -{ - detail::_interpreter::get(); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_semilogy, plot_args, + kwargs); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - PyObject* pystring = PyString_FromString(format.c_str()); + return res; + } - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_loglog, plot_args, kwargs); + template + bool named_loglog(const std::string& name, const std::vector& x, + const std::vector& y, + const std::string& format = "") + { + detail::_interpreter::get(); - Py_DECREF(kwargs); - Py_DECREF(plot_args); - if (res) Py_DECREF(res); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", + PyString_FromString(name.c_str())); - return res; -} + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); -template -bool stem(const std::vector& y, const std::string& format = "") -{ - std::vector x(y.size()); - for (size_t i = 0; i < x.size(); ++i) x.at(i) = i; - return stem(x, y, format); -} + PyObject* pystring = PyString_FromString(format.c_str()); -template -void text(Numeric x, Numeric y, const std::string& s = "") -{ - detail::_interpreter::get(); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_loglog, plot_args, + kwargs); - PyObject* args = PyTuple_New(3); - PyTuple_SetItem(args, 0, PyFloat_FromDouble(x)); - PyTuple_SetItem(args, 1, PyFloat_FromDouble(y)); - PyTuple_SetItem(args, 2, PyString_FromString(s.c_str())); + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_text, args); - if(!res) throw std::runtime_error("Call to text() failed."); + return res; + } - Py_DECREF(args); - Py_DECREF(res); -} + template + bool stem(const std::vector& y, const std::string& format = "") + { + std::vector x(y.size()); + for(size_t i = 0; i < x.size(); ++i) x.at(i) = i; + return stem(x, y, format); + } -inline void colorbar(PyObject* mappable = NULL, const std::map& keywords = {}) -{ - if (mappable == NULL) - throw std::runtime_error("Must call colorbar with PyObject* returned from an image, contour, surface, etc."); + template + void text(Numeric x, Numeric y, const std::string& s = "") + { + detail::_interpreter::get(); - detail::_interpreter::get(); + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(x)); + PyTuple_SetItem(args, 1, PyFloat_FromDouble(y)); + PyTuple_SetItem(args, 2, PyString_FromString(s.c_str())); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, mappable); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_text, args); + if(!res) throw std::runtime_error("Call to text() failed."); - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) - { - PyDict_SetItemString(kwargs, it->first.c_str(), PyFloat_FromDouble(it->second)); + Py_DECREF(args); + Py_DECREF(res); } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_colorbar, args, kwargs); - if(!res) throw std::runtime_error("Call to colorbar() failed."); - - Py_DECREF(args); - Py_DECREF(kwargs); - Py_DECREF(res); -} + inline void colorbar(PyObject* mappable = NULL, + const std::map& keywords = {}) + { + if(mappable == NULL) + throw std::runtime_error( + "Must call colorbar with PyObject* returned from an image, " + "contour, surface, etc."); + detail::_interpreter::get(); -inline long figure(long number = -1) -{ - detail::_interpreter::get(); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, mappable); - PyObject *res; - if (number == -1) - res = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, detail::_interpreter::get().s_python_empty_tuple); - else { - assert(number > 0); + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyFloat_FromDouble(it->second)); + } - // Make sure interpreter is initialised - detail::_interpreter::get(); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_colorbar, args, + kwargs); + if(!res) throw std::runtime_error("Call to colorbar() failed."); - PyObject *args = PyTuple_New(1); - PyTuple_SetItem(args, 0, PyLong_FromLong(number)); - res = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, args); Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); } - if(!res) throw std::runtime_error("Call to figure() failed."); + inline long figure(long number = -1) + { + detail::_interpreter::get(); + + PyObject* res; + if(number == -1) + res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_figure, + detail::_interpreter::get().s_python_empty_tuple); + else { + assert(number > 0); - PyObject* num = PyObject_GetAttrString(res, "number"); - if (!num) throw std::runtime_error("Could not get number attribute of figure object"); - const long figureNumber = PyLong_AsLong(num); + // Make sure interpreter is initialised + detail::_interpreter::get(); - Py_DECREF(num); - Py_DECREF(res); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyLong_FromLong(number)); + res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_figure, args); + Py_DECREF(args); + } - return figureNumber; -} + if(!res) throw std::runtime_error("Call to figure() failed."); -inline bool fignum_exists(long number) -{ - detail::_interpreter::get(); + PyObject* num = PyObject_GetAttrString(res, "number"); + if(!num) + throw std::runtime_error( + "Could not get number attribute of figure object"); + const long figureNumber = PyLong_AsLong(num); - PyObject *args = PyTuple_New(1); - PyTuple_SetItem(args, 0, PyLong_FromLong(number)); - PyObject *res = PyObject_CallObject(detail::_interpreter::get().s_python_function_fignum_exists, args); - if(!res) throw std::runtime_error("Call to fignum_exists() failed."); + Py_DECREF(num); + Py_DECREF(res); - bool ret = PyObject_IsTrue(res); - Py_DECREF(res); - Py_DECREF(args); + return figureNumber; + } - return ret; -} + inline bool fignum_exists(long number) + { + detail::_interpreter::get(); -inline void figure_size(size_t w, size_t h) -{ - detail::_interpreter::get(); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyLong_FromLong(number)); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_fignum_exists, args); + if(!res) throw std::runtime_error("Call to fignum_exists() failed."); - const size_t dpi = 100; - PyObject* size = PyTuple_New(2); - PyTuple_SetItem(size, 0, PyFloat_FromDouble((double)w / dpi)); - PyTuple_SetItem(size, 1, PyFloat_FromDouble((double)h / dpi)); + bool ret = PyObject_IsTrue(res); + Py_DECREF(res); + Py_DECREF(args); - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "figsize", size); - PyDict_SetItemString(kwargs, "dpi", PyLong_FromSize_t(dpi)); + return ret; + } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_figure, - detail::_interpreter::get().s_python_empty_tuple, kwargs); + inline void figure_size(size_t w, size_t h) + { + detail::_interpreter::get(); - Py_DECREF(kwargs); + const size_t dpi = 100; + PyObject* size = PyTuple_New(2); + PyTuple_SetItem(size, 0, PyFloat_FromDouble((double)w / dpi)); + PyTuple_SetItem(size, 1, PyFloat_FromDouble((double)h / dpi)); - if(!res) throw std::runtime_error("Call to figure_size() failed."); - Py_DECREF(res); -} + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "figsize", size); + PyDict_SetItemString(kwargs, "dpi", PyLong_FromSize_t(dpi)); -inline void legend() -{ - detail::_interpreter::get(); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_figure, + detail::_interpreter::get().s_python_empty_tuple, kwargs); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_legend, detail::_interpreter::get().s_python_empty_tuple); - if(!res) throw std::runtime_error("Call to legend() failed."); - - Py_DECREF(res); -} - -inline void legend(const std::map& keywords) -{ - detail::_interpreter::get(); - - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) - { - PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); - } - - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_legend, detail::_interpreter::get().s_python_empty_tuple, kwargs); - if(!res) throw std::runtime_error("Call to legend() failed."); + Py_DECREF(kwargs); - Py_DECREF(kwargs); - Py_DECREF(res); -} + if(!res) throw std::runtime_error("Call to figure_size() failed."); + Py_DECREF(res); + } -template -inline void set_aspect(Numeric ratio) -{ - detail::_interpreter::get(); + inline void legend() + { + detail::_interpreter::get(); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, PyFloat_FromDouble(ratio)); - PyObject* kwargs = PyDict_New(); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_legend, + detail::_interpreter::get().s_python_empty_tuple); + if(!res) throw std::runtime_error("Call to legend() failed."); - PyObject *ax = - PyObject_CallObject(detail::_interpreter::get().s_python_function_gca, - detail::_interpreter::get().s_python_empty_tuple); - if (!ax) throw std::runtime_error("Call to gca() failed."); - Py_INCREF(ax); + Py_DECREF(res); + } - PyObject *set_aspect = PyObject_GetAttrString(ax, "set_aspect"); - if (!set_aspect) throw std::runtime_error("Attribute set_aspect not found."); - Py_INCREF(set_aspect); + inline void legend(const std::map& keywords) + { + detail::_interpreter::get(); - PyObject *res = PyObject_Call(set_aspect, args, kwargs); - if (!res) throw std::runtime_error("Call to set_aspect() failed."); - Py_DECREF(set_aspect); + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } - Py_DECREF(ax); - Py_DECREF(args); - Py_DECREF(kwargs); -} + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_legend, + detail::_interpreter::get().s_python_empty_tuple, kwargs); + if(!res) throw std::runtime_error("Call to legend() failed."); -inline void set_aspect_equal() -{ - // expect ratio == "equal". Leaving error handling to matplotlib. - detail::_interpreter::get(); + Py_DECREF(kwargs); + Py_DECREF(res); + } - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, PyString_FromString("equal")); - PyObject* kwargs = PyDict_New(); + template inline void set_aspect(Numeric ratio) + { + detail::_interpreter::get(); - PyObject *ax = - PyObject_CallObject(detail::_interpreter::get().s_python_function_gca, - detail::_interpreter::get().s_python_empty_tuple); - if (!ax) throw std::runtime_error("Call to gca() failed."); - Py_INCREF(ax); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(ratio)); + PyObject* kwargs = PyDict_New(); - PyObject *set_aspect = PyObject_GetAttrString(ax, "set_aspect"); - if (!set_aspect) throw std::runtime_error("Attribute set_aspect not found."); - Py_INCREF(set_aspect); + PyObject* ax = PyObject_CallObject( + detail::_interpreter::get().s_python_function_gca, + detail::_interpreter::get().s_python_empty_tuple); + if(!ax) throw std::runtime_error("Call to gca() failed."); + Py_INCREF(ax); - PyObject *res = PyObject_Call(set_aspect, args, kwargs); - if (!res) throw std::runtime_error("Call to set_aspect() failed."); - Py_DECREF(set_aspect); + PyObject* set_aspect = PyObject_GetAttrString(ax, "set_aspect"); + if(!set_aspect) + throw std::runtime_error("Attribute set_aspect not found."); + Py_INCREF(set_aspect); - Py_DECREF(ax); - Py_DECREF(args); - Py_DECREF(kwargs); -} + PyObject* res = PyObject_Call(set_aspect, args, kwargs); + if(!res) throw std::runtime_error("Call to set_aspect() failed."); + Py_DECREF(set_aspect); -template -void ylim(Numeric left, Numeric right) -{ - detail::_interpreter::get(); + Py_DECREF(ax); + Py_DECREF(args); + Py_DECREF(kwargs); + } - PyObject* list = PyList_New(2); - PyList_SetItem(list, 0, PyFloat_FromDouble(left)); - PyList_SetItem(list, 1, PyFloat_FromDouble(right)); + inline void set_aspect_equal() + { + // expect ratio == "equal". Leaving error handling to matplotlib. + detail::_interpreter::get(); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, list); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyString_FromString("equal")); + PyObject* kwargs = PyDict_New(); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_ylim, args); - if(!res) throw std::runtime_error("Call to ylim() failed."); + PyObject* ax = PyObject_CallObject( + detail::_interpreter::get().s_python_function_gca, + detail::_interpreter::get().s_python_empty_tuple); + if(!ax) throw std::runtime_error("Call to gca() failed."); + Py_INCREF(ax); - Py_DECREF(args); - Py_DECREF(res); -} + PyObject* set_aspect = PyObject_GetAttrString(ax, "set_aspect"); + if(!set_aspect) + throw std::runtime_error("Attribute set_aspect not found."); + Py_INCREF(set_aspect); -template -void xlim(Numeric left, Numeric right) -{ - detail::_interpreter::get(); + PyObject* res = PyObject_Call(set_aspect, args, kwargs); + if(!res) throw std::runtime_error("Call to set_aspect() failed."); + Py_DECREF(set_aspect); - PyObject* list = PyList_New(2); - PyList_SetItem(list, 0, PyFloat_FromDouble(left)); - PyList_SetItem(list, 1, PyFloat_FromDouble(right)); + Py_DECREF(ax); + Py_DECREF(args); + Py_DECREF(kwargs); + } - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, list); + template void ylim(Numeric left, Numeric right) + { + detail::_interpreter::get(); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_xlim, args); - if(!res) throw std::runtime_error("Call to xlim() failed."); + PyObject* list = PyList_New(2); + PyList_SetItem(list, 0, PyFloat_FromDouble(left)); + PyList_SetItem(list, 1, PyFloat_FromDouble(right)); - Py_DECREF(args); - Py_DECREF(res); -} + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, list); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_ylim, args); + if(!res) throw std::runtime_error("Call to ylim() failed."); -inline std::array xlim() -{ - PyObject* args = PyTuple_New(0); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_xlim, args); + Py_DECREF(args); + Py_DECREF(res); + } - if(!res) throw std::runtime_error("Call to xlim() failed."); + template void xlim(Numeric left, Numeric right) + { + detail::_interpreter::get(); - Py_DECREF(res); + PyObject* list = PyList_New(2); + PyList_SetItem(list, 0, PyFloat_FromDouble(left)); + PyList_SetItem(list, 1, PyFloat_FromDouble(right)); - PyObject* left = PyTuple_GetItem(res,0); - PyObject* right = PyTuple_GetItem(res,1); - return { PyFloat_AsDouble(left), PyFloat_AsDouble(right) }; -} + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, list); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_xlim, args); + if(!res) throw std::runtime_error("Call to xlim() failed."); -inline std::array ylim() -{ - PyObject* args = PyTuple_New(0); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_ylim, args); + Py_DECREF(args); + Py_DECREF(res); + } - if(!res) throw std::runtime_error("Call to ylim() failed."); + inline std::array xlim() + { + PyObject* args = PyTuple_New(0); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_xlim, args); - Py_DECREF(res); + if(!res) throw std::runtime_error("Call to xlim() failed."); - PyObject* left = PyTuple_GetItem(res,0); - PyObject* right = PyTuple_GetItem(res,1); - return { PyFloat_AsDouble(left), PyFloat_AsDouble(right) }; -} + Py_DECREF(res); -template -inline void xticks(const std::vector &ticks, const std::vector &labels = {}, const std::map& keywords = {}) -{ - assert(labels.size() == 0 || ticks.size() == labels.size()); + PyObject* left = PyTuple_GetItem(res, 0); + PyObject* right = PyTuple_GetItem(res, 1); + return {PyFloat_AsDouble(left), PyFloat_AsDouble(right)}; + } - detail::_interpreter::get(); + inline std::array ylim() + { + PyObject* args = PyTuple_New(0); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_ylim, args); - // using numpy array - PyObject* ticksarray = detail::get_array(ticks); + if(!res) throw std::runtime_error("Call to ylim() failed."); - PyObject* args; - if(labels.size() == 0) { - // construct positional args - args = PyTuple_New(1); - PyTuple_SetItem(args, 0, ticksarray); - } else { - // make tuple of tick labels - PyObject* labelstuple = PyTuple_New(labels.size()); - for (size_t i = 0; i < labels.size(); i++) - PyTuple_SetItem(labelstuple, i, PyUnicode_FromString(labels[i].c_str())); + Py_DECREF(res); - // construct positional args - args = PyTuple_New(2); - PyTuple_SetItem(args, 0, ticksarray); - PyTuple_SetItem(args, 1, labelstuple); + PyObject* left = PyTuple_GetItem(res, 0); + PyObject* right = PyTuple_GetItem(res, 1); + return {PyFloat_AsDouble(left), PyFloat_AsDouble(right)}; } - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + template + inline void xticks(const std::vector& ticks, + const std::vector& labels = {}, + const std::map& keywords = {}) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); - } + assert(labels.size() == 0 || ticks.size() == labels.size()); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_xticks, args, kwargs); - - Py_DECREF(args); - Py_DECREF(kwargs); - if(!res) throw std::runtime_error("Call to xticks() failed"); - - Py_DECREF(res); -} + detail::_interpreter::get(); -template -inline void xticks(const std::vector &ticks, const std::map& keywords) -{ - xticks(ticks, {}, keywords); -} + // using numpy array + PyObject* ticksarray = detail::get_array(ticks); -// options only, e.g. plt::xticks(rotation=20) -inline void xticks(const std::map& keywords) -{ - detail::_interpreter::get(); + PyObject* args; + if(labels.size() == 0) { + // construct positional args + args = PyTuple_New(1); + PyTuple_SetItem(args, 0, ticksarray); + } + else { + // make tuple of tick labels + PyObject* labelstuple = PyTuple_New(labels.size()); + for(size_t i = 0; i < labels.size(); i++) + PyTuple_SetItem(labelstuple, i, + PyUnicode_FromString(labels[i].c_str())); + + // construct positional args + args = PyTuple_New(2); + PyTuple_SetItem(args, 0, ticksarray); + PyTuple_SetItem(args, 1, labelstuple); + } - PyObject* args = PyTuple_New(0); + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } - // construct keyword args - PyObject* kwargs = PyDict_New(); - for (const auto& [k, v] : keywords) - PyDict_SetItemString(kwargs, k.c_str(), PyString_FromString(v.c_str())); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_xticks, args, kwargs); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_xticks, args, kwargs); + Py_DECREF(args); + Py_DECREF(kwargs); + if(!res) throw std::runtime_error("Call to xticks() failed"); - Py_DECREF(kwargs); - if(!res) throw std::runtime_error("Call to xticks() failed"); + Py_DECREF(res); + } - Py_DECREF(res); -} + template + inline void xticks(const std::vector& ticks, + const std::map& keywords) + { + xticks(ticks, {}, keywords); + } + // options only, e.g. plt::xticks(rotation=20) + inline void xticks(const std::map& keywords) + { + detail::_interpreter::get(); -template -inline void yticks(const std::vector &ticks, const std::vector &labels = {}, const std::map& keywords = {}) -{ - assert(labels.size() == 0 || ticks.size() == labels.size()); + PyObject* args = PyTuple_New(0); - detail::_interpreter::get(); + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(const auto& [k, v]: keywords) + PyDict_SetItemString(kwargs, k.c_str(), + PyString_FromString(v.c_str())); - // using numpy array - PyObject* ticksarray = detail::get_array(ticks); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_xticks, args, kwargs); - PyObject* args; - if(labels.size() == 0) { - // construct positional args - args = PyTuple_New(1); - PyTuple_SetItem(args, 0, ticksarray); - } else { - // make tuple of tick labels - PyObject* labelstuple = PyTuple_New(labels.size()); - for (size_t i = 0; i < labels.size(); i++) - PyTuple_SetItem(labelstuple, i, PyUnicode_FromString(labels[i].c_str())); + Py_DECREF(kwargs); + if(!res) throw std::runtime_error("Call to xticks() failed"); - // construct positional args - args = PyTuple_New(2); - PyTuple_SetItem(args, 0, ticksarray); - PyTuple_SetItem(args, 1, labelstuple); + Py_DECREF(res); } - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + template + inline void yticks(const std::vector& ticks, + const std::vector& labels = {}, + const std::map& keywords = {}) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); - } + assert(labels.size() == 0 || ticks.size() == labels.size()); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_yticks, args, kwargs); - - Py_DECREF(args); - Py_DECREF(kwargs); - if(!res) throw std::runtime_error("Call to yticks() failed"); + detail::_interpreter::get(); - Py_DECREF(res); -} + // using numpy array + PyObject* ticksarray = detail::get_array(ticks); -template -inline void yticks(const std::vector &ticks, const std::map& keywords) -{ - yticks(ticks, {}, keywords); -} + PyObject* args; + if(labels.size() == 0) { + // construct positional args + args = PyTuple_New(1); + PyTuple_SetItem(args, 0, ticksarray); + } + else { + // make tuple of tick labels + PyObject* labelstuple = PyTuple_New(labels.size()); + for(size_t i = 0; i < labels.size(); i++) + PyTuple_SetItem(labelstuple, i, + PyUnicode_FromString(labels[i].c_str())); + + // construct positional args + args = PyTuple_New(2); + PyTuple_SetItem(args, 0, ticksarray); + PyTuple_SetItem(args, 1, labelstuple); + } -template inline void margins(Numeric margin) -{ - // construct positional args - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, PyFloat_FromDouble(margin)); + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } - PyObject* res = - PyObject_CallObject(detail::_interpreter::get().s_python_function_margins, args); - if (!res) - throw std::runtime_error("Call to margins() failed."); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_yticks, args, kwargs); - Py_DECREF(args); - Py_DECREF(res); -} + Py_DECREF(args); + Py_DECREF(kwargs); + if(!res) throw std::runtime_error("Call to yticks() failed"); -template inline void margins(Numeric margin_x, Numeric margin_y) -{ - // construct positional args - PyObject* args = PyTuple_New(2); - PyTuple_SetItem(args, 0, PyFloat_FromDouble(margin_x)); - PyTuple_SetItem(args, 1, PyFloat_FromDouble(margin_y)); + Py_DECREF(res); + } - PyObject* res = - PyObject_CallObject(detail::_interpreter::get().s_python_function_margins, args); - if (!res) - throw std::runtime_error("Call to margins() failed."); + template + inline void yticks(const std::vector& ticks, + const std::map& keywords) + { + yticks(ticks, {}, keywords); + } - Py_DECREF(args); - Py_DECREF(res); -} + template inline void margins(Numeric margin) + { + // construct positional args + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(margin)); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_margins, args); + if(!res) throw std::runtime_error("Call to margins() failed."); -inline void tick_params(const std::map& keywords, const std::string axis = "both") -{ - detail::_interpreter::get(); + Py_DECREF(args); + Py_DECREF(res); + } - // construct positional args - PyObject* args; - args = PyTuple_New(1); - PyTuple_SetItem(args, 0, PyString_FromString(axis.c_str())); + template + inline void margins(Numeric margin_x, Numeric margin_y) + { + // construct positional args + PyObject* args = PyTuple_New(2); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(margin_x)); + PyTuple_SetItem(args, 1, PyFloat_FromDouble(margin_y)); - // construct keyword args - PyObject* kwargs = PyDict_New(); - for (std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) - { - PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); - } + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_margins, args); + if(!res) throw std::runtime_error("Call to margins() failed."); + Py_DECREF(args); + Py_DECREF(res); + } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_tick_params, args, kwargs); + inline void tick_params(const std::map& keywords, + const std::string axis = "both") + { + detail::_interpreter::get(); - Py_DECREF(args); - Py_DECREF(kwargs); - if (!res) throw std::runtime_error("Call to tick_params() failed"); + // construct positional args + PyObject* args; + args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyString_FromString(axis.c_str())); - Py_DECREF(res); -} + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } -inline void subplot(long nrows, long ncols, long plot_number) -{ - detail::_interpreter::get(); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_tick_params, args, + kwargs); - // construct positional args - PyObject* args = PyTuple_New(3); - PyTuple_SetItem(args, 0, PyFloat_FromDouble(nrows)); - PyTuple_SetItem(args, 1, PyFloat_FromDouble(ncols)); - PyTuple_SetItem(args, 2, PyFloat_FromDouble(plot_number)); + Py_DECREF(args); + Py_DECREF(kwargs); + if(!res) throw std::runtime_error("Call to tick_params() failed"); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_subplot, args); - if(!res) throw std::runtime_error("Call to subplot() failed."); + Py_DECREF(res); + } - Py_DECREF(args); - Py_DECREF(res); -} + inline void subplot(long nrows, long ncols, long plot_number) + { + detail::_interpreter::get(); -inline void subplot2grid(long nrows, long ncols, long rowid=0, long colid=0, long rowspan=1, long colspan=1) -{ - detail::_interpreter::get(); + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(nrows)); + PyTuple_SetItem(args, 1, PyFloat_FromDouble(ncols)); + PyTuple_SetItem(args, 2, PyFloat_FromDouble(plot_number)); - PyObject* shape = PyTuple_New(2); - PyTuple_SetItem(shape, 0, PyLong_FromLong(nrows)); - PyTuple_SetItem(shape, 1, PyLong_FromLong(ncols)); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_subplot, args); + if(!res) throw std::runtime_error("Call to subplot() failed."); - PyObject* loc = PyTuple_New(2); - PyTuple_SetItem(loc, 0, PyLong_FromLong(rowid)); - PyTuple_SetItem(loc, 1, PyLong_FromLong(colid)); + Py_DECREF(args); + Py_DECREF(res); + } - PyObject* args = PyTuple_New(4); - PyTuple_SetItem(args, 0, shape); - PyTuple_SetItem(args, 1, loc); - PyTuple_SetItem(args, 2, PyLong_FromLong(rowspan)); - PyTuple_SetItem(args, 3, PyLong_FromLong(colspan)); + inline void subplot2grid(long nrows, long ncols, long rowid = 0, + long colid = 0, long rowspan = 1, long colspan = 1) + { + detail::_interpreter::get(); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_subplot2grid, args); - if(!res) throw std::runtime_error("Call to subplot2grid() failed."); + PyObject* shape = PyTuple_New(2); + PyTuple_SetItem(shape, 0, PyLong_FromLong(nrows)); + PyTuple_SetItem(shape, 1, PyLong_FromLong(ncols)); - Py_DECREF(shape); - Py_DECREF(loc); - Py_DECREF(args); - Py_DECREF(res); -} + PyObject* loc = PyTuple_New(2); + PyTuple_SetItem(loc, 0, PyLong_FromLong(rowid)); + PyTuple_SetItem(loc, 1, PyLong_FromLong(colid)); -inline void title(const std::string &titlestr, const std::map &keywords = {}) -{ - detail::_interpreter::get(); + PyObject* args = PyTuple_New(4); + PyTuple_SetItem(args, 0, shape); + PyTuple_SetItem(args, 1, loc); + PyTuple_SetItem(args, 2, PyLong_FromLong(rowspan)); + PyTuple_SetItem(args, 3, PyLong_FromLong(colspan)); - PyObject* pytitlestr = PyString_FromString(titlestr.c_str()); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, pytitlestr); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_subplot2grid, args); + if(!res) throw std::runtime_error("Call to subplot2grid() failed."); - PyObject* kwargs = PyDict_New(); - for (auto it = keywords.begin(); it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + Py_DECREF(shape); + Py_DECREF(loc); + Py_DECREF(args); + Py_DECREF(res); } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_title, args, kwargs); - if(!res) throw std::runtime_error("Call to title() failed."); + inline void title(const std::string& titlestr, + const std::map& keywords = {}) + { + detail::_interpreter::get(); - Py_DECREF(args); - Py_DECREF(kwargs); - Py_DECREF(res); -} + PyObject* pytitlestr = PyString_FromString(titlestr.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pytitlestr); -inline void suptitle(const std::string &suptitlestr, const std::map &keywords = {}) -{ - detail::_interpreter::get(); + PyObject* kwargs = PyDict_New(); + for(auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyUnicode_FromString(it->second.c_str())); + } - PyObject* pysuptitlestr = PyString_FromString(suptitlestr.c_str()); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, pysuptitlestr); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_title, args, kwargs); + if(!res) throw std::runtime_error("Call to title() failed."); - PyObject* kwargs = PyDict_New(); - for (auto it = keywords.begin(); it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_suptitle, args, kwargs); - if(!res) throw std::runtime_error("Call to suptitle() failed."); + inline void suptitle(const std::string& suptitlestr, + const std::map& keywords + = {}) + { + detail::_interpreter::get(); + + PyObject* pysuptitlestr = PyString_FromString(suptitlestr.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pysuptitlestr); - Py_DECREF(args); - Py_DECREF(kwargs); - Py_DECREF(res); -} + PyObject* kwargs = PyDict_New(); + for(auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyUnicode_FromString(it->second.c_str())); + } -inline void axis(const std::string &axisstr) -{ - detail::_interpreter::get(); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_suptitle, args, + kwargs); + if(!res) throw std::runtime_error("Call to suptitle() failed."); - PyObject* str = PyString_FromString(axisstr.c_str()); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, str); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); + } - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_axis, args); - if(!res) throw std::runtime_error("Call to title() failed."); + inline void axis(const std::string& axisstr) + { + detail::_interpreter::get(); - Py_DECREF(args); - Py_DECREF(res); -} + PyObject* str = PyString_FromString(axisstr.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, str); -inline void axhline(double y, double xmin = 0., double xmax = 1., const std::map& keywords = std::map()) -{ - detail::_interpreter::get(); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_axis, args); + if(!res) throw std::runtime_error("Call to title() failed."); - // construct positional args - PyObject* args = PyTuple_New(3); - PyTuple_SetItem(args, 0, PyFloat_FromDouble(y)); - PyTuple_SetItem(args, 1, PyFloat_FromDouble(xmin)); - PyTuple_SetItem(args, 2, PyFloat_FromDouble(xmax)); + Py_DECREF(args); + Py_DECREF(res); + } - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + inline void axhline(double y, double xmin = 0., double xmax = 1., + const std::map& keywords + = std::map()) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); - } + detail::_interpreter::get(); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_axhline, args, kwargs); + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(y)); + PyTuple_SetItem(args, 1, PyFloat_FromDouble(xmin)); + PyTuple_SetItem(args, 2, PyFloat_FromDouble(xmax)); - Py_DECREF(args); - Py_DECREF(kwargs); + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } - if(res) Py_DECREF(res); -} + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_axhline, args, + kwargs); -inline void axvline(double x, double ymin = 0., double ymax = 1., const std::map& keywords = std::map()) -{ - detail::_interpreter::get(); + Py_DECREF(args); + Py_DECREF(kwargs); - // construct positional args - PyObject* args = PyTuple_New(3); - PyTuple_SetItem(args, 0, PyFloat_FromDouble(x)); - PyTuple_SetItem(args, 1, PyFloat_FromDouble(ymin)); - PyTuple_SetItem(args, 2, PyFloat_FromDouble(ymax)); + if(res) Py_DECREF(res); + } - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + inline void axvline(double x, double ymin = 0., double ymax = 1., + const std::map& keywords + = std::map()) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); - } + detail::_interpreter::get(); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_axvline, args, kwargs); + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(x)); + PyTuple_SetItem(args, 1, PyFloat_FromDouble(ymin)); + PyTuple_SetItem(args, 2, PyFloat_FromDouble(ymax)); - Py_DECREF(args); - Py_DECREF(kwargs); + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } - if(res) Py_DECREF(res); -} + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_axvline, args, + kwargs); -inline void axvspan(double xmin, double xmax, double ymin = 0., double ymax = 1., const std::map& keywords = std::map()) -{ - // construct positional args - PyObject* args = PyTuple_New(4); - PyTuple_SetItem(args, 0, PyFloat_FromDouble(xmin)); - PyTuple_SetItem(args, 1, PyFloat_FromDouble(xmax)); - PyTuple_SetItem(args, 2, PyFloat_FromDouble(ymin)); - PyTuple_SetItem(args, 3, PyFloat_FromDouble(ymax)); + Py_DECREF(args); + Py_DECREF(kwargs); - // construct keyword args - PyObject* kwargs = PyDict_New(); - for (auto it = keywords.begin(); it != keywords.end(); ++it) { - if (it->first == "linewidth" || it->first == "alpha") { - PyDict_SetItemString(kwargs, it->first.c_str(), - PyFloat_FromDouble(std::stod(it->second))); - } else { - PyDict_SetItemString(kwargs, it->first.c_str(), - PyString_FromString(it->second.c_str())); - } + if(res) Py_DECREF(res); } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_axvspan, args, kwargs); - Py_DECREF(args); - Py_DECREF(kwargs); - - if(res) Py_DECREF(res); -} + inline void axvspan(double xmin, double xmax, double ymin = 0., + double ymax = 1., + const std::map& keywords + = std::map()) + { + // construct positional args + PyObject* args = PyTuple_New(4); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(xmin)); + PyTuple_SetItem(args, 1, PyFloat_FromDouble(xmax)); + PyTuple_SetItem(args, 2, PyFloat_FromDouble(ymin)); + PyTuple_SetItem(args, 3, PyFloat_FromDouble(ymax)); -inline void xlabel(const std::string &str, const std::map &keywords = {}) -{ - detail::_interpreter::get(); + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(auto it = keywords.begin(); it != keywords.end(); ++it) { + if(it->first == "linewidth" || it->first == "alpha") { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyFloat_FromDouble(std::stod(it->second))); + } + else { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } + } - PyObject* pystr = PyString_FromString(str.c_str()); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, pystr); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_axvspan, args, + kwargs); + Py_DECREF(args); + Py_DECREF(kwargs); - PyObject* kwargs = PyDict_New(); - for (auto it = keywords.begin(); it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + if(res) Py_DECREF(res); } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_xlabel, args, kwargs); - if(!res) throw std::runtime_error("Call to xlabel() failed."); + inline void xlabel(const std::string& str, + const std::map& keywords = {}) + { + detail::_interpreter::get(); - Py_DECREF(args); - Py_DECREF(kwargs); - Py_DECREF(res); -} + PyObject* pystr = PyString_FromString(str.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pystr); -inline void ylabel(const std::string &str, const std::map& keywords = {}) -{ - detail::_interpreter::get(); + PyObject* kwargs = PyDict_New(); + for(auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyUnicode_FromString(it->second.c_str())); + } - PyObject* pystr = PyString_FromString(str.c_str()); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, pystr); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_xlabel, args, kwargs); + if(!res) throw std::runtime_error("Call to xlabel() failed."); - PyObject* kwargs = PyDict_New(); - for (auto it = keywords.begin(); it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_ylabel, args, kwargs); - if(!res) throw std::runtime_error("Call to ylabel() failed."); - - Py_DECREF(args); - Py_DECREF(kwargs); - Py_DECREF(res); -} + inline void ylabel(const std::string& str, + const std::map& keywords = {}) + { + detail::_interpreter::get(); -inline void set_zlabel(const std::string &str, const std::map& keywords = {}) -{ - detail::_interpreter::get(); + PyObject* pystr = PyString_FromString(str.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pystr); - // Same as with plot_surface: We lazily load the modules here the first time - // this function is called because I'm not sure that we can assume "matplotlib - // installed" implies "mpl_toolkits installed" on all platforms, and we don't - // want to require it for people who don't need 3d plots. - static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; - if (!mpl_toolkitsmod) { - PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); - PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); - if (!mpl_toolkits || !axis3d) { throw std::runtime_error("couldnt create string"); } + PyObject* kwargs = PyDict_New(); + for(auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyUnicode_FromString(it->second.c_str())); + } - mpl_toolkitsmod = PyImport_Import(mpl_toolkits); - Py_DECREF(mpl_toolkits); - if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_ylabel, args, kwargs); + if(!res) throw std::runtime_error("Call to ylabel() failed."); - axis3dmod = PyImport_Import(axis3d); - Py_DECREF(axis3d); - if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); } - PyObject* pystr = PyString_FromString(str.c_str()); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, pystr); + inline void set_zlabel(const std::string& str, + const std::map& keywords + = {}) + { + detail::_interpreter::get(); - PyObject* kwargs = PyDict_New(); - for (auto it = keywords.begin(); it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); - } + // Same as with plot_surface: We lazily load the modules here the first + // time this function is called because I'm not sure that we can assume + // "matplotlib installed" implies "mpl_toolkits installed" on all + // platforms, and we don't want to require it for people who don't need + // 3d plots. + static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; + if(!mpl_toolkitsmod) { + PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); + PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); + if(!mpl_toolkits || !axis3d) { + throw std::runtime_error("couldnt create string"); + } + + mpl_toolkitsmod = PyImport_Import(mpl_toolkits); + Py_DECREF(mpl_toolkits); + if(!mpl_toolkitsmod) { + throw std::runtime_error("Error loading module mpl_toolkits!"); + } + + axis3dmod = PyImport_Import(axis3d); + Py_DECREF(axis3d); + if(!axis3dmod) { + throw std::runtime_error( + "Error loading module mpl_toolkits.mplot3d!"); + } + } - PyObject *ax = - PyObject_CallObject(detail::_interpreter::get().s_python_function_gca, - detail::_interpreter::get().s_python_empty_tuple); - if (!ax) throw std::runtime_error("Call to gca() failed."); - Py_INCREF(ax); + PyObject* pystr = PyString_FromString(str.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pystr); - PyObject *zlabel = PyObject_GetAttrString(ax, "set_zlabel"); - if (!zlabel) throw std::runtime_error("Attribute set_zlabel not found."); - Py_INCREF(zlabel); + PyObject* kwargs = PyDict_New(); + for(auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyUnicode_FromString(it->second.c_str())); + } - PyObject *res = PyObject_Call(zlabel, args, kwargs); - if (!res) throw std::runtime_error("Call to set_zlabel() failed."); - Py_DECREF(zlabel); + PyObject* ax = PyObject_CallObject( + detail::_interpreter::get().s_python_function_gca, + detail::_interpreter::get().s_python_empty_tuple); + if(!ax) throw std::runtime_error("Call to gca() failed."); + Py_INCREF(ax); - Py_DECREF(ax); - Py_DECREF(args); - Py_DECREF(kwargs); - if (res) Py_DECREF(res); -} + PyObject* zlabel = PyObject_GetAttrString(ax, "set_zlabel"); + if(!zlabel) throw std::runtime_error("Attribute set_zlabel not found."); + Py_INCREF(zlabel); -inline void grid(bool flag) -{ - detail::_interpreter::get(); + PyObject* res = PyObject_Call(zlabel, args, kwargs); + if(!res) throw std::runtime_error("Call to set_zlabel() failed."); + Py_DECREF(zlabel); - PyObject* pyflag = flag ? Py_True : Py_False; - Py_INCREF(pyflag); + Py_DECREF(ax); + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + } - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, pyflag); + inline void grid(bool flag) + { + detail::_interpreter::get(); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_grid, args); - if(!res) throw std::runtime_error("Call to grid() failed."); + PyObject* pyflag = flag ? Py_True : Py_False; + Py_INCREF(pyflag); - Py_DECREF(args); - Py_DECREF(res); -} + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pyflag); -inline void show(const bool block = true) -{ - detail::_interpreter::get(); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_grid, args); + if(!res) throw std::runtime_error("Call to grid() failed."); - PyObject* res; - if(block) - { - res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_show, - detail::_interpreter::get().s_python_empty_tuple); + Py_DECREF(args); + Py_DECREF(res); } - else + + inline void show(const bool block = true) { - PyObject *kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "block", Py_False); - res = PyObject_Call( detail::_interpreter::get().s_python_function_show, detail::_interpreter::get().s_python_empty_tuple, kwargs); - Py_DECREF(kwargs); - } + detail::_interpreter::get(); + PyObject* res; + if(block) { + res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_show, + detail::_interpreter::get().s_python_empty_tuple); + } + else { + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "block", Py_False); + res = PyObject_Call( + detail::_interpreter::get().s_python_function_show, + detail::_interpreter::get().s_python_empty_tuple, kwargs); + Py_DECREF(kwargs); + } - if (!res) throw std::runtime_error("Call to show() failed."); + if(!res) throw std::runtime_error("Call to show() failed."); - Py_DECREF(res); -} + Py_DECREF(res); + } -inline void close() -{ - detail::_interpreter::get(); + inline void close() + { + detail::_interpreter::get(); - PyObject* res = PyObject_CallObject( + PyObject* res = PyObject_CallObject( detail::_interpreter::get().s_python_function_close, detail::_interpreter::get().s_python_empty_tuple); - if (!res) throw std::runtime_error("Call to close() failed."); - - Py_DECREF(res); -} + if(!res) throw std::runtime_error("Call to close() failed."); -inline void xkcd() { - detail::_interpreter::get(); + Py_DECREF(res); + } - PyObject* res; - PyObject *kwargs = PyDict_New(); + inline void xkcd() + { + detail::_interpreter::get(); - res = PyObject_Call(detail::_interpreter::get().s_python_function_xkcd, - detail::_interpreter::get().s_python_empty_tuple, kwargs); + PyObject* res; + PyObject* kwargs = PyDict_New(); - Py_DECREF(kwargs); + res = PyObject_Call(detail::_interpreter::get().s_python_function_xkcd, + detail::_interpreter::get().s_python_empty_tuple, + kwargs); - if (!res) - throw std::runtime_error("Call to show() failed."); + Py_DECREF(kwargs); - Py_DECREF(res); -} + if(!res) throw std::runtime_error("Call to show() failed."); -inline void draw() -{ - detail::_interpreter::get(); + Py_DECREF(res); + } - PyObject* res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_draw, - detail::_interpreter::get().s_python_empty_tuple); + inline void draw() + { + detail::_interpreter::get(); - if (!res) throw std::runtime_error("Call to draw() failed."); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_draw, + detail::_interpreter::get().s_python_empty_tuple); - Py_DECREF(res); -} + if(!res) throw std::runtime_error("Call to draw() failed."); -template -inline void pause(Numeric interval) -{ - detail::_interpreter::get(); + Py_DECREF(res); + } - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, PyFloat_FromDouble(interval)); + template inline void pause(Numeric interval) + { + detail::_interpreter::get(); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_pause, args); - if(!res) throw std::runtime_error("Call to pause() failed."); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(interval)); - Py_DECREF(args); - Py_DECREF(res); -} + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_pause, args); + if(!res) throw std::runtime_error("Call to pause() failed."); -inline void save(const std::string& filename, const int dpi=0) -{ - detail::_interpreter::get(); + Py_DECREF(args); + Py_DECREF(res); + } - PyObject* pyfilename = PyString_FromString(filename.c_str()); + inline void save(const std::string& filename, const int dpi = 0) + { + detail::_interpreter::get(); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, pyfilename); + PyObject* pyfilename = PyString_FromString(filename.c_str()); - PyObject* kwargs = PyDict_New(); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pyfilename); - if(dpi > 0) - { - PyDict_SetItemString(kwargs, "dpi", PyLong_FromLong(dpi)); - } + PyObject* kwargs = PyDict_New(); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_save, args, kwargs); - if (!res) throw std::runtime_error("Call to save() failed."); + if(dpi > 0) { + PyDict_SetItemString(kwargs, "dpi", PyLong_FromLong(dpi)); + } - Py_DECREF(args); - Py_DECREF(kwargs); - Py_DECREF(res); -} + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_save, args, kwargs); + if(!res) throw std::runtime_error("Call to save() failed."); -inline void rcparams(const std::map& keywords = {}) { - detail::_interpreter::get(); - PyObject* args = PyTuple_New(0); - PyObject* kwargs = PyDict_New(); - for (auto it = keywords.begin(); it != keywords.end(); ++it) { - if ("text.usetex" == it->first) - PyDict_SetItemString(kwargs, it->first.c_str(), PyLong_FromLong(std::stoi(it->second.c_str()))); - else PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); } - PyObject * update = PyObject_GetAttrString(detail::_interpreter::get().s_python_function_rcparams, "update"); - PyObject * res = PyObject_Call(update, args, kwargs); - if(!res) throw std::runtime_error("Call to rcParams.update() failed."); - Py_DECREF(args); - Py_DECREF(kwargs); - Py_DECREF(update); - Py_DECREF(res); -} - -inline void clf() { - detail::_interpreter::get(); + inline void rcparams(const std::map& keywords + = {}) + { + detail::_interpreter::get(); + PyObject* args = PyTuple_New(0); + PyObject* kwargs = PyDict_New(); + for(auto it = keywords.begin(); it != keywords.end(); ++it) { + if("text.usetex" == it->first) + PyDict_SetItemString( + kwargs, it->first.c_str(), + PyLong_FromLong(std::stoi(it->second.c_str()))); + else + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } - PyObject *res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_clf, - detail::_interpreter::get().s_python_empty_tuple); + PyObject* update = PyObject_GetAttrString( + detail::_interpreter::get().s_python_function_rcparams, "update"); + PyObject* res = PyObject_Call(update, args, kwargs); + if(!res) throw std::runtime_error("Call to rcParams.update() failed."); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(update); + Py_DECREF(res); + } - if (!res) throw std::runtime_error("Call to clf() failed."); + inline void clf() + { + detail::_interpreter::get(); - Py_DECREF(res); -} + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_clf, + detail::_interpreter::get().s_python_empty_tuple); -inline void cla() { - detail::_interpreter::get(); + if(!res) throw std::runtime_error("Call to clf() failed."); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_cla, - detail::_interpreter::get().s_python_empty_tuple); + Py_DECREF(res); + } - if (!res) - throw std::runtime_error("Call to cla() failed."); + inline void cla() + { + detail::_interpreter::get(); - Py_DECREF(res); -} + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_cla, + detail::_interpreter::get().s_python_empty_tuple); -inline void ion() { - detail::_interpreter::get(); + if(!res) throw std::runtime_error("Call to cla() failed."); - PyObject *res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_ion, - detail::_interpreter::get().s_python_empty_tuple); + Py_DECREF(res); + } - if (!res) throw std::runtime_error("Call to ion() failed."); + inline void ion() + { + detail::_interpreter::get(); - Py_DECREF(res); -} + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_ion, + detail::_interpreter::get().s_python_empty_tuple); -inline std::vector> ginput(const int numClicks = 1, const std::map& keywords = {}) -{ - detail::_interpreter::get(); + if(!res) throw std::runtime_error("Call to ion() failed."); - PyObject *args = PyTuple_New(1); - PyTuple_SetItem(args, 0, PyLong_FromLong(numClicks)); + Py_DECREF(res); + } - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + inline std::vector> + ginput(const int numClicks = 1, + const std::map& keywords = {}) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); - } + detail::_interpreter::get(); - PyObject* res = PyObject_Call( - detail::_interpreter::get().s_python_function_ginput, args, kwargs); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyLong_FromLong(numClicks)); - Py_DECREF(kwargs); - Py_DECREF(args); - if (!res) throw std::runtime_error("Call to ginput() failed."); + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyUnicode_FromString(it->second.c_str())); + } - const size_t len = PyList_Size(res); - std::vector> out; - out.reserve(len); - for (size_t i = 0; i < len; i++) { - PyObject *current = PyList_GetItem(res, i); - std::array position; - position[0] = PyFloat_AsDouble(PyTuple_GetItem(current, 0)); - position[1] = PyFloat_AsDouble(PyTuple_GetItem(current, 1)); - out.push_back(position); - } - Py_DECREF(res); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_ginput, args, kwargs); - return out; -} + Py_DECREF(kwargs); + Py_DECREF(args); + if(!res) throw std::runtime_error("Call to ginput() failed."); + + const size_t len = PyList_Size(res); + std::vector> out; + out.reserve(len); + for(size_t i = 0; i < len; i++) { + PyObject* current = PyList_GetItem(res, i); + std::array position; + position[0] = PyFloat_AsDouble(PyTuple_GetItem(current, 0)); + position[1] = PyFloat_AsDouble(PyTuple_GetItem(current, 1)); + out.push_back(position); + } + Py_DECREF(res); -// Actually, is there any reason not to call this automatically for every plot? -inline void tight_layout() { - detail::_interpreter::get(); + return out; + } - PyObject *res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_tight_layout, - detail::_interpreter::get().s_python_empty_tuple); + // Actually, is there any reason not to call this automatically for every + // plot? + inline void tight_layout() + { + detail::_interpreter::get(); - if (!res) throw std::runtime_error("Call to tight_layout() failed."); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_tight_layout, + detail::_interpreter::get().s_python_empty_tuple); - Py_DECREF(res); -} + if(!res) throw std::runtime_error("Call to tight_layout() failed."); -// Support for variadic plot() and initializer lists: + Py_DECREF(res); + } -namespace detail { + // Support for variadic plot() and initializer lists: -template -using is_function = typename std::is_function>>::type; + namespace detail { -template -struct is_callable_impl; + template using is_function = typename std::is_function< + std::remove_pointer>>::type; -template -struct is_callable_impl -{ - typedef is_function type; -}; // a non-object is callable iff it is a function + template struct is_callable_impl; -template -struct is_callable_impl -{ - struct Fallback { void operator()(); }; - struct Derived : T, Fallback { }; + template struct is_callable_impl { + typedef is_function type; + }; // a non-object is callable iff it is a function - template struct Check; + template struct is_callable_impl { + struct Fallback { + void operator()(); + }; + struct Derived : T, Fallback {}; - template - static std::true_type test( ... ); // use a variadic function to make sure (1) it accepts everything and (2) its always the worst match + template struct Check; - template - static std::false_type test( Check* ); + template static std::true_type + test(...); // use a variadic function to make sure (1) it accepts + // everything and (2) its always the worst match -public: - typedef decltype(test(nullptr)) type; - typedef decltype(&Fallback::operator()) dtype; - static constexpr bool value = type::value; -}; // an object is callable iff it defines operator() + template static std::false_type + test(Check*); + public: + typedef decltype(test(nullptr)) type; + typedef decltype(&Fallback::operator()) dtype; + static constexpr bool value = type::value; + }; // an object is callable iff it defines operator() -template -struct is_callable -{ - // dispatch to is_callable_impl or is_callable_impl depending on whether T is of class type or not - typedef typename is_callable_impl::value, T>::type type; -}; + template struct is_callable { + // dispatch to is_callable_impl or is_callable_impl depending on whether T is of class type or not + typedef typename is_callable_impl::value, T>::type + type; + }; -template -struct plot_impl { }; + template struct plot_impl {}; #ifdef WITHOUT_NUMPY -template<> -struct plot_impl -{ - template - bool operator()(const IterableX& x, const IterableY& y, const std::string& format) - { - detail::_interpreter::get(); + template<> struct plot_impl { + template + bool operator()(const IterableX& x, const IterableY& y, + const std::string& format) + { + detail::_interpreter::get(); - // 2-phase lookup for distance, begin, end - using std::distance; - using std::begin; - using std::end; + // 2-phase lookup for distance, begin, end + using std::begin; + using std::distance; + using std::end; - auto xs = distance(begin(x), end(x)); - auto ys = distance(begin(y), end(y)); + auto xs = distance(begin(x), end(x)); + auto ys = distance(begin(y), end(y)); - assert(xs == ys && "x and y must have the same number of elements!"); + assert(xs == ys + && "x and y must have the same number of elements!"); - // No PyArray, must use lists, and they must have the same length. - PyObject* xlist = PyList_New(xs); - PyObject* ylist = PyList_New(ys); - PyObject* pystring = PyString_FromString(format.c_str()); + // No PyArray, must use lists, and they must have the same + // length. + PyObject* xlist = PyList_New(xs); + PyObject* ylist = PyList_New(ys); + PyObject* pystring = PyString_FromString(format.c_str()); - auto itx = begin(x), ity = begin(y); - for(decltype(xs) i = 0; i < xs; ++i) { - PyList_SetItem(xlist, i, PyFloat_FromDouble(*itx++)); - PyList_SetItem(ylist, i, PyFloat_FromDouble(*ity++)); - } + auto itx = begin(x), ity = begin(y); + for(decltype(xs) i = 0; i < xs; ++i) { + PyList_SetItem(xlist, i, PyFloat_FromDouble(*itx++)); + PyList_SetItem(ylist, i, PyFloat_FromDouble(*ity++)); + } - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_plot, + plot_args); - Py_DECREF(plot_args); - if(res) Py_DECREF(res); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - return res; - } -}; + return res; + } + }; #else -template<> -struct plot_impl -{ - template - bool operator()(const IterableX& x, const IterableY& y, const std::string& format) - { - // pyassert(PyArray_API, "NumPy needed"); + template<> struct plot_impl { + template + bool operator()(const IterableX& x, const IterableY& y, + const std::string& format) + { + // pyassert(PyArray_API, "NumPy needed"); - detail::_interpreter::get(); + detail::_interpreter::get(); - // 2-phase lookup for distance, begin, end - using std::distance; - using std::begin; - using std::end; + // 2-phase lookup for distance, begin, end + using std::begin; + using std::distance; + using std::end; - auto xs = distance(begin(x), end(x)); - auto ys = distance(begin(y), end(y)); + auto xs = distance(begin(x), end(x)); + auto ys = distance(begin(y), end(y)); - assert(ys%xs == 0 && "length of y must be a multiple of length of x!"); + assert(ys % xs == 0 + && "length of y must be a multiple of length of x!"); - typedef typename IterableX::value_type NumberX; - typedef typename IterableY::value_type NumberY; + typedef typename IterableX::value_type NumberX; + typedef typename IterableY::value_type NumberY; - NPY_TYPES xtype=detail::select_npy_type::type; - NPY_TYPES ytype=detail::select_npy_type::type; + NPY_TYPES xtype = detail::select_npy_type::type; + NPY_TYPES ytype = detail::select_npy_type::type; - npy_intp xsize=xs; - npy_intp yrows=xsize, ycols=ys/yrows; - npy_intp ysize[]={yrows, ycols}; // ysize[0] must equal xsize + npy_intp xsize = xs; + npy_intp yrows = xsize, ycols = ys / yrows; + npy_intp ysize[] = {yrows, ycols}; // ysize[0] must equal xsize - PyObject* xarray = - PyArray_New(&PyArray_Type, - 1, &xsize, xtype, nullptr, nullptr, - 0, NPY_ARRAY_FARRAY, nullptr); - PyObject* yarray = - PyArray_New(&PyArray_Type, - 2, ysize, ytype, nullptr, nullptr, - 0, NPY_ARRAY_FARRAY, nullptr); // column major! - PyObject* pystring = PyString_FromString(format.c_str()); + PyObject* xarray + = PyArray_New(&PyArray_Type, 1, &xsize, xtype, nullptr, + nullptr, 0, NPY_ARRAY_FARRAY, nullptr); + PyObject* yarray = PyArray_New( + &PyArray_Type, 2, ysize, ytype, nullptr, nullptr, 0, + NPY_ARRAY_FARRAY, nullptr); // column major! + PyObject* pystring = PyString_FromString(format.c_str()); - // fill the data - auto itx = begin(x), ity = begin(y); - NumberX* xdata = (NumberX*) PyArray_DATA((PyArrayObject*)xarray); - for(decltype(xs) i = 0; i < xs; ++i) - xdata[i]=*itx++; + // fill the data + auto itx = begin(x), ity = begin(y); + NumberX* xdata = (NumberX*)PyArray_DATA((PyArrayObject*)xarray); + for(decltype(xs) i = 0; i < xs; ++i) xdata[i] = *itx++; - NumberY* ydata = (NumberY*) PyArray_DATA((PyArrayObject*)yarray); - for(decltype(ys) i = 0; i < ys; ++i) - ydata[i]=*ity++; + NumberY* ydata = (NumberY*)PyArray_DATA((PyArrayObject*)yarray); + for(decltype(ys) i = 0; i < ys; ++i) ydata[i] = *ity++; - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_plot, + plot_args); - Py_DECREF(plot_args); - if(res) Py_DECREF(res); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - return res; - } -}; + return res; + } + }; #endif - -template<> -struct plot_impl -{ - template - bool operator()(const Iterable& ticks, const Callable& f, const std::string& format) + template<> struct plot_impl { + template + bool operator()(const Iterable& ticks, const Callable& f, + const std::string& format) + { + if(begin(ticks) == end(ticks)) return true; + + // We could use additional meta-programming to deduce the + // correct element type of y, but all values have to be + // convertible to double anyways + std::vector y; + for(auto x: ticks) y.push_back(f(x)); + return plot_impl()(ticks, y, format); + } + }; + + } // end namespace detail + + // recursion stop for the above + template bool plot() { return true; } + + template + bool plot(const A& a, const B& b, const std::string& format, Args... args) { - if(begin(ticks) == end(ticks)) return true; - - // We could use additional meta-programming to deduce the correct element type of y, - // but all values have to be convertible to double anyways - std::vector y; - for(auto x : ticks) y.push_back(f(x)); - return plot_impl()(ticks,y,format); + return detail::plot_impl::type>()( + a, b, format) + && plot(args...); } -}; -} // end namespace detail - -// recursion stop for the above -template -bool plot() { return true; } - -template -bool plot(const A& a, const B& b, const std::string& format, Args... args) -{ - return detail::plot_impl::type>()(a,b,format) && plot(args...); -} - -/* - * This group of plot() functions is needed to support initializer lists, i.e. calling - * plot( {1,2,3,4} ) - */ -inline bool plot(const std::vector& x, const std::vector& y, const std::string& format = "") -{ + /* + * This group of plot() functions is needed to support initializer lists, + * i.e. calling plot( {1,2,3,4} ) + */ + inline bool plot(const std::vector& x, const std::vector& y, + const std::string& format = "") + { #if __cplusplus >= CPP20 - return plot, std::vector>(x, y, format); + return plot, std::vector>(x, y, format); #else - return plot(x, y, format); + return plot(x, y, format); #endif -} + } -inline bool plot(const std::vector& y, const std::string& format = "") -{ + inline bool plot(const std::vector& y, + const std::string& format = "") + { #if __cplusplus >= CPP20 - return plot>(y,format); + return plot>(y, format); #else - return plot(y,format); + return plot(y, format); #endif -} + } -/* - * This class allows dynamic plots, ie changing the plotted data without clearing and re-plotting - */ -class Plot -{ -public: - // default initialization with plot label, some data and format - template - Plot(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { - detail::_interpreter::get(); + /* + * This class allows dynamic plots, ie changing the plotted data without + * clearing and re-plotting + */ + class Plot { + public: + // default initialization with plot label, some data and format + template + Plot(const std::string& name, const std::vector& x, + const std::vector& y, const std::string& format = "") + { + detail::_interpreter::get(); - assert(x.size() == y.size()); + assert(x.size() == y.size()); - PyObject* kwargs = PyDict_New(); - if(name != "") - PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + PyObject* kwargs = PyDict_New(); + if(name != "") + PyDict_SetItemString(kwargs, "label", + PyString_FromString(name.c_str())); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - PyObject* pystring = PyString_FromString(format.c_str()); + PyObject* pystring = PyString_FromString(format.c_str()); - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, plot_args, kwargs); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_plot, plot_args, + kwargs); - Py_DECREF(kwargs); - Py_DECREF(plot_args); + Py_DECREF(kwargs); + Py_DECREF(plot_args); - if(res) - { - line= PyList_GetItem(res, 0); + if(res) { + line = PyList_GetItem(res, 0); - if(line) - set_data_fct = PyObject_GetAttrString(line,"set_data"); - else - Py_DECREF(line); - Py_DECREF(res); + if(line) + set_data_fct = PyObject_GetAttrString(line, "set_data"); + else + Py_DECREF(line); + Py_DECREF(res); + } } - } - // shorter initialization with name or format only - // basically calls line, = plot([], []) - Plot(const std::string& name = "", const std::string& format = "") - : Plot(name, std::vector(), std::vector(), format) {} + // shorter initialization with name or format only + // basically calls line, = plot([], []) + Plot(const std::string& name = "", const std::string& format = "") + : Plot(name, std::vector(), std::vector(), format) + {} - template - bool update(const std::vector& x, const std::vector& y) { - assert(x.size() == y.size()); - if(set_data_fct) + template bool update(const std::vector& x, + const std::vector& y) { - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); - - PyObject* plot_args = PyTuple_New(2); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - - PyObject* res = PyObject_CallObject(set_data_fct, plot_args); - if (res) Py_DECREF(res); - return res; + assert(x.size() == y.size()); + if(set_data_fct) { + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + + PyObject* res = PyObject_CallObject(set_data_fct, plot_args); + if(res) Py_DECREF(res); + return res; + } + return false; } - return false; - } - - // clears the plot but keep it available - bool clear() { - return update(std::vector(), std::vector()); - } - // definitely remove this line - void remove() { - if(line) + // clears the plot but keep it available + bool clear() { - auto remove_fct = PyObject_GetAttrString(line,"remove"); - PyObject* args = PyTuple_New(0); - PyObject* res = PyObject_CallObject(remove_fct, args); - if (res) Py_DECREF(res); + return update(std::vector(), std::vector()); } - decref(); - } - - ~Plot() { - decref(); - } -private: - - void decref() { - if(line) - Py_DECREF(line); - if(set_data_fct) - Py_DECREF(set_data_fct); - } + // definitely remove this line + void remove() + { + if(line) { + auto remove_fct = PyObject_GetAttrString(line, "remove"); + PyObject* args = PyTuple_New(0); + PyObject* res = PyObject_CallObject(remove_fct, args); + if(res) Py_DECREF(res); + } + decref(); + } - PyObject* line = nullptr; - PyObject* set_data_fct = nullptr; -}; + ~Plot() { decref(); } + private: + void decref() + { + if(line) Py_DECREF(line); + if(set_data_fct) Py_DECREF(set_data_fct); + } + PyObject* line = nullptr; + PyObject* set_data_fct = nullptr; + }; } // end namespace matplotlibcpp - - From e1657bcfa73b381bf7112b42a94cea694cf42288 Mon Sep 17 00:00:00 2001 From: Amadeus Date: Sat, 26 Mar 2022 17:28:30 -0400 Subject: [PATCH 10/15] style: * remove unnecessary inline keyword from mile-long functions * modify the .clang-format style file to break after template declaration --- .clang-format | 2 +- matplotlibcpp.h | 171 +++++++++++++++++++++++++++++------------------- 2 files changed, 103 insertions(+), 70 deletions(-) diff --git a/.clang-format b/.clang-format index 6ff7d38..44423ba 100644 --- a/.clang-format +++ b/.clang-format @@ -17,7 +17,7 @@ AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: true AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: No +AlwaysBreakTemplateDeclarations: true BinPackArguments: true BinPackParameters: true BitFieldColonSpacing: Both diff --git a/matplotlibcpp.h b/matplotlibcpp.h index c40247d..36a1896 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -328,9 +328,9 @@ namespace matplotlibcpp { /// /// See also: /// https://matplotlib.org/2.0.2/api/matplotlib_configuration_api.html#matplotlib.use - inline void backend(const std::string& name) { detail::s_backend = name; } + void backend(const std::string& name) { detail::s_backend = name; } - inline bool annotate(std::string annotation, double x, double y) + bool annotate(std::string annotation, double x, double y) { detail::_interpreter::get(); @@ -362,51 +362,79 @@ namespace matplotlibcpp { #ifndef WITHOUT_NUMPY // Type selector for numpy array conversion - template struct select_npy_type { + template + struct select_npy_type { const static NPY_TYPES type = NPY_NOTYPE; }; // Default - template<> struct select_npy_type { + + template<> + struct select_npy_type { const static NPY_TYPES type = NPY_DOUBLE; }; - template<> struct select_npy_type { + + template<> + struct select_npy_type { const static NPY_TYPES type = NPY_FLOAT; }; - template<> struct select_npy_type { + + template<> + struct select_npy_type { const static NPY_TYPES type = NPY_BOOL; }; - template<> struct select_npy_type { + + template<> + struct select_npy_type { const static NPY_TYPES type = NPY_INT8; }; - template<> struct select_npy_type { + + template<> + struct select_npy_type { const static NPY_TYPES type = NPY_SHORT; }; - template<> struct select_npy_type { + + template<> + struct select_npy_type { const static NPY_TYPES type = NPY_INT; }; - template<> struct select_npy_type { + + template<> + struct select_npy_type { const static NPY_TYPES type = NPY_INT64; }; - template<> struct select_npy_type { + + template<> + struct select_npy_type { const static NPY_TYPES type = NPY_UINT8; }; - template<> struct select_npy_type { + + template<> + struct select_npy_type { const static NPY_TYPES type = NPY_USHORT; }; - template<> struct select_npy_type { + + template<> + struct select_npy_type { const static NPY_TYPES type = NPY_ULONG; }; - template<> struct select_npy_type { + + template<> + struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; // Sanity checks; comment them out or change the numpy type below if // you're compiling on a platform where they don't apply static_assert(sizeof(long long) == 8); - template<> struct select_npy_type { + + template<> + struct select_npy_type { const static NPY_TYPES type = NPY_INT64; }; + static_assert(sizeof(unsigned long long) == 8); - template<> struct select_npy_type { + + template<> + struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; @@ -470,7 +498,7 @@ namespace matplotlibcpp { #endif // WITHOUT_NUMPY // sometimes, for labels and such, we need string arrays - inline PyObject* get_array(const std::vector& strings) + PyObject* get_array(const std::vector& strings) { PyObject* list = PyList_New(strings.size()); for(std::size_t i = 0; i < strings.size(); ++i) { @@ -1244,7 +1272,7 @@ namespace matplotlibcpp { #ifndef WITHOUT_NUMPY namespace detail { - inline void imshow(void* ptr, const NPY_TYPES type, const int rows, + void imshow(void* ptr, const NPY_TYPES type, const int rows, const int columns, const int colors, const std::map& keywords, PyObject** out) @@ -1284,7 +1312,7 @@ namespace matplotlibcpp { } // namespace detail - inline void imshow(const unsigned char* ptr, const int rows, + void imshow(const unsigned char* ptr, const int rows, const int columns, const int colors, const std::map& keywords = {}, PyObject** out = nullptr) @@ -1293,7 +1321,7 @@ namespace matplotlibcpp { out); } - inline void imshow(const float* ptr, const int rows, const int columns, + void imshow(const float* ptr, const int rows, const int columns, const int colors, const std::map& keywords = {}, PyObject** out = nullptr) @@ -1671,7 +1699,7 @@ namespace matplotlibcpp { return res; } - inline bool subplots_adjust(const std::map& keywords + bool subplots_adjust(const std::map& keywords = {}) { detail::_interpreter::get(); @@ -2057,9 +2085,10 @@ namespace matplotlibcpp { return res; } - template bool named_plot(const std::string& name, - const std::vector& y, - const std::string& format = "") + template + bool named_plot(const std::string& name, + const std::vector& y, + const std::string& format = "") { detail::_interpreter::get(); @@ -2240,7 +2269,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void colorbar(PyObject* mappable = NULL, + void colorbar(PyObject* mappable = NULL, const std::map& keywords = {}) { if(mappable == NULL) @@ -2270,7 +2299,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline long figure(long number = -1) + long figure(long number = -1) { detail::_interpreter::get(); @@ -2306,7 +2335,7 @@ namespace matplotlibcpp { return figureNumber; } - inline bool fignum_exists(long number) + bool fignum_exists(long number) { detail::_interpreter::get(); @@ -2323,7 +2352,7 @@ namespace matplotlibcpp { return ret; } - inline void figure_size(size_t w, size_t h) + void figure_size(size_t w, size_t h) { detail::_interpreter::get(); @@ -2346,7 +2375,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void legend() + void legend() { detail::_interpreter::get(); @@ -2358,7 +2387,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void legend(const std::map& keywords) + void legend(const std::map& keywords) { detail::_interpreter::get(); @@ -2380,7 +2409,8 @@ namespace matplotlibcpp { Py_DECREF(res); } - template inline void set_aspect(Numeric ratio) + template + void set_aspect(Numeric ratio) { detail::_interpreter::get(); @@ -2408,7 +2438,7 @@ namespace matplotlibcpp { Py_DECREF(kwargs); } - inline void set_aspect_equal() + void set_aspect_equal() { // expect ratio == "equal". Leaving error handling to matplotlib. detail::_interpreter::get(); @@ -2456,7 +2486,8 @@ namespace matplotlibcpp { Py_DECREF(res); } - template void xlim(Numeric left, Numeric right) + template + void xlim(Numeric left, Numeric right) { detail::_interpreter::get(); @@ -2475,7 +2506,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline std::array xlim() + std::array xlim() { PyObject* args = PyTuple_New(0); PyObject* res = PyObject_CallObject( @@ -2490,7 +2521,7 @@ namespace matplotlibcpp { return {PyFloat_AsDouble(left), PyFloat_AsDouble(right)}; } - inline std::array ylim() + std::array ylim() { PyObject* args = PyTuple_New(0); PyObject* res = PyObject_CallObject( @@ -2506,7 +2537,7 @@ namespace matplotlibcpp { } template - inline void xticks(const std::vector& ticks, + void xticks(const std::vector& ticks, const std::vector& labels = {}, const std::map& keywords = {}) { @@ -2556,14 +2587,14 @@ namespace matplotlibcpp { } template - inline void xticks(const std::vector& ticks, + void xticks(const std::vector& ticks, const std::map& keywords) { xticks(ticks, {}, keywords); } // options only, e.g. plt::xticks(rotation=20) - inline void xticks(const std::map& keywords) + void xticks(const std::map& keywords) { detail::_interpreter::get(); @@ -2585,7 +2616,7 @@ namespace matplotlibcpp { } template - inline void yticks(const std::vector& ticks, + void yticks(const std::vector& ticks, const std::vector& labels = {}, const std::map& keywords = {}) { @@ -2635,13 +2666,14 @@ namespace matplotlibcpp { } template - inline void yticks(const std::vector& ticks, + void yticks(const std::vector& ticks, const std::map& keywords) { yticks(ticks, {}, keywords); } - template inline void margins(Numeric margin) + template + void margins(Numeric margin) { // construct positional args PyObject* args = PyTuple_New(1); @@ -2656,7 +2688,7 @@ namespace matplotlibcpp { } template - inline void margins(Numeric margin_x, Numeric margin_y) + void margins(Numeric margin_x, Numeric margin_y) { // construct positional args PyObject* args = PyTuple_New(2); @@ -2671,7 +2703,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void tick_params(const std::map& keywords, + void tick_params(const std::map& keywords, const std::string axis = "both") { detail::_interpreter::get(); @@ -2701,7 +2733,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void subplot(long nrows, long ncols, long plot_number) + void subplot(long nrows, long ncols, long plot_number) { detail::_interpreter::get(); @@ -2719,7 +2751,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void subplot2grid(long nrows, long ncols, long rowid = 0, + void subplot2grid(long nrows, long ncols, long rowid = 0, long colid = 0, long rowspan = 1, long colspan = 1) { detail::_interpreter::get(); @@ -2748,7 +2780,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void title(const std::string& titlestr, + void title(const std::string& titlestr, const std::map& keywords = {}) { detail::_interpreter::get(); @@ -2772,7 +2804,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void suptitle(const std::string& suptitlestr, + void suptitle(const std::string& suptitlestr, const std::map& keywords = {}) { @@ -2798,7 +2830,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void axis(const std::string& axisstr) + void axis(const std::string& axisstr) { detail::_interpreter::get(); @@ -2814,7 +2846,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void axhline(double y, double xmin = 0., double xmax = 1., + void axhline(double y, double xmin = 0., double xmax = 1., const std::map& keywords = std::map()) { @@ -2845,7 +2877,7 @@ namespace matplotlibcpp { if(res) Py_DECREF(res); } - inline void axvline(double x, double ymin = 0., double ymax = 1., + void axvline(double x, double ymin = 0., double ymax = 1., const std::map& keywords = std::map()) { @@ -2876,7 +2908,7 @@ namespace matplotlibcpp { if(res) Py_DECREF(res); } - inline void axvspan(double xmin, double xmax, double ymin = 0., + void axvspan(double xmin, double xmax, double ymin = 0., double ymax = 1., const std::map& keywords = std::map()) @@ -2910,7 +2942,7 @@ namespace matplotlibcpp { if(res) Py_DECREF(res); } - inline void xlabel(const std::string& str, + void xlabel(const std::string& str, const std::map& keywords = {}) { detail::_interpreter::get(); @@ -2934,7 +2966,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void ylabel(const std::string& str, + void ylabel(const std::string& str, const std::map& keywords = {}) { detail::_interpreter::get(); @@ -2958,7 +2990,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void set_zlabel(const std::string& str, + void set_zlabel(const std::string& str, const std::map& keywords = {}) { @@ -3021,7 +3053,7 @@ namespace matplotlibcpp { if(res) Py_DECREF(res); } - inline void grid(bool flag) + void grid(bool flag) { detail::_interpreter::get(); @@ -3039,7 +3071,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void show(const bool block = true) + void show(const bool block = true) { detail::_interpreter::get(); @@ -3063,7 +3095,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void close() + void close() { detail::_interpreter::get(); @@ -3076,7 +3108,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void xkcd() + void xkcd() { detail::_interpreter::get(); @@ -3094,7 +3126,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void draw() + void draw() { detail::_interpreter::get(); @@ -3107,7 +3139,8 @@ namespace matplotlibcpp { Py_DECREF(res); } - template inline void pause(Numeric interval) + template + void pause(Numeric interval) { detail::_interpreter::get(); @@ -3122,7 +3155,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void save(const std::string& filename, const int dpi = 0) + void save(const std::string& filename, const int dpi = 0) { detail::_interpreter::get(); @@ -3146,7 +3179,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void rcparams(const std::map& keywords + void rcparams(const std::map& keywords = {}) { detail::_interpreter::get(); @@ -3172,7 +3205,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void clf() + void clf() { detail::_interpreter::get(); @@ -3185,7 +3218,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void cla() + void cla() { detail::_interpreter::get(); @@ -3198,7 +3231,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline void ion() + void ion() { detail::_interpreter::get(); @@ -3211,7 +3244,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - inline std::vector> + std::vector> ginput(const int numClicks = 1, const std::map& keywords = {}) { @@ -3253,7 +3286,7 @@ namespace matplotlibcpp { // Actually, is there any reason not to call this automatically for every // plot? - inline void tight_layout() + void tight_layout() { detail::_interpreter::get(); @@ -3455,7 +3488,7 @@ namespace matplotlibcpp { * This group of plot() functions is needed to support initializer lists, * i.e. calling plot( {1,2,3,4} ) */ - inline bool plot(const std::vector& x, const std::vector& y, + bool plot(const std::vector& x, const std::vector& y, const std::string& format = "") { #if __cplusplus >= CPP20 @@ -3465,7 +3498,7 @@ namespace matplotlibcpp { #endif } - inline bool plot(const std::vector& y, + bool plot(const std::vector& y, const std::string& format = "") { #if __cplusplus >= CPP20 From 6be6a2a4f507baa8e063d2fad35955882c3e4bce Mon Sep 17 00:00:00 2001 From: Amadeus Date: Sat, 26 Mar 2022 18:13:43 -0400 Subject: [PATCH 11/15] style: small changes --- .clang-format | 10 +- datetime_utils.h | 11 ++- matplotlibcpp.h | 240 +++++++++++++++++++++++++++-------------------- 3 files changed, 151 insertions(+), 110 deletions(-) diff --git a/.clang-format b/.clang-format index 44423ba..0b4b36b 100644 --- a/.clang-format +++ b/.clang-format @@ -24,13 +24,13 @@ BitFieldColonSpacing: Both BreakBeforeBraces: Custom BraceWrapping: AfterCaseLabel: false - AfterClass: false + AfterClass: true AfterFunction: true AfterControlStatement: false SplitEmptyFunction: false AfterEnum: false - AfterNamespace: false - AfterStruct: false + AfterNamespace: true + AfterStruct: true AfterUnion: false AfterExternBlock: false BeforeCatch: true @@ -68,10 +68,10 @@ SpaceAroundPointerQualifiers: Default SpaceBeforeAssignmentOperators: true SpaceBeforeCaseColon: false SpaceBeforeCpp11BracedList: false -SpaceBeforeCtorInitializerColon: false +SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: Never -SpaceBeforeRangeBasedForLoopColon: false +SpaceBeforeRangeBasedForLoopColon: true SpaceBeforeSquareBrackets: false SpaceInEmptyBlock: false SpaceInEmptyParentheses: false diff --git a/datetime_utils.h b/datetime_utils.h index 7b7b54f..e459dad 100644 --- a/datetime_utils.h +++ b/datetime_utils.h @@ -25,7 +25,8 @@ PyObject* toPyDateTime(const TimePoint& t, int dummy = 0) return obj; } -template<> PyObject* toPyDateTime(const time_t& t, int us) +template<> +PyObject* toPyDateTime(const time_t& t, int us) { tm tm{}; gmtime_r(&t, &tm); // compatible with matlab, inverse of datenum. @@ -43,7 +44,8 @@ template<> PyObject* toPyDateTime(const time_t& t, int us) return obj; } -template PyObject* toPyDateTimeList(const Time_t* t, size_t nt) +template +PyObject* toPyDateTimeList(const Time_t* t, size_t nt) { PyObject* tlist = PyList_New(nt); if(tlist == nullptr) return nullptr; @@ -60,7 +62,9 @@ template PyObject* toPyDateTimeList(const Time_t* t, size_t nt) return tlist; } -template class DateTimeList { +template +class DateTimeList +{ public: DateTimeList(const Time_t* t, size_t nt) { @@ -80,7 +84,6 @@ template class DateTimeList { namespace matplotlibcpp { - // special purpose function to plot against python datetime objects. template bool plot(const DateTimeList& t, const ContainerY& y, diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 36a1896..aecdb95 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -42,12 +42,14 @@ #define CPP20 202002L -namespace matplotlibcpp { - namespace detail { - +namespace matplotlibcpp +{ + namespace detail + { static std::string s_backend; - struct _interpreter { + struct _interpreter + { PyObject* s_python_function_arrow; PyObject* s_python_function_show; PyObject* s_python_function_close; @@ -358,67 +360,79 @@ namespace matplotlibcpp { return res; } - namespace detail { - + namespace detail + { #ifndef WITHOUT_NUMPY // Type selector for numpy array conversion template - struct select_npy_type { + struct select_npy_type + { const static NPY_TYPES type = NPY_NOTYPE; }; // Default template<> - struct select_npy_type { + struct select_npy_type + { const static NPY_TYPES type = NPY_DOUBLE; }; template<> - struct select_npy_type { + struct select_npy_type + { const static NPY_TYPES type = NPY_FLOAT; }; template<> - struct select_npy_type { + struct select_npy_type + { const static NPY_TYPES type = NPY_BOOL; }; template<> - struct select_npy_type { + struct select_npy_type + { const static NPY_TYPES type = NPY_INT8; }; template<> - struct select_npy_type { + struct select_npy_type + { const static NPY_TYPES type = NPY_SHORT; }; template<> - struct select_npy_type { + struct select_npy_type + { const static NPY_TYPES type = NPY_INT; }; template<> - struct select_npy_type { + struct select_npy_type + { const static NPY_TYPES type = NPY_INT64; }; template<> - struct select_npy_type { + struct select_npy_type + { const static NPY_TYPES type = NPY_UINT8; }; template<> - struct select_npy_type { + struct select_npy_type + { const static NPY_TYPES type = NPY_USHORT; }; template<> - struct select_npy_type { + struct select_npy_type + { const static NPY_TYPES type = NPY_ULONG; }; template<> - struct select_npy_type { + struct select_npy_type + { const static NPY_TYPES type = NPY_UINT64; }; @@ -427,14 +441,16 @@ namespace matplotlibcpp { static_assert(sizeof(long long) == 8); template<> - struct select_npy_type { + struct select_npy_type + { const static NPY_TYPES type = NPY_INT64; }; static_assert(sizeof(unsigned long long) == 8); template<> - struct select_npy_type { + struct select_npy_type + { const static NPY_TYPES type = NPY_UINT64; }; @@ -473,7 +489,7 @@ namespace matplotlibcpp { double* vd_begin = static_cast(PyArray_DATA(varray)); - for(const ::std::vector& v_row: v) { + for(const ::std::vector& v_row : v) { if(v_row.size() != static_cast(vsize[1])) throw std::runtime_error("Missmatched array size"); std::copy(v_row.begin(), v_row.end(), vd_begin); @@ -1270,12 +1286,12 @@ namespace matplotlibcpp { } #ifndef WITHOUT_NUMPY - namespace detail { - + namespace detail + { void imshow(void* ptr, const NPY_TYPES type, const int rows, - const int columns, const int colors, - const std::map& keywords, - PyObject** out) + const int columns, const int colors, + const std::map& keywords, + PyObject** out) { assert(type == NPY_UINT8 || type == NPY_FLOAT); assert(colors == 1 || colors == 3 || colors == 4); @@ -1312,19 +1328,19 @@ namespace matplotlibcpp { } // namespace detail - void imshow(const unsigned char* ptr, const int rows, - const int columns, const int colors, - const std::map& keywords = {}, - PyObject** out = nullptr) + void imshow(const unsigned char* ptr, const int rows, const int columns, + const int colors, + const std::map& keywords = {}, + PyObject** out = nullptr) { detail::imshow((void*)ptr, NPY_UINT8, rows, columns, colors, keywords, out); } void imshow(const float* ptr, const int rows, const int columns, - const int colors, - const std::map& keywords = {}, - PyObject** out = nullptr) + const int colors, + const std::map& keywords = {}, + PyObject** out = nullptr) { detail::imshow((void*)ptr, NPY_FLOAT, rows, columns, colors, keywords, out); @@ -1378,7 +1394,7 @@ namespace matplotlibcpp { PyObject* kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "s", PyLong_FromLong(s)); - for(const auto& it: keywords) { + for(const auto& it : keywords) { PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); } @@ -1398,12 +1414,13 @@ namespace matplotlibcpp { return res; } - template bool - scatter_colored(const std::vector& x, - const std::vector& y, - const std::vector& colors, - const double s = 1.0, // The marker size in points**2 - const std::map& keywords = {}) + template + bool scatter_colored(const std::vector& x, + const std::vector& y, + const std::vector& colors, + const double s = 1.0, // The marker size in points**2 + const std::map& keywords + = {}) { detail::_interpreter::get(); @@ -1417,7 +1434,7 @@ namespace matplotlibcpp { PyDict_SetItemString(kwargs, "s", PyLong_FromLong(s)); PyDict_SetItemString(kwargs, "c", colors_array); - for(const auto& it: keywords) { + for(const auto& it : keywords) { PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); } @@ -1565,7 +1582,7 @@ namespace matplotlibcpp { } // take care of the remaining keywords - for(const auto& it: keywords) { + for(const auto& it : keywords) { PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); } @@ -1593,7 +1610,7 @@ namespace matplotlibcpp { PyTuple_SetItem(args, 0, vector); PyObject* kwargs = PyDict_New(); - for(const auto& it: keywords) { + for(const auto& it : keywords) { PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); } @@ -1699,8 +1716,7 @@ namespace matplotlibcpp { return res; } - bool subplots_adjust(const std::map& keywords - = {}) + bool subplots_adjust(const std::map& keywords = {}) { detail::_interpreter::get(); @@ -2086,9 +2102,8 @@ namespace matplotlibcpp { } template - bool named_plot(const std::string& name, - const std::vector& y, - const std::string& format = "") + bool named_plot(const std::string& name, const std::vector& y, + const std::string& format = "") { detail::_interpreter::get(); @@ -2270,7 +2285,7 @@ namespace matplotlibcpp { } void colorbar(PyObject* mappable = NULL, - const std::map& keywords = {}) + const std::map& keywords = {}) { if(mappable == NULL) throw std::runtime_error( @@ -2467,7 +2482,8 @@ namespace matplotlibcpp { Py_DECREF(kwargs); } - template void ylim(Numeric left, Numeric right) + template + void ylim(Numeric left, Numeric right) { detail::_interpreter::get(); @@ -2538,8 +2554,8 @@ namespace matplotlibcpp { template void xticks(const std::vector& ticks, - const std::vector& labels = {}, - const std::map& keywords = {}) + const std::vector& labels = {}, + const std::map& keywords = {}) { assert(labels.size() == 0 || ticks.size() == labels.size()); @@ -2588,7 +2604,7 @@ namespace matplotlibcpp { template void xticks(const std::vector& ticks, - const std::map& keywords) + const std::map& keywords) { xticks(ticks, {}, keywords); } @@ -2602,7 +2618,7 @@ namespace matplotlibcpp { // construct keyword args PyObject* kwargs = PyDict_New(); - for(const auto& [k, v]: keywords) + for(const auto& [k, v] : keywords) PyDict_SetItemString(kwargs, k.c_str(), PyString_FromString(v.c_str())); @@ -2617,8 +2633,8 @@ namespace matplotlibcpp { template void yticks(const std::vector& ticks, - const std::vector& labels = {}, - const std::map& keywords = {}) + const std::vector& labels = {}, + const std::map& keywords = {}) { assert(labels.size() == 0 || ticks.size() == labels.size()); @@ -2667,7 +2683,7 @@ namespace matplotlibcpp { template void yticks(const std::vector& ticks, - const std::map& keywords) + const std::map& keywords) { yticks(ticks, {}, keywords); } @@ -2704,7 +2720,7 @@ namespace matplotlibcpp { } void tick_params(const std::map& keywords, - const std::string axis = "both") + const std::string axis = "both") { detail::_interpreter::get(); @@ -2751,8 +2767,8 @@ namespace matplotlibcpp { Py_DECREF(res); } - void subplot2grid(long nrows, long ncols, long rowid = 0, - long colid = 0, long rowspan = 1, long colspan = 1) + void subplot2grid(long nrows, long ncols, long rowid = 0, long colid = 0, + long rowspan = 1, long colspan = 1) { detail::_interpreter::get(); @@ -2781,7 +2797,7 @@ namespace matplotlibcpp { } void title(const std::string& titlestr, - const std::map& keywords = {}) + const std::map& keywords = {}) { detail::_interpreter::get(); @@ -2805,8 +2821,7 @@ namespace matplotlibcpp { } void suptitle(const std::string& suptitlestr, - const std::map& keywords - = {}) + const std::map& keywords = {}) { detail::_interpreter::get(); @@ -2847,8 +2862,8 @@ namespace matplotlibcpp { } void axhline(double y, double xmin = 0., double xmax = 1., - const std::map& keywords - = std::map()) + const std::map& keywords + = std::map()) { detail::_interpreter::get(); @@ -2878,8 +2893,8 @@ namespace matplotlibcpp { } void axvline(double x, double ymin = 0., double ymax = 1., - const std::map& keywords - = std::map()) + const std::map& keywords + = std::map()) { detail::_interpreter::get(); @@ -2908,10 +2923,9 @@ namespace matplotlibcpp { if(res) Py_DECREF(res); } - void axvspan(double xmin, double xmax, double ymin = 0., - double ymax = 1., - const std::map& keywords - = std::map()) + void axvspan(double xmin, double xmax, double ymin = 0., double ymax = 1., + const std::map& keywords + = std::map()) { // construct positional args PyObject* args = PyTuple_New(4); @@ -2943,7 +2957,7 @@ namespace matplotlibcpp { } void xlabel(const std::string& str, - const std::map& keywords = {}) + const std::map& keywords = {}) { detail::_interpreter::get(); @@ -2967,7 +2981,7 @@ namespace matplotlibcpp { } void ylabel(const std::string& str, - const std::map& keywords = {}) + const std::map& keywords = {}) { detail::_interpreter::get(); @@ -2991,8 +3005,7 @@ namespace matplotlibcpp { } void set_zlabel(const std::string& str, - const std::map& keywords - = {}) + const std::map& keywords = {}) { detail::_interpreter::get(); @@ -3179,8 +3192,7 @@ namespace matplotlibcpp { Py_DECREF(res); } - void rcparams(const std::map& keywords - = {}) + void rcparams(const std::map& keywords = {}) { detail::_interpreter::get(); PyObject* args = PyTuple_New(0); @@ -3301,30 +3313,41 @@ namespace matplotlibcpp { // Support for variadic plot() and initializer lists: - namespace detail { - - template using is_function = typename std::is_function< + namespace detail + { + template + using is_function = typename std::is_function< std::remove_pointer>>::type; - template struct is_callable_impl; + template + struct is_callable_impl; - template struct is_callable_impl { + template + struct is_callable_impl + { typedef is_function type; }; // a non-object is callable iff it is a function - template struct is_callable_impl { - struct Fallback { + template + struct is_callable_impl + { + struct Fallback + { void operator()(); }; - struct Derived : T, Fallback {}; + struct Derived : T, Fallback + {}; - template struct Check; + template + struct Check; - template static std::true_type + template + static std::true_type test(...); // use a variadic function to make sure (1) it accepts // everything and (2) its always the worst match - template static std::false_type + template + static std::false_type test(Check*); public: typedef decltype(test(nullptr)) type; @@ -3332,18 +3355,24 @@ namespace matplotlibcpp { static constexpr bool value = type::value; }; // an object is callable iff it defines operator() - template struct is_callable { + template + struct is_callable + { // dispatch to is_callable_impl or is_callable_impl depending on whether T is of class type or not typedef typename is_callable_impl::value, T>::type type; }; - template struct plot_impl {}; + template + struct plot_impl + {}; #ifdef WITHOUT_NUMPY - template<> struct plot_impl { + template<> + struct plot_impl + { template bool operator()(const IterableX& x, const IterableY& y, const std::string& format) @@ -3391,7 +3420,9 @@ namespace matplotlibcpp { #else - template<> struct plot_impl { + template<> + struct plot_impl + { template bool operator()(const IterableX& x, const IterableY& y, const std::string& format) @@ -3455,7 +3486,9 @@ namespace matplotlibcpp { #endif - template<> struct plot_impl { + template<> + struct plot_impl + { template bool operator()(const Iterable& ticks, const Callable& f, const std::string& format) @@ -3466,7 +3499,7 @@ namespace matplotlibcpp { // correct element type of y, but all values have to be // convertible to double anyways std::vector y; - for(auto x: ticks) y.push_back(f(x)); + for(auto x : ticks) y.push_back(f(x)); return plot_impl()(ticks, y, format); } }; @@ -3474,7 +3507,11 @@ namespace matplotlibcpp { } // end namespace detail // recursion stop for the above - template bool plot() { return true; } + template + bool plot() + { + return true; + } template bool plot(const A& a, const B& b, const std::string& format, Args... args) @@ -3489,7 +3526,7 @@ namespace matplotlibcpp { * i.e. calling plot( {1,2,3,4} ) */ bool plot(const std::vector& x, const std::vector& y, - const std::string& format = "") + const std::string& format = "") { #if __cplusplus >= CPP20 return plot, std::vector>(x, y, format); @@ -3498,8 +3535,7 @@ namespace matplotlibcpp { #endif } - bool plot(const std::vector& y, - const std::string& format = "") + bool plot(const std::vector& y, const std::string& format = "") { #if __cplusplus >= CPP20 return plot>(y, format); @@ -3512,7 +3548,8 @@ namespace matplotlibcpp { * This class allows dynamic plots, ie changing the plotted data without * clearing and re-plotting */ - class Plot { + class Plot + { public: // default initialization with plot label, some data and format template @@ -3562,8 +3599,9 @@ namespace matplotlibcpp { : Plot(name, std::vector(), std::vector(), format) {} - template bool update(const std::vector& x, - const std::vector& y) + template + bool update(const std::vector& x, + const std::vector& y) { assert(x.size() == y.size()); if(set_data_fct) { From 33020369e1d5d08809ab336d19fb8a02dcbd764f Mon Sep 17 00:00:00 2001 From: Amadeus Date: Sat, 26 Mar 2022 18:53:19 -0400 Subject: [PATCH 12/15] matplotlibcpp.h: add legend() function that takes as input the list of labels --- datetime_utils.h | 2 -- matplotlibcpp.h | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/datetime_utils.h b/datetime_utils.h index e459dad..ee588a8 100644 --- a/datetime_utils.h +++ b/datetime_utils.h @@ -50,8 +50,6 @@ PyObject* toPyDateTimeList(const Time_t* t, size_t nt) PyObject* tlist = PyList_New(nt); if(tlist == nullptr) return nullptr; - // Py_INCREF(tlist); - if(!PyDateTimeAPI) { PyDateTime_IMPORT; } for(size_t i = 0; i < nt; i++) { diff --git a/matplotlibcpp.h b/matplotlibcpp.h index aecdb95..3b73611 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -37,7 +37,6 @@ #if PY_MAJOR_VERSION >= 3 #define PyString_FromString PyUnicode_FromString #define PyInt_FromLong PyLong_FromLong -#define PyString_FromString PyUnicode_FromString #endif #define CPP20 202002L @@ -2424,6 +2423,46 @@ namespace matplotlibcpp Py_DECREF(res); } + template + void legend(const Labels& labels, const std::map& keywords={}) + { + detail::_interpreter::get(); + + PyObject* llist = PyList_New(labels.size()); + if(llist == nullptr) + throw "Can't allocate labels list in legend() function."; + + // iterate over labels, hopefully not too many! + size_t i=0; + for (const auto& l : labels) { + PyObject* str = PyString_FromString(l.c_str()); + PyList_SET_ITEM(llist, i++, str); + } + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } + + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, llist); + + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_legend, + args, kwargs); + // PyObject* res = PyObject_Call( + // detail::_interpreter::get().s_python_function_legend, + // detail::_interpreter::get().s_python_empty_tuple, kwargs); + if(!res) throw std::runtime_error("Call to legend() failed."); + + Py_DECREF(kwargs); + Py_DECREF(res); + } + template void set_aspect(Numeric ratio) { From 86e719de8e5d21cf64501d11c3cd310b6d6ad2e3 Mon Sep 17 00:00:00 2001 From: Amadeus Date: Sun, 17 Apr 2022 12:05:07 -0400 Subject: [PATCH 13/15] DateTimeList: add assignment operator and default constructor --- datetime_utils.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/datetime_utils.h b/datetime_utils.h index ee588a8..ace9080 100644 --- a/datetime_utils.h +++ b/datetime_utils.h @@ -64,8 +64,12 @@ template class DateTimeList { public: + + DateTimeList() = default; + DateTimeList(const Time_t* t, size_t nt) { + matplotlibcpp::detail::_interpreter::get(); tlist = (PyListObject*)toPyDateTimeList(t, nt); } @@ -74,6 +78,12 @@ class DateTimeList if(tlist) Py_DECREF((PyObject*)tlist); } + DateTimeList& operator=(const DateTimeList& rhs) { + tlist=rhs.tlist; + Py_INCREF(tlist); + return *this; + } + PyListObject* get() const { return tlist; } size_t size() const { return tlist ? PyList_Size((PyObject*)tlist) : 0; } private: From b87d8be3cab412c51c67f2a499a5834da0b7c16c Mon Sep 17 00:00:00 2001 From: Amadeus Date: Wed, 11 Sep 2024 23:26:05 -0400 Subject: [PATCH 14/15] matplotlibcpp.h: get it to compile with python >= 3.8, as the C API has changed --- examples/basic.cpp | 4 +- examples/basic.png | Bin 37470 -> 37470 bytes examples/imshow.cpp | 2 +- examples/timeseries.cpp | 17 +- matplotlibcpp.h | 718 +++++++++++++++++++++------------------- 5 files changed, 388 insertions(+), 353 deletions(-) diff --git a/examples/basic.cpp b/examples/basic.cpp index a8facb7..b4d3c8a 100644 --- a/examples/basic.cpp +++ b/examples/basic.cpp @@ -1,6 +1,6 @@ // -// g++ -g -Wall -o basic -I/usr/include/python3.9 basic.cpp -lpython3.9 -// g++ -g -Wall -o basic $(python-config --includes) basic.cpp $(python-config --ldflags --embed) +// g++ -g -Wall -std=c++20 -o basic -I/usr/include/python3.9 basic.cpp -lpython3.9 +// g++ -g -Wall -std=c++20 -o basic $(python-config --includes) basic.cpp $(python-config --ldflags --embed) // #define _USE_MATH_DEFINES diff --git a/examples/basic.png b/examples/basic.png index 6b83650a0863f8d7ebe9f2da5a68ab396c46e279..e6f4f1472da5fb22bd7b0884693f05b9096753a1 100644 GIT binary patch delta 45 zcmcb&gz4TArU`Be7J4Q+3K=CO1;tkS`nicE1v&X8Ihjd%`9= 3 - void* import_numpy() - { - import_array(); // initialize C-API - return NULL; - } - + void* import_numpy() { + import_array(); // initialize C-API + return NULL; + } #else - - void import_numpy() - { - import_array(); // initialize C-API - } - + void import_numpy() { + import_array(); // initialize C-API + } #endif #endif - _interpreter() - { - // optional but recommended + _interpreter() { + // optional but recommended +// #if PY_MAJOR_VERSION >= 3 +// wchar_t name[] = L"plotting"; +// #else +// char name[] = "plotting"; +// #endif + + // FIXME: + // https://docs.python.org/3.12/c-api/init_config.html#init-config + #if PY_MAJOR_VERSION >= 3 - wchar_t name[] = L"plotting"; +#if PY_MINOR_VERSION >= 8 + PyStatus status; + + PyConfig config; + PyConfig_InitPythonConfig(&config); + config.isolated = 1; + + // const char* dummy_args[] + // = {"Python", NULL}; // const is needed because literals + // // must not be modified + // char* const* argv = dummy_args; + // char* const* argv = nullptr; + // const char* argv_[] = {"PyPlot", NULL}; + const char* const argv_[] = {"PyPlot", NULL}; + // int argc = sizeof(dummy_args) / sizeof(dummy_args[0]) - 1; + int argc_ = sizeof(argv_) / sizeof(argv_[0]) - 1; + // int argc=1; + + /* Decode command line arguments. + Implicitly preinitialize Python (in isolated mode). */ + status = PyConfig_SetBytesArgv(&config, argc_, (char* const*)argv_); + if (PyStatus_Exception(status)) { + // goto exception; + throw std::runtime_error(status.err_msg); + } + + status = Py_InitializeFromConfig(&config); + if (PyStatus_Exception(status)) { + throw std::runtime_error("Initialization from config failed"); + // goto exception; + } + PyConfig_Clear(&config); + #else - char name[] = "plotting"; -#endif - Py_SetProgramName(name); - Py_Initialize(); - wchar_t const* dummy_args[] - = {L"Python", NULL}; // const is needed because literals - // must not be modified - wchar_t const** argv = dummy_args; - int argc = sizeof(dummy_args) / sizeof(dummy_args[0]) - 1; + wchar_t name[] = L"plotting"; + Py_SetProgramName(name); + Py_Initialize(); -#if PY_MAJOR_VERSION >= 3 - PySys_SetArgv(argc, const_cast(argv)); + wchar_t const* dummy_args[] + = {L"Python", NULL}; // const is needed because literals + // must not be modified + wchar_t const** argv = dummy_args; + int argc = sizeof(dummy_args) / sizeof(dummy_args[0]) - 1; + + PySys_SetArgv(argc, const_cast(argv)); +#endif #else - PySys_SetArgv(argc, (char**)(argv)); + PySys_SetArgv(argc, (char**)(argv)); #endif #ifndef WITHOUT_NUMPY - import_numpy(); // initialize numpy C-API + import_numpy(); // initialize numpy C-API #endif - PyObject* matplotlibname = PyString_FromString("matplotlib"); - PyObject* pyplotname = PyString_FromString("matplotlib.pyplot"); - PyObject* cmname = PyString_FromString("matplotlib.cm"); - PyObject* pylabname = PyString_FromString("pylab"); - if(!pyplotname || !pylabname || !matplotlibname || !cmname) { - throw std::runtime_error("couldnt create string"); - } - - PyObject* matplotlib = PyImport_Import(matplotlibname); - - Py_DECREF(matplotlibname); - if(!matplotlib) { - PyErr_Print(); - throw std::runtime_error( - "Error loading module matplotlib!"); - } - - // matplotlib.use() must be called *before* pylab, - // matplotlib.pyplot, or matplotlib.backends is imported for the - // first time - if(!s_backend.empty()) { - PyObject_CallMethod(matplotlib, const_cast("use"), - const_cast("s"), - s_backend.c_str()); - } - - PyObject* pymod = PyImport_Import(pyplotname); - Py_DECREF(pyplotname); - if(!pymod) { - throw std::runtime_error( - "Error loading module matplotlib.pyplot!"); - } - - s_python_colormap = PyImport_Import(cmname); - Py_DECREF(cmname); - if(!s_python_colormap) { - throw std::runtime_error( - "Error loading module matplotlib.cm!"); - } - - PyObject* pylabmod = PyImport_Import(pylabname); - Py_DECREF(pylabname); - if(!pylabmod) { - throw std::runtime_error("Error loading module pylab!"); - } - - s_python_function_arrow = safe_import(pymod, "arrow"); - s_python_function_show = safe_import(pymod, "show"); - s_python_function_close = safe_import(pymod, "close"); - s_python_function_draw = safe_import(pymod, "draw"); - s_python_function_pause = safe_import(pymod, "pause"); - s_python_function_figure = safe_import(pymod, "figure"); - s_python_function_fignum_exists - = safe_import(pymod, "fignum_exists"); - s_python_function_plot = safe_import(pymod, "plot"); - s_python_function_quiver = safe_import(pymod, "quiver"); - s_python_function_contour = safe_import(pymod, "contour"); - s_python_function_semilogx = safe_import(pymod, "semilogx"); - s_python_function_semilogy = safe_import(pymod, "semilogy"); - s_python_function_loglog = safe_import(pymod, "loglog"); - s_python_function_fill = safe_import(pymod, "fill"); - s_python_function_fill_between - = safe_import(pymod, "fill_between"); - s_python_function_hist = safe_import(pymod, "hist"); - s_python_function_scatter = safe_import(pymod, "scatter"); - s_python_function_boxplot = safe_import(pymod, "boxplot"); - s_python_function_subplot = safe_import(pymod, "subplot"); - s_python_function_subplot2grid - = safe_import(pymod, "subplot2grid"); - s_python_function_legend = safe_import(pymod, "legend"); - s_python_function_xlim = safe_import(pymod, "xlim"); - s_python_function_ylim = safe_import(pymod, "ylim"); - s_python_function_title = safe_import(pymod, "title"); - s_python_function_axis = safe_import(pymod, "axis"); - s_python_function_axhline = safe_import(pymod, "axhline"); - s_python_function_axvline = safe_import(pymod, "axvline"); - s_python_function_axvspan = safe_import(pymod, "axvspan"); - s_python_function_xlabel = safe_import(pymod, "xlabel"); - s_python_function_ylabel = safe_import(pymod, "ylabel"); - s_python_function_gca = safe_import(pymod, "gca"); - s_python_function_xticks = safe_import(pymod, "xticks"); - s_python_function_yticks = safe_import(pymod, "yticks"); - s_python_function_margins = safe_import(pymod, "margins"); - s_python_function_tick_params - = safe_import(pymod, "tick_params"); - s_python_function_grid = safe_import(pymod, "grid"); - s_python_function_ion = safe_import(pymod, "ion"); - s_python_function_ginput = safe_import(pymod, "ginput"); - s_python_function_save = safe_import(pylabmod, "savefig"); - s_python_function_annotate = safe_import(pymod, "annotate"); - s_python_function_cla = safe_import(pymod, "cla"); - s_python_function_clf = safe_import(pymod, "clf"); - s_python_function_errorbar = safe_import(pymod, "errorbar"); - s_python_function_tight_layout - = safe_import(pymod, "tight_layout"); - s_python_function_stem = safe_import(pymod, "stem"); - s_python_function_xkcd = safe_import(pymod, "xkcd"); - s_python_function_text = safe_import(pymod, "text"); - s_python_function_suptitle = safe_import(pymod, "suptitle"); - s_python_function_bar = safe_import(pymod, "bar"); - s_python_function_barh = safe_import(pymod, "barh"); - s_python_function_colorbar - = PyObject_GetAttrString(pymod, "colorbar"); - s_python_function_subplots_adjust - = safe_import(pymod, "subplots_adjust"); - s_python_function_rcparams - = PyObject_GetAttrString(pymod, "rcParams"); - s_python_function_spy = PyObject_GetAttrString(pymod, "spy"); + PyObject* matplotlibname = PyString_FromString("matplotlib"); + PyObject* pyplotname = PyString_FromString("matplotlib.pyplot"); + PyObject* cmname = PyString_FromString("matplotlib.cm"); + PyObject* pylabname = PyString_FromString("pylab"); + if(!pyplotname || !pylabname || !matplotlibname || !cmname) { + throw std::runtime_error("couldnt create string"); + } + + PyObject* matplotlib = PyImport_Import(matplotlibname); + + Py_DECREF(matplotlibname); + if(!matplotlib) { + PyErr_Print(); + throw std::runtime_error( + "Error loading module matplotlib!"); + } + + // matplotlib.use() must be called *before* pylab, + // matplotlib.pyplot, or matplotlib.backends is imported for the + // first time + if(!s_backend.empty()) { + PyObject_CallMethod(matplotlib, const_cast("use"), + const_cast("s"), + s_backend.c_str()); + } + + PyObject* pymod = PyImport_Import(pyplotname); + Py_DECREF(pyplotname); + if(!pymod) { + throw std::runtime_error( + "Error loading module matplotlib.pyplot!"); + } + + s_python_colormap = PyImport_Import(cmname); + Py_DECREF(cmname); + if(!s_python_colormap) { + throw std::runtime_error( + "Error loading module matplotlib.cm!"); + } + + PyObject* pylabmod = PyImport_Import(pylabname); + Py_DECREF(pylabname); + if(!pylabmod) { + throw std::runtime_error("Error loading module pylab!"); + } + + s_python_function_arrow = safe_import(pymod, "arrow"); + s_python_function_show = safe_import(pymod, "show"); + s_python_function_close = safe_import(pymod, "close"); + s_python_function_draw = safe_import(pymod, "draw"); + s_python_function_pause = safe_import(pymod, "pause"); + s_python_function_figure = safe_import(pymod, "figure"); + s_python_function_fignum_exists + = safe_import(pymod, "fignum_exists"); + s_python_function_plot = safe_import(pymod, "plot"); + s_python_function_quiver = safe_import(pymod, "quiver"); + s_python_function_contour = safe_import(pymod, "contour"); + s_python_function_semilogx = safe_import(pymod, "semilogx"); + s_python_function_semilogy = safe_import(pymod, "semilogy"); + s_python_function_loglog = safe_import(pymod, "loglog"); + s_python_function_fill = safe_import(pymod, "fill"); + s_python_function_fill_between + = safe_import(pymod, "fill_between"); + s_python_function_hist = safe_import(pymod, "hist"); + s_python_function_scatter = safe_import(pymod, "scatter"); + s_python_function_boxplot = safe_import(pymod, "boxplot"); + s_python_function_subplot = safe_import(pymod, "subplot"); + s_python_function_subplot2grid + = safe_import(pymod, "subplot2grid"); + s_python_function_legend = safe_import(pymod, "legend"); + s_python_function_xlim = safe_import(pymod, "xlim"); + s_python_function_ylim = safe_import(pymod, "ylim"); + s_python_function_title = safe_import(pymod, "title"); + s_python_function_axis = safe_import(pymod, "axis"); + s_python_function_axhline = safe_import(pymod, "axhline"); + s_python_function_axvline = safe_import(pymod, "axvline"); + s_python_function_axvspan = safe_import(pymod, "axvspan"); + s_python_function_xlabel = safe_import(pymod, "xlabel"); + s_python_function_ylabel = safe_import(pymod, "ylabel"); + s_python_function_gca = safe_import(pymod, "gca"); + s_python_function_xticks = safe_import(pymod, "xticks"); + s_python_function_yticks = safe_import(pymod, "yticks"); + s_python_function_margins = safe_import(pymod, "margins"); + s_python_function_tick_params + = safe_import(pymod, "tick_params"); + s_python_function_grid = safe_import(pymod, "grid"); + s_python_function_ion = safe_import(pymod, "ion"); + s_python_function_ginput = safe_import(pymod, "ginput"); + s_python_function_save = safe_import(pylabmod, "savefig"); + s_python_function_annotate = safe_import(pymod, "annotate"); + s_python_function_cla = safe_import(pymod, "cla"); + s_python_function_clf = safe_import(pymod, "clf"); + s_python_function_errorbar = safe_import(pymod, "errorbar"); + s_python_function_tight_layout + = safe_import(pymod, "tight_layout"); + s_python_function_stem = safe_import(pymod, "stem"); + s_python_function_xkcd = safe_import(pymod, "xkcd"); + s_python_function_text = safe_import(pymod, "text"); + s_python_function_suptitle = safe_import(pymod, "suptitle"); + s_python_function_bar = safe_import(pymod, "bar"); + s_python_function_barh = safe_import(pymod, "barh"); + s_python_function_colorbar + = PyObject_GetAttrString(pymod, "colorbar"); + s_python_function_subplots_adjust + = safe_import(pymod, "subplots_adjust"); + s_python_function_rcparams + = PyObject_GetAttrString(pymod, "rcParams"); + s_python_function_spy = PyObject_GetAttrString(pymod, "spy"); #ifndef WITHOUT_NUMPY - s_python_function_imshow = safe_import(pymod, "imshow"); + s_python_function_imshow = safe_import(pymod, "imshow"); #endif - s_python_empty_tuple = PyTuple_New(0); - } + s_python_empty_tuple = PyTuple_New(0); + } ~_interpreter() { Py_Finalize(); } }; @@ -672,7 +706,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -731,7 +765,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -860,7 +894,7 @@ namespace matplotlibcpp PyDict_SetItemString(kwargs, "cmap", python_colormap_coolwarm); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { if(it->first == "linewidth" || it->first == "alpha") { PyDict_SetItemString(kwargs, it->first.c_str(), @@ -947,7 +981,7 @@ namespace matplotlibcpp PyDict_SetItemString(kwargs, "cmap", python_colormap_coolwarm); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -978,7 +1012,7 @@ namespace matplotlibcpp PyFloat_FromDouble(markersize)); } for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -1052,7 +1086,7 @@ namespace matplotlibcpp PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -1124,7 +1158,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -1200,7 +1234,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -1307,7 +1341,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -1508,7 +1542,7 @@ namespace matplotlibcpp PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -1643,7 +1677,7 @@ namespace matplotlibcpp PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -1694,7 +1728,7 @@ namespace matplotlibcpp PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -1788,7 +1822,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -1830,7 +1864,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -1905,7 +1939,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -2073,7 +2107,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -2408,7 +2442,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -2442,7 +2476,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -2625,7 +2659,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -2704,7 +2738,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -2771,7 +2805,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -2915,7 +2949,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -2946,7 +2980,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -3307,7 +3341,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -3400,7 +3434,7 @@ namespace matplotlibcpp // dispatch to is_callable_impl or is_callable_impl depending on whether T is of class type or not typedef typename is_callable_impl::value, T>::type - type; + type; }; template @@ -3415,46 +3449,46 @@ namespace matplotlibcpp template bool operator()(const IterableX& x, const IterableY& y, const std::string& format) - { - detail::_interpreter::get(); - - // 2-phase lookup for distance, begin, end - using std::begin; - using std::distance; - using std::end; - - auto xs = distance(begin(x), end(x)); - auto ys = distance(begin(y), end(y)); - - assert(xs == ys - && "x and y must have the same number of elements!"); - - // No PyArray, must use lists, and they must have the same - // length. - PyObject* xlist = PyList_New(xs); - PyObject* ylist = PyList_New(ys); - PyObject* pystring = PyString_FromString(format.c_str()); - - auto itx = begin(x), ity = begin(y); - for(decltype(xs) i = 0; i < xs; ++i) { - PyList_SetItem(xlist, i, PyFloat_FromDouble(*itx++)); - PyList_SetItem(ylist, i, PyFloat_FromDouble(*ity++)); - } - - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); - - PyObject* res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_plot, - plot_args); - - Py_DECREF(plot_args); - if(res) Py_DECREF(res); - - return res; - } + { + detail::_interpreter::get(); + + // 2-phase lookup for distance, begin, end + using std::begin; + using std::distance; + using std::end; + + auto xs = distance(begin(x), end(x)); + auto ys = distance(begin(y), end(y)); + + assert(xs == ys + && "x and y must have the same number of elements!"); + + // No PyArray, must use lists, and they must have the same + // length. + PyObject* xlist = PyList_New(xs); + PyObject* ylist = PyList_New(ys); + PyObject* pystring = PyString_FromString(format.c_str()); + + auto itx = begin(x), ity = begin(y); + for(decltype(xs) i = 0; i < xs; ++i) { + PyList_SetItem(xlist, i, PyFloat_FromDouble(*itx++)); + PyList_SetItem(ylist, i, PyFloat_FromDouble(*ity++)); + } + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_plot, + plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; + } }; #else @@ -3465,62 +3499,62 @@ namespace matplotlibcpp template bool operator()(const IterableX& x, const IterableY& y, const std::string& format) - { - // pyassert(PyArray_API, "NumPy needed"); + { + // pyassert(PyArray_API, "NumPy needed"); - detail::_interpreter::get(); + detail::_interpreter::get(); - // 2-phase lookup for distance, begin, end - using std::begin; - using std::distance; - using std::end; + // 2-phase lookup for distance, begin, end + using std::begin; + using std::distance; + using std::end; - auto xs = distance(begin(x), end(x)); - auto ys = distance(begin(y), end(y)); + auto xs = distance(begin(x), end(x)); + auto ys = distance(begin(y), end(y)); - assert(ys % xs == 0 - && "length of y must be a multiple of length of x!"); + assert(ys % xs == 0 + && "length of y must be a multiple of length of x!"); - typedef typename IterableX::value_type NumberX; - typedef typename IterableY::value_type NumberY; + typedef typename IterableX::value_type NumberX; + typedef typename IterableY::value_type NumberY; - NPY_TYPES xtype = detail::select_npy_type::type; - NPY_TYPES ytype = detail::select_npy_type::type; + NPY_TYPES xtype = detail::select_npy_type::type; + NPY_TYPES ytype = detail::select_npy_type::type; - npy_intp xsize = xs; - npy_intp yrows = xsize, ycols = ys / yrows; - npy_intp ysize[] = {yrows, ycols}; // ysize[0] must equal xsize + npy_intp xsize = xs; + npy_intp yrows = xsize, ycols = ys / yrows; + npy_intp ysize[] = {yrows, ycols}; // ysize[0] must equal xsize - PyObject* xarray - = PyArray_New(&PyArray_Type, 1, &xsize, xtype, nullptr, - nullptr, 0, NPY_ARRAY_FARRAY, nullptr); - PyObject* yarray = PyArray_New( - &PyArray_Type, 2, ysize, ytype, nullptr, nullptr, 0, - NPY_ARRAY_FARRAY, nullptr); // column major! - PyObject* pystring = PyString_FromString(format.c_str()); + PyObject* xarray + = PyArray_New(&PyArray_Type, 1, &xsize, xtype, nullptr, + nullptr, 0, NPY_ARRAY_FARRAY, nullptr); + PyObject* yarray = PyArray_New( + &PyArray_Type, 2, ysize, ytype, nullptr, nullptr, 0, + NPY_ARRAY_FARRAY, nullptr); // column major! + PyObject* pystring = PyString_FromString(format.c_str()); - // fill the data - auto itx = begin(x), ity = begin(y); - NumberX* xdata = (NumberX*)PyArray_DATA((PyArrayObject*)xarray); - for(decltype(xs) i = 0; i < xs; ++i) xdata[i] = *itx++; + // fill the data + auto itx = begin(x), ity = begin(y); + NumberX* xdata = (NumberX*)PyArray_DATA((PyArrayObject*)xarray); + for(decltype(xs) i = 0; i < xs; ++i) xdata[i] = *itx++; - NumberY* ydata = (NumberY*)PyArray_DATA((PyArrayObject*)yarray); - for(decltype(ys) i = 0; i < ys; ++i) ydata[i] = *ity++; + NumberY* ydata = (NumberY*)PyArray_DATA((PyArrayObject*)yarray); + for(decltype(ys) i = 0; i < ys; ++i) ydata[i] = *ity++; - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - PyObject* res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_plot, - plot_args); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_plot, + plot_args); - Py_DECREF(plot_args); - if(res) Py_DECREF(res); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - return res; - } + return res; + } }; #endif @@ -3531,16 +3565,16 @@ namespace matplotlibcpp template bool operator()(const Iterable& ticks, const Callable& f, const std::string& format) - { - if(begin(ticks) == end(ticks)) return true; - - // We could use additional meta-programming to deduce the - // correct element type of y, but all values have to be - // convertible to double anyways - std::vector y; - for(auto x : ticks) y.push_back(f(x)); - return plot_impl()(ticks, y, format); - } + { + if(begin(ticks) == end(ticks)) return true; + + // We could use additional meta-programming to deduce the + // correct element type of y, but all values have to be + // convertible to double anyways + std::vector y; + for(auto x : ticks) y.push_back(f(x)); + return plot_impl()(ticks, y, format); + } }; } // end namespace detail @@ -3556,8 +3590,8 @@ namespace matplotlibcpp bool plot(const A& a, const B& b, const std::string& format, Args... args) { return detail::plot_impl::type>()( - a, b, format) - && plot(args...); + a, b, format) + && plot(args...); } /* @@ -3594,95 +3628,95 @@ namespace matplotlibcpp template Plot(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") - { - detail::_interpreter::get(); + { + detail::_interpreter::get(); - assert(x.size() == y.size()); + assert(x.size() == y.size()); - PyObject* kwargs = PyDict_New(); - if(name != "") - PyDict_SetItemString(kwargs, "label", - PyString_FromString(name.c_str())); + PyObject* kwargs = PyDict_New(); + if(name != "") + PyDict_SetItemString(kwargs, "label", + PyString_FromString(name.c_str())); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - PyObject* pystring = PyString_FromString(format.c_str()); + PyObject* pystring = PyString_FromString(format.c_str()); - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - PyObject* res = PyObject_Call( - detail::_interpreter::get().s_python_function_plot, plot_args, - kwargs); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_plot, plot_args, + kwargs); - Py_DECREF(kwargs); - Py_DECREF(plot_args); + Py_DECREF(kwargs); + Py_DECREF(plot_args); - if(res) { - line = PyList_GetItem(res, 0); + if(res) { + line = PyList_GetItem(res, 0); - if(line) - set_data_fct = PyObject_GetAttrString(line, "set_data"); - else - Py_DECREF(line); - Py_DECREF(res); - } - } + if(line) + set_data_fct = PyObject_GetAttrString(line, "set_data"); + else + Py_DECREF(line); + Py_DECREF(res); + } + } // shorter initialization with name or format only // basically calls line, = plot([], []) Plot(const std::string& name = "", const std::string& format = "") : Plot(name, std::vector(), std::vector(), format) - {} + {} template bool update(const std::vector& x, const std::vector& y) - { - assert(x.size() == y.size()); - if(set_data_fct) { - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); - - PyObject* plot_args = PyTuple_New(2); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - - PyObject* res = PyObject_CallObject(set_data_fct, plot_args); - if(res) Py_DECREF(res); - return res; - } - return false; - } + { + assert(x.size() == y.size()); + if(set_data_fct) { + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + + PyObject* res = PyObject_CallObject(set_data_fct, plot_args); + if(res) Py_DECREF(res); + return res; + } + return false; + } // clears the plot but keep it available bool clear() - { - return update(std::vector(), std::vector()); - } + { + return update(std::vector(), std::vector()); + } // definitely remove this line void remove() - { - if(line) { - auto remove_fct = PyObject_GetAttrString(line, "remove"); - PyObject* args = PyTuple_New(0); - PyObject* res = PyObject_CallObject(remove_fct, args); - if(res) Py_DECREF(res); - } - decref(); - } + { + if(line) { + auto remove_fct = PyObject_GetAttrString(line, "remove"); + PyObject* args = PyTuple_New(0); + PyObject* res = PyObject_CallObject(remove_fct, args); + if(res) Py_DECREF(res); + } + decref(); + } ~Plot() { decref(); } private: void decref() - { - if(line) Py_DECREF(line); - if(set_data_fct) Py_DECREF(set_data_fct); - } + { + if(line) Py_DECREF(line); + if(set_data_fct) Py_DECREF(set_data_fct); + } PyObject* line = nullptr; PyObject* set_data_fct = nullptr; From d7724376ffe91d94f14ff2a3ee69e0e03175bf81 Mon Sep 17 00:00:00 2001 From: Amadeus Date: Wed, 22 Jan 2025 22:04:48 -0500 Subject: [PATCH 15/15] matplotlibcpp.h: fix initialization for python >= 3.8 --- examples/span.cpp | 16 + matplotlibcpp.h | 791 ++++++++++++++++++++++++---------------------- 2 files changed, 431 insertions(+), 376 deletions(-) diff --git a/examples/span.cpp b/examples/span.cpp index a54b4b1..2370595 100644 --- a/examples/span.cpp +++ b/examples/span.cpp @@ -3,7 +3,9 @@ // #include "../matplotlibcpp.h" +#include #include +#include using namespace std; namespace plt = matplotlibcpp; @@ -18,7 +20,21 @@ int main() 3, 3, 3, 3 }; + // vector xticks {1, 2, 3, 4}; + // vector xlabels {"a", "b", "c", "d"}; + vector xticks {1, 1.5, 2, 2.5, 3, 3.5, 4}; + vector xlabels {"aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg"}; + + // https://matplotlib.org/stable/api/text_api.html#matplotlib.text.Text + map kwargs {{"rotation", "45"}, + {"fontsize", "xx-large"}, + {"color", "m"} + }; + plt::plot(span(t, 4), span(data, 12)); + // plt::xticks(xticks, xlabels); + plt::xticks(xticks, xlabels, kwargs); + plt::grid(true); plt::show(); plt::detail::_interpreter::kill(); diff --git a/matplotlibcpp.h b/matplotlibcpp.h index c74274e..48d540f 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -117,11 +117,11 @@ namespace matplotlibcpp that the interpreter is constructed and destroyed within the same thread. - 1: + 1: http://bytes.com/topic/python/answers/793370-multiple-independent-python-interpreters-c-c-program - 2: + 2: https://github.com/lava/matplotlib-cpp/pull/202#issue-436220256 - */ + */ static _interpreter& get() { return interkeeper(false); } @@ -130,222 +130,226 @@ namespace matplotlibcpp // Stores the actual singleton object referenced by `get()` and // `kill()`. static _interpreter& interkeeper(bool should_kill) { - static _interpreter ctx; - if(should_kill) ctx.~_interpreter(); - return ctx; - } + static _interpreter ctx; + if(should_kill) ctx.~_interpreter(); + return ctx; + } PyObject* safe_import(PyObject* module, std::string fname) { - PyObject* fn = PyObject_GetAttrString(module, fname.c_str()); + PyObject* fn = PyObject_GetAttrString(module, fname.c_str()); - if(!fn) - throw std::runtime_error( - std::string("Couldn't find required function: ") - + fname); + if(!fn) + throw std::runtime_error( + std::string("Couldn't find required function: ") + + fname); - if(!PyFunction_Check(fn)) - throw std::runtime_error( - fname - + std::string(" is unexpectedly not a PyFunction.")); + if(!PyFunction_Check(fn)) + throw std::runtime_error( + fname + + std::string(" is unexpectedly not a PyFunction.")); - return fn; - } + return fn; + } private: #ifndef WITHOUT_NUMPY #if PY_MAJOR_VERSION >= 3 void* import_numpy() { - import_array(); // initialize C-API - return NULL; - } + import_array(); // initialize C-API + return NULL; + } #else void import_numpy() { - import_array(); // initialize C-API - } + import_array(); // initialize C-API + } #endif #endif _interpreter() { - // optional but recommended + // optional but recommended // #if PY_MAJOR_VERSION >= 3 // wchar_t name[] = L"plotting"; // #else // char name[] = "plotting"; // #endif - // FIXME: - // https://docs.python.org/3.12/c-api/init_config.html#init-config + // FIXME: + // https://docs.python.org/3.12/c-api/init_config.html#init-config #if PY_MAJOR_VERSION >= 3 #if PY_MINOR_VERSION >= 8 - PyStatus status; - - PyConfig config; - PyConfig_InitPythonConfig(&config); - config.isolated = 1; - - // const char* dummy_args[] - // = {"Python", NULL}; // const is needed because literals - // // must not be modified - // char* const* argv = dummy_args; - // char* const* argv = nullptr; - // const char* argv_[] = {"PyPlot", NULL}; - const char* const argv_[] = {"PyPlot", NULL}; - // int argc = sizeof(dummy_args) / sizeof(dummy_args[0]) - 1; - int argc_ = sizeof(argv_) / sizeof(argv_[0]) - 1; - // int argc=1; - - /* Decode command line arguments. - Implicitly preinitialize Python (in isolated mode). */ - status = PyConfig_SetBytesArgv(&config, argc_, (char* const*)argv_); - if (PyStatus_Exception(status)) { - // goto exception; - throw std::runtime_error(status.err_msg); - } - - status = Py_InitializeFromConfig(&config); - if (PyStatus_Exception(status)) { - throw std::runtime_error("Initialization from config failed"); - // goto exception; - } - PyConfig_Clear(&config); + + // https://docs.python.org/3/c-api/init_config.html + PyStatus status; + + PyConfig config; + PyConfig_InitPythonConfig(&config); + config.isolated = 1; + + // const char* dummy_args[] + // = {"Python", NULL}; // const is needed because literals + // // must not be modified + // char* const* argv = dummy_args; + // char* const* argv = nullptr; + // const char* argv_[] = {"PyPlot", NULL}; + const char* const argv_[] = {"PyPlot", NULL}; + // int argc = sizeof(dummy_args) / sizeof(dummy_args[0]) - 1; + int argc_ = sizeof(argv_) / sizeof(argv_[0]) - 1; + // int argc=1; + + /* Decode command line arguments. + Implicitly preinitialize Python (in isolated mode). */ + status = PyConfig_SetBytesArgv(&config, argc_, (char* const*)argv_); + if (PyStatus_Exception(status)) { + // goto exception; + throw std::runtime_error(status.err_msg); + } + + status = Py_InitializeFromConfig(&config); + if (PyStatus_Exception(status)) { + throw std::runtime_error("Initialization from config failed"); + // goto exception; + } + PyConfig_Clear(&config); + + // Py_RunMain(); #else - wchar_t name[] = L"plotting"; - Py_SetProgramName(name); - Py_Initialize(); + wchar_t name[] = L"plotting"; + Py_SetProgramName(name); + Py_Initialize(); - wchar_t const* dummy_args[] - = {L"Python", NULL}; // const is needed because literals - // must not be modified - wchar_t const** argv = dummy_args; - int argc = sizeof(dummy_args) / sizeof(dummy_args[0]) - 1; + wchar_t const* dummy_args[] + = {L"Python", NULL}; // const is needed because literals + // must not be modified + wchar_t const** argv = dummy_args; + int argc = sizeof(dummy_args) / sizeof(dummy_args[0]) - 1; - PySys_SetArgv(argc, const_cast(argv)); + PySys_SetArgv(argc, const_cast(argv)); #endif #else - PySys_SetArgv(argc, (char**)(argv)); + PySys_SetArgv(argc, (char**)(argv)); #endif #ifndef WITHOUT_NUMPY - import_numpy(); // initialize numpy C-API + import_numpy(); // initialize numpy C-API #endif - PyObject* matplotlibname = PyString_FromString("matplotlib"); - PyObject* pyplotname = PyString_FromString("matplotlib.pyplot"); - PyObject* cmname = PyString_FromString("matplotlib.cm"); - PyObject* pylabname = PyString_FromString("pylab"); - if(!pyplotname || !pylabname || !matplotlibname || !cmname) { - throw std::runtime_error("couldnt create string"); - } - - PyObject* matplotlib = PyImport_Import(matplotlibname); - - Py_DECREF(matplotlibname); - if(!matplotlib) { - PyErr_Print(); - throw std::runtime_error( - "Error loading module matplotlib!"); - } - - // matplotlib.use() must be called *before* pylab, - // matplotlib.pyplot, or matplotlib.backends is imported for the - // first time - if(!s_backend.empty()) { - PyObject_CallMethod(matplotlib, const_cast("use"), - const_cast("s"), - s_backend.c_str()); - } - - PyObject* pymod = PyImport_Import(pyplotname); - Py_DECREF(pyplotname); - if(!pymod) { - throw std::runtime_error( - "Error loading module matplotlib.pyplot!"); - } - - s_python_colormap = PyImport_Import(cmname); - Py_DECREF(cmname); - if(!s_python_colormap) { - throw std::runtime_error( - "Error loading module matplotlib.cm!"); - } - - PyObject* pylabmod = PyImport_Import(pylabname); - Py_DECREF(pylabname); - if(!pylabmod) { - throw std::runtime_error("Error loading module pylab!"); - } - - s_python_function_arrow = safe_import(pymod, "arrow"); - s_python_function_show = safe_import(pymod, "show"); - s_python_function_close = safe_import(pymod, "close"); - s_python_function_draw = safe_import(pymod, "draw"); - s_python_function_pause = safe_import(pymod, "pause"); - s_python_function_figure = safe_import(pymod, "figure"); - s_python_function_fignum_exists - = safe_import(pymod, "fignum_exists"); - s_python_function_plot = safe_import(pymod, "plot"); - s_python_function_quiver = safe_import(pymod, "quiver"); - s_python_function_contour = safe_import(pymod, "contour"); - s_python_function_semilogx = safe_import(pymod, "semilogx"); - s_python_function_semilogy = safe_import(pymod, "semilogy"); - s_python_function_loglog = safe_import(pymod, "loglog"); - s_python_function_fill = safe_import(pymod, "fill"); - s_python_function_fill_between - = safe_import(pymod, "fill_between"); - s_python_function_hist = safe_import(pymod, "hist"); - s_python_function_scatter = safe_import(pymod, "scatter"); - s_python_function_boxplot = safe_import(pymod, "boxplot"); - s_python_function_subplot = safe_import(pymod, "subplot"); - s_python_function_subplot2grid - = safe_import(pymod, "subplot2grid"); - s_python_function_legend = safe_import(pymod, "legend"); - s_python_function_xlim = safe_import(pymod, "xlim"); - s_python_function_ylim = safe_import(pymod, "ylim"); - s_python_function_title = safe_import(pymod, "title"); - s_python_function_axis = safe_import(pymod, "axis"); - s_python_function_axhline = safe_import(pymod, "axhline"); - s_python_function_axvline = safe_import(pymod, "axvline"); - s_python_function_axvspan = safe_import(pymod, "axvspan"); - s_python_function_xlabel = safe_import(pymod, "xlabel"); - s_python_function_ylabel = safe_import(pymod, "ylabel"); - s_python_function_gca = safe_import(pymod, "gca"); - s_python_function_xticks = safe_import(pymod, "xticks"); - s_python_function_yticks = safe_import(pymod, "yticks"); - s_python_function_margins = safe_import(pymod, "margins"); - s_python_function_tick_params - = safe_import(pymod, "tick_params"); - s_python_function_grid = safe_import(pymod, "grid"); - s_python_function_ion = safe_import(pymod, "ion"); - s_python_function_ginput = safe_import(pymod, "ginput"); - s_python_function_save = safe_import(pylabmod, "savefig"); - s_python_function_annotate = safe_import(pymod, "annotate"); - s_python_function_cla = safe_import(pymod, "cla"); - s_python_function_clf = safe_import(pymod, "clf"); - s_python_function_errorbar = safe_import(pymod, "errorbar"); - s_python_function_tight_layout - = safe_import(pymod, "tight_layout"); - s_python_function_stem = safe_import(pymod, "stem"); - s_python_function_xkcd = safe_import(pymod, "xkcd"); - s_python_function_text = safe_import(pymod, "text"); - s_python_function_suptitle = safe_import(pymod, "suptitle"); - s_python_function_bar = safe_import(pymod, "bar"); - s_python_function_barh = safe_import(pymod, "barh"); - s_python_function_colorbar - = PyObject_GetAttrString(pymod, "colorbar"); - s_python_function_subplots_adjust - = safe_import(pymod, "subplots_adjust"); - s_python_function_rcparams - = PyObject_GetAttrString(pymod, "rcParams"); - s_python_function_spy = PyObject_GetAttrString(pymod, "spy"); + PyObject* matplotlibname = PyString_FromString("matplotlib"); + PyObject* pyplotname = PyString_FromString("matplotlib.pyplot"); + PyObject* cmname = PyString_FromString("matplotlib.cm"); + PyObject* pylabname = PyString_FromString("pylab"); + if(!pyplotname || !pylabname || !matplotlibname || !cmname) { + throw std::runtime_error("couldnt create string"); + } + + PyObject* matplotlib = PyImport_Import(matplotlibname); + + Py_DECREF(matplotlibname); + if(!matplotlib) { + PyErr_Print(); + throw std::runtime_error( + "Error loading module matplotlib!"); + } + + // matplotlib.use() must be called *before* pylab, + // matplotlib.pyplot, or matplotlib.backends is imported for the + // first time + if(!s_backend.empty()) { + PyObject_CallMethod(matplotlib, const_cast("use"), + const_cast("s"), + s_backend.c_str()); + } + + PyObject* pymod = PyImport_Import(pyplotname); + Py_DECREF(pyplotname); + if(!pymod) { + throw std::runtime_error( + "Error loading module matplotlib.pyplot!"); + } + + s_python_colormap = PyImport_Import(cmname); + Py_DECREF(cmname); + if(!s_python_colormap) { + throw std::runtime_error( + "Error loading module matplotlib.cm!"); + } + + PyObject* pylabmod = PyImport_Import(pylabname); + Py_DECREF(pylabname); + if(!pylabmod) { + throw std::runtime_error("Error loading module pylab!"); + } + + s_python_function_arrow = safe_import(pymod, "arrow"); + s_python_function_show = safe_import(pymod, "show"); + s_python_function_close = safe_import(pymod, "close"); + s_python_function_draw = safe_import(pymod, "draw"); + s_python_function_pause = safe_import(pymod, "pause"); + s_python_function_figure = safe_import(pymod, "figure"); + s_python_function_fignum_exists + = safe_import(pymod, "fignum_exists"); + s_python_function_plot = safe_import(pymod, "plot"); + s_python_function_quiver = safe_import(pymod, "quiver"); + s_python_function_contour = safe_import(pymod, "contour"); + s_python_function_semilogx = safe_import(pymod, "semilogx"); + s_python_function_semilogy = safe_import(pymod, "semilogy"); + s_python_function_loglog = safe_import(pymod, "loglog"); + s_python_function_fill = safe_import(pymod, "fill"); + s_python_function_fill_between + = safe_import(pymod, "fill_between"); + s_python_function_hist = safe_import(pymod, "hist"); + s_python_function_scatter = safe_import(pymod, "scatter"); + s_python_function_boxplot = safe_import(pymod, "boxplot"); + s_python_function_subplot = safe_import(pymod, "subplot"); + s_python_function_subplot2grid + = safe_import(pymod, "subplot2grid"); + s_python_function_legend = safe_import(pymod, "legend"); + s_python_function_xlim = safe_import(pymod, "xlim"); + s_python_function_ylim = safe_import(pymod, "ylim"); + s_python_function_title = safe_import(pymod, "title"); + s_python_function_axis = safe_import(pymod, "axis"); + s_python_function_axhline = safe_import(pymod, "axhline"); + s_python_function_axvline = safe_import(pymod, "axvline"); + s_python_function_axvspan = safe_import(pymod, "axvspan"); + s_python_function_xlabel = safe_import(pymod, "xlabel"); + s_python_function_ylabel = safe_import(pymod, "ylabel"); + s_python_function_gca = safe_import(pymod, "gca"); + s_python_function_xticks = safe_import(pymod, "xticks"); + s_python_function_yticks = safe_import(pymod, "yticks"); + s_python_function_margins = safe_import(pymod, "margins"); + s_python_function_tick_params + = safe_import(pymod, "tick_params"); + s_python_function_grid = safe_import(pymod, "grid"); + s_python_function_ion = safe_import(pymod, "ion"); + s_python_function_ginput = safe_import(pymod, "ginput"); + s_python_function_save = safe_import(pylabmod, "savefig"); + s_python_function_annotate = safe_import(pymod, "annotate"); + s_python_function_cla = safe_import(pymod, "cla"); + s_python_function_clf = safe_import(pymod, "clf"); + s_python_function_errorbar = safe_import(pymod, "errorbar"); + s_python_function_tight_layout + = safe_import(pymod, "tight_layout"); + s_python_function_stem = safe_import(pymod, "stem"); + s_python_function_xkcd = safe_import(pymod, "xkcd"); + s_python_function_text = safe_import(pymod, "text"); + s_python_function_suptitle = safe_import(pymod, "suptitle"); + s_python_function_bar = safe_import(pymod, "bar"); + s_python_function_barh = safe_import(pymod, "barh"); + s_python_function_colorbar + = PyObject_GetAttrString(pymod, "colorbar"); + s_python_function_subplots_adjust + = safe_import(pymod, "subplots_adjust"); + s_python_function_rcparams + = PyObject_GetAttrString(pymod, "rcParams"); + s_python_function_spy = PyObject_GetAttrString(pymod, "spy"); #ifndef WITHOUT_NUMPY - s_python_function_imshow = safe_import(pymod, "imshow"); + s_python_function_imshow = safe_import(pymod, "imshow"); #endif - s_python_empty_tuple = PyTuple_New(0); - } + s_python_empty_tuple = PyTuple_New(0); + } ~_interpreter() { Py_Finalize(); } }; @@ -706,7 +710,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -765,7 +769,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -894,7 +898,7 @@ namespace matplotlibcpp PyDict_SetItemString(kwargs, "cmap", python_colormap_coolwarm); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { if(it->first == "linewidth" || it->first == "alpha") { PyDict_SetItemString(kwargs, it->first.c_str(), @@ -981,7 +985,7 @@ namespace matplotlibcpp PyDict_SetItemString(kwargs, "cmap", python_colormap_coolwarm); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -1012,7 +1016,7 @@ namespace matplotlibcpp PyFloat_FromDouble(markersize)); } for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -1086,7 +1090,7 @@ namespace matplotlibcpp PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -1158,7 +1162,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -1234,7 +1238,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -1341,7 +1345,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -1542,7 +1546,7 @@ namespace matplotlibcpp PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -1677,7 +1681,7 @@ namespace matplotlibcpp PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -1728,7 +1732,7 @@ namespace matplotlibcpp PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -1822,7 +1826,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -1864,7 +1868,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -1939,7 +1943,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -2107,7 +2111,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -2442,7 +2446,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -2462,21 +2466,21 @@ namespace matplotlibcpp { detail::_interpreter::get(); - PyObject* llist = PyList_New(labels.size()); - if(llist == nullptr) - throw "Can't allocate labels list in legend() function."; + PyObject* llist = PyList_New(labels.size()); + if(llist == nullptr) + throw "Can't allocate labels list in legend() function."; - // iterate over labels, hopefully not too many! - size_t i=0; - for (const auto& l : labels) { - PyObject* str = PyString_FromString(l.c_str()); - PyList_SET_ITEM(llist, i++, str); - } + // iterate over labels, hopefully not too many! + size_t i=0; + for (const auto& l : labels) { + PyObject* str = PyString_FromString(l.c_str()); + PyList_SET_ITEM(llist, i++, str); + } // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -2487,7 +2491,7 @@ namespace matplotlibcpp PyObject* res = PyObject_Call( detail::_interpreter::get().s_python_function_legend, - args, kwargs); + args, kwargs); // PyObject* res = PyObject_Call( // detail::_interpreter::get().s_python_function_legend, // detail::_interpreter::get().s_python_empty_tuple, kwargs); @@ -2657,12 +2661,30 @@ namespace matplotlibcpp } // construct keyword args + // https://docs.python.org/3/c-api/dict.html + // https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.xticks.html + // https://matplotlib.org/stable/api/text_api.html#matplotlib.text.Text PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), - PyString_FromString(it->second.c_str())); + const auto& [key, val] = *it; + try { + // if it's numeric, add it as number, e.g. rotation = 45 + // https://matplotlib.org/stable/api/text_api.html#matplotlib.text.Text + double x = std::stod(val); + PyDict_SetItemString(kwargs, key.c_str(), PyFloat_FromDouble(x)); + } + catch (const std::exception& e) { + // if it wasn't a number, add it as string + // TODO: allow for other types, e.g. bool + // https://matplotlib.org/stable/api/text_api.html#matplotlib.text.Text + PyDict_SetItemString(kwargs, key.c_str(), + PyString_FromString(val.c_str())); + + } + // PyDict_SetItemString(kwargs, it->first.c_str(), + // PyString_FromString(it->second.c_str())); } PyObject* res = PyObject_Call( @@ -2691,12 +2713,29 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); - for(const auto& [k, v] : keywords) - PyDict_SetItemString(kwargs, k.c_str(), - PyString_FromString(v.c_str())); + for(const auto& [key, val] : keywords){ + try { + // if it's numeric, add it as number, e.g. rotation = 45 + // https://matplotlib.org/stable/api/text_api.html#matplotlib.text.Text + double x = std::stod(val); + PyDict_SetItemString(kwargs, key.c_str(), PyFloat_FromDouble(x)); + } + catch (const std::exception& e) { + // if it wasn't a number, add it as string + // TODO: allow for other types, e.g. bool + // https://matplotlib.org/stable/api/text_api.html#matplotlib.text.Text + PyDict_SetItemString(kwargs, key.c_str(), + PyString_FromString(val.c_str())); + } + } - PyObject* res = PyObject_Call( - detail::_interpreter::get().s_python_function_xticks, args, kwargs); + // PyDict_SetItemString(kwargs, k.c_str(), + // PyString_FromString(v.c_str())); + + auto* xticks = detail::_interpreter::get().s_python_function_xticks; + PyObject* res = PyObject_Call(xticks, args, kwargs); + // PyObject* res = PyObject_Call( + // detail::_interpreter::get().s_python_function_xticks, args, kwargs); Py_DECREF(kwargs); if(!res) throw std::runtime_error("Call to xticks() failed"); @@ -2738,7 +2777,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -2805,7 +2844,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -2949,7 +2988,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -2980,7 +3019,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); @@ -3341,7 +3380,7 @@ namespace matplotlibcpp // construct keyword args PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it - = keywords.begin(); + = keywords.begin(); it != keywords.end(); ++it) { PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); @@ -3434,7 +3473,7 @@ namespace matplotlibcpp // dispatch to is_callable_impl or is_callable_impl depending on whether T is of class type or not typedef typename is_callable_impl::value, T>::type - type; + type; }; template @@ -3449,46 +3488,46 @@ namespace matplotlibcpp template bool operator()(const IterableX& x, const IterableY& y, const std::string& format) - { - detail::_interpreter::get(); - - // 2-phase lookup for distance, begin, end - using std::begin; - using std::distance; - using std::end; - - auto xs = distance(begin(x), end(x)); - auto ys = distance(begin(y), end(y)); - - assert(xs == ys - && "x and y must have the same number of elements!"); - - // No PyArray, must use lists, and they must have the same - // length. - PyObject* xlist = PyList_New(xs); - PyObject* ylist = PyList_New(ys); - PyObject* pystring = PyString_FromString(format.c_str()); - - auto itx = begin(x), ity = begin(y); - for(decltype(xs) i = 0; i < xs; ++i) { - PyList_SetItem(xlist, i, PyFloat_FromDouble(*itx++)); - PyList_SetItem(ylist, i, PyFloat_FromDouble(*ity++)); - } - - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); - - PyObject* res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_plot, - plot_args); - - Py_DECREF(plot_args); - if(res) Py_DECREF(res); - - return res; - } + { + detail::_interpreter::get(); + + // 2-phase lookup for distance, begin, end + using std::begin; + using std::distance; + using std::end; + + auto xs = distance(begin(x), end(x)); + auto ys = distance(begin(y), end(y)); + + assert(xs == ys + && "x and y must have the same number of elements!"); + + // No PyArray, must use lists, and they must have the same + // length. + PyObject* xlist = PyList_New(xs); + PyObject* ylist = PyList_New(ys); + PyObject* pystring = PyString_FromString(format.c_str()); + + auto itx = begin(x), ity = begin(y); + for(decltype(xs) i = 0; i < xs; ++i) { + PyList_SetItem(xlist, i, PyFloat_FromDouble(*itx++)); + PyList_SetItem(ylist, i, PyFloat_FromDouble(*ity++)); + } + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_plot, + plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; + } }; #else @@ -3499,62 +3538,62 @@ namespace matplotlibcpp template bool operator()(const IterableX& x, const IterableY& y, const std::string& format) - { - // pyassert(PyArray_API, "NumPy needed"); + { + // pyassert(PyArray_API, "NumPy needed"); - detail::_interpreter::get(); + detail::_interpreter::get(); - // 2-phase lookup for distance, begin, end - using std::begin; - using std::distance; - using std::end; + // 2-phase lookup for distance, begin, end + using std::begin; + using std::distance; + using std::end; - auto xs = distance(begin(x), end(x)); - auto ys = distance(begin(y), end(y)); + auto xs = distance(begin(x), end(x)); + auto ys = distance(begin(y), end(y)); - assert(ys % xs == 0 - && "length of y must be a multiple of length of x!"); + assert(ys % xs == 0 + && "length of y must be a multiple of length of x!"); - typedef typename IterableX::value_type NumberX; - typedef typename IterableY::value_type NumberY; + typedef typename IterableX::value_type NumberX; + typedef typename IterableY::value_type NumberY; - NPY_TYPES xtype = detail::select_npy_type::type; - NPY_TYPES ytype = detail::select_npy_type::type; + NPY_TYPES xtype = detail::select_npy_type::type; + NPY_TYPES ytype = detail::select_npy_type::type; - npy_intp xsize = xs; - npy_intp yrows = xsize, ycols = ys / yrows; - npy_intp ysize[] = {yrows, ycols}; // ysize[0] must equal xsize + npy_intp xsize = xs; + npy_intp yrows = xsize, ycols = ys / yrows; + npy_intp ysize[] = {yrows, ycols}; // ysize[0] must equal xsize - PyObject* xarray - = PyArray_New(&PyArray_Type, 1, &xsize, xtype, nullptr, - nullptr, 0, NPY_ARRAY_FARRAY, nullptr); - PyObject* yarray = PyArray_New( - &PyArray_Type, 2, ysize, ytype, nullptr, nullptr, 0, - NPY_ARRAY_FARRAY, nullptr); // column major! - PyObject* pystring = PyString_FromString(format.c_str()); + PyObject* xarray + = PyArray_New(&PyArray_Type, 1, &xsize, xtype, nullptr, + nullptr, 0, NPY_ARRAY_FARRAY, nullptr); + PyObject* yarray = PyArray_New( + &PyArray_Type, 2, ysize, ytype, nullptr, nullptr, 0, + NPY_ARRAY_FARRAY, nullptr); // column major! + PyObject* pystring = PyString_FromString(format.c_str()); - // fill the data - auto itx = begin(x), ity = begin(y); - NumberX* xdata = (NumberX*)PyArray_DATA((PyArrayObject*)xarray); - for(decltype(xs) i = 0; i < xs; ++i) xdata[i] = *itx++; + // fill the data + auto itx = begin(x), ity = begin(y); + NumberX* xdata = (NumberX*)PyArray_DATA((PyArrayObject*)xarray); + for(decltype(xs) i = 0; i < xs; ++i) xdata[i] = *itx++; - NumberY* ydata = (NumberY*)PyArray_DATA((PyArrayObject*)yarray); - for(decltype(ys) i = 0; i < ys; ++i) ydata[i] = *ity++; + NumberY* ydata = (NumberY*)PyArray_DATA((PyArrayObject*)yarray); + for(decltype(ys) i = 0; i < ys; ++i) ydata[i] = *ity++; - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - PyObject* res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_plot, - plot_args); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_plot, + plot_args); - Py_DECREF(plot_args); - if(res) Py_DECREF(res); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - return res; - } + return res; + } }; #endif @@ -3565,16 +3604,16 @@ namespace matplotlibcpp template bool operator()(const Iterable& ticks, const Callable& f, const std::string& format) - { - if(begin(ticks) == end(ticks)) return true; - - // We could use additional meta-programming to deduce the - // correct element type of y, but all values have to be - // convertible to double anyways - std::vector y; - for(auto x : ticks) y.push_back(f(x)); - return plot_impl()(ticks, y, format); - } + { + if(begin(ticks) == end(ticks)) return true; + + // We could use additional meta-programming to deduce the + // correct element type of y, but all values have to be + // convertible to double anyways + std::vector y; + for(auto x : ticks) y.push_back(f(x)); + return plot_impl()(ticks, y, format); + } }; } // end namespace detail @@ -3590,8 +3629,8 @@ namespace matplotlibcpp bool plot(const A& a, const B& b, const std::string& format, Args... args) { return detail::plot_impl::type>()( - a, b, format) - && plot(args...); + a, b, format) + && plot(args...); } /* @@ -3628,95 +3667,95 @@ namespace matplotlibcpp template Plot(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") - { - detail::_interpreter::get(); + { + detail::_interpreter::get(); - assert(x.size() == y.size()); + assert(x.size() == y.size()); - PyObject* kwargs = PyDict_New(); - if(name != "") - PyDict_SetItemString(kwargs, "label", - PyString_FromString(name.c_str())); + PyObject* kwargs = PyDict_New(); + if(name != "") + PyDict_SetItemString(kwargs, "label", + PyString_FromString(name.c_str())); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - PyObject* pystring = PyString_FromString(format.c_str()); + PyObject* pystring = PyString_FromString(format.c_str()); - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - PyObject* res = PyObject_Call( - detail::_interpreter::get().s_python_function_plot, plot_args, - kwargs); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_plot, plot_args, + kwargs); - Py_DECREF(kwargs); - Py_DECREF(plot_args); + Py_DECREF(kwargs); + Py_DECREF(plot_args); - if(res) { - line = PyList_GetItem(res, 0); + if(res) { + line = PyList_GetItem(res, 0); - if(line) - set_data_fct = PyObject_GetAttrString(line, "set_data"); - else - Py_DECREF(line); - Py_DECREF(res); - } - } + if(line) + set_data_fct = PyObject_GetAttrString(line, "set_data"); + else + Py_DECREF(line); + Py_DECREF(res); + } + } // shorter initialization with name or format only // basically calls line, = plot([], []) Plot(const std::string& name = "", const std::string& format = "") : Plot(name, std::vector(), std::vector(), format) - {} + {} template bool update(const std::vector& x, const std::vector& y) - { - assert(x.size() == y.size()); - if(set_data_fct) { - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); - - PyObject* plot_args = PyTuple_New(2); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - - PyObject* res = PyObject_CallObject(set_data_fct, plot_args); - if(res) Py_DECREF(res); - return res; - } - return false; - } + { + assert(x.size() == y.size()); + if(set_data_fct) { + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + + PyObject* res = PyObject_CallObject(set_data_fct, plot_args); + if(res) Py_DECREF(res); + return res; + } + return false; + } // clears the plot but keep it available bool clear() - { - return update(std::vector(), std::vector()); - } + { + return update(std::vector(), std::vector()); + } // definitely remove this line void remove() - { - if(line) { - auto remove_fct = PyObject_GetAttrString(line, "remove"); - PyObject* args = PyTuple_New(0); - PyObject* res = PyObject_CallObject(remove_fct, args); - if(res) Py_DECREF(res); - } - decref(); - } + { + if(line) { + auto remove_fct = PyObject_GetAttrString(line, "remove"); + PyObject* args = PyTuple_New(0); + PyObject* res = PyObject_CallObject(remove_fct, args); + if(res) Py_DECREF(res); + } + decref(); + } ~Plot() { decref(); } private: void decref() - { - if(line) Py_DECREF(line); - if(set_data_fct) Py_DECREF(set_data_fct); - } + { + if(line) Py_DECREF(line); + if(set_data_fct) Py_DECREF(set_data_fct); + } PyObject* line = nullptr; PyObject* set_data_fct = nullptr;