diff --git a/.gitignore b/.gitignore index 7622be79..1c4a1b0a 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ # Build /examples/build/* + +# vim temp files +*.sw* diff --git a/Makefile b/Makefile index 4faeb0e5..60e5f659 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,22 @@ -# Use C++11 -CXXFLAGS += -std=c++11 +# Use C++11, dont warn on long-to-float conversion +CXXFLAGS += -std=c++11 -Wno-conversion # Default to using system's default version of python PYTHON_BIN ?= python PYTHON_CONFIG := $(PYTHON_BIN)-config PYTHON_INCLUDE ?= $(shell $(PYTHON_CONFIG) --includes) -CXXFLAGS += $(PYTHON_INCLUDE) +EXTRA_FLAGS := $(PYTHON_INCLUDE) LDFLAGS += $(shell $(PYTHON_CONFIG) --libs) # Either finds numpy or set -DWITHOUT_NUMPY -CXXFLAGS += $(shell $(PYTHON_BIN) $(CURDIR)/numpy_flags.py) -WITHOUT_NUMPY := $(findstring $(CXXFLAGS), WITHOUT_NUMPY) +EXTRA_FLAGS += $(shell $(PYTHON_BIN) $(CURDIR)/numpy_flags.py) +WITHOUT_NUMPY := $(findstring $(EXTRA_FLAGS), WITHOUT_NUMPY) # Examples requiring numpy support to compile -EXAMPLES_NUMPY := surface -EXAMPLES := minimal basic modern animation nonblock xkcd quiver bar fill_inbetween fill update subplot2grid \ - $(if WITHOUT_NUMPY,,$(EXAMPLES_NUMPY)) +EXAMPLES_NUMPY := surface colorbar +EXAMPLES := minimal basic modern animation nonblock xkcd quiver bar \ + fill_inbetween fill update subplot2grid lines3d \ + $(if $(WITHOUT_NUMPY),,$(EXAMPLES_NUMPY)) # Prefix every example with 'examples/build/' EXAMPLE_TARGETS := $(patsubst %,examples/build/%,$(EXAMPLES)) @@ -24,10 +25,14 @@ EXAMPLE_TARGETS := $(patsubst %,examples/build/%,$(EXAMPLES)) examples: $(EXAMPLE_TARGETS) +docs: + doxygen + moxygen doc/xml --noindex -o doc/api.md + # Assume every *.cpp file is a separate example -$(EXAMPLE_TARGETS): examples/build/%: examples/%.cpp +$(EXAMPLE_TARGETS): examples/build/%: examples/%.cpp matplotlibcpp.h mkdir -p examples/build - $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS) + $(CXX) -o $@ $< $(EXTRA_FLAGS) $(CXXFLAGS) $(LDFLAGS) clean: rm -f ${EXAMPLE_TARGETS} diff --git a/README.md b/README.md index 338bea7b..72bab39f 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,21 @@ matplotlib-cpp. If you prefer to use CMake as build system, you will want to add something like this to your CMakeLists.txt: + +**Recommended way (since CMake 3.12):** + +It's easy to use cmake official [docs](https://cmake.org/cmake/help/git-stage/module/FindPython2.html#module:FindPython2) to find Python 2(or 3) interpreter, compiler and development environment (include directories and libraries). + +NumPy is optional here, delete it from cmake script, if you don't need it. + +```cmake +find_package(Python2 COMPONENTS Development NumPy) +target_include_directories(myproject PRIVATE ${Python2_INCLUDE_DIRS} ${Python2_NumPy_INCLUDE_DIRS}) +target_link_libraries(myproject Python2::Python Python2::NumPy) +``` + +**Alternative way (for CMake <= 3.11):** + ```cmake find_package(PythonLibs 2.7) target_include_directories(myproject PRIVATE ${PYTHON_INCLUDE_DIRS}) @@ -273,3 +288,6 @@ Todo/Issues/Wishlist * If you use Anaconda on Windows, you might need to set PYTHONHOME to Anaconda home directory and QT_QPA_PLATFORM_PLUGIN_PATH to %PYTHONHOME%Library/plugins/platforms. The latter is for especially when you get the error which says 'This application failed to start because it could not find or load the Qt platform plugin "windows" in "".' + +* MacOS: `Unable to import matplotlib.pyplot`. Cause: In mac os image rendering back end of matplotlib (what-is-a-backend to render using the API of Cocoa by default). There is Qt4Agg and GTKAgg and as a back-end is not the default. Set the back end of macosx that is differ compare with other windows or linux os. +Solution is discribed [here](https://stackoverflow.com/questions/21784641/installation-issue-with-matplotlib-python?noredirect=1&lq=1), additional information can be found there too(see links in answers). diff --git a/examples/colorbar.cpp b/examples/colorbar.cpp new file mode 100644 index 00000000..f53e01da --- /dev/null +++ b/examples/colorbar.cpp @@ -0,0 +1,32 @@ +#define _USE_MATH_DEFINES +#include +#include +#include "../matplotlibcpp.h" + +using namespace std; +namespace plt = matplotlibcpp; + +int main() +{ + // Prepare data + int ncols = 500, nrows = 300; + std::vector z(ncols * nrows); + for (int j=0; j + +namespace plt = matplotlibcpp; + +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); + r = z[i]*z[i] + 1; + x.push_back(r * sin(theta)); + y.push_back(r * cos(theta)); + } + + std::map keywords; + keywords.insert(std::pair("label", "parametric curve") ); + + plt::plot3(x, y, z, keywords); + plt::xlabel("x label"); + plt::ylabel("y label"); + plt::set_zlabel("z label"); // set_zlabel rather than just zlabel, in accordance with the Axes3D method + plt::legend(); + plt::show(); +} diff --git a/examples/lines3d.png b/examples/lines3d.png new file mode 100644 index 00000000..7a0c478a Binary files /dev/null and b/examples/lines3d.png differ diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 477bf754..0112344c 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -61,6 +61,7 @@ struct _interpreter { 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; @@ -70,8 +71,10 @@ struct _interpreter { PyObject *s_python_function_ylim; PyObject *s_python_function_title; PyObject *s_python_function_axis; + PyObject *s_python_function_axvline; 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_tick_params; @@ -87,6 +90,7 @@ struct _interpreter { PyObject *s_python_function_text; PyObject *s_python_function_suptitle; PyObject *s_python_function_bar; + PyObject *s_python_function_colorbar; PyObject *s_python_function_subplots_adjust; @@ -195,17 +199,20 @@ struct _interpreter { 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_ylim = safe_import(pymod, "ylim"); s_python_function_title = safe_import(pymod, "title"); s_python_function_axis = safe_import(pymod, "axis"); + s_python_function_axvline = safe_import(pymod, "axvline"); 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_tick_params = safe_import(pymod, "tick_params"); + s_python_function_tick_params = safe_import(pymod, "tick_params"); s_python_function_grid = safe_import(pymod, "grid"); s_python_function_xlim = safe_import(pymod, "xlim"); s_python_function_ion = safe_import(pymod, "ion"); @@ -220,11 +227,11 @@ struct _interpreter { 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_colorbar = PyObject_GetAttrString(pymod, "colorbar"); s_python_function_subplots_adjust = safe_import(pymod,"subplots_adjust"); #ifndef WITHOUT_NUMPY s_python_function_imshow = safe_import(pymod, "imshow"); #endif - s_python_empty_tuple = PyTuple_New(0); } @@ -235,7 +242,15 @@ struct _interpreter { } // end namespace detail -// must be called before the first regular call to matplotlib to have any effect +/// 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; @@ -265,6 +280,8 @@ inline bool annotate(std::string annotation, double x, double y) return res; } +namespace detail { + #ifndef WITHOUT_NUMPY // Type selector for numpy array conversion template struct select_npy_type { const static NPY_TYPES type = NPY_NOTYPE; }; //Default @@ -337,14 +354,40 @@ PyObject* get_array(const std::vector& v) #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 template bool plot(const std::vector &x, const std::vector &y, const std::map& keywords) { assert(x.size() == y.size()); // using numpy arrays - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); // construct positional args PyObject* args = PyTuple_New(2); @@ -368,7 +411,7 @@ bool plot(const std::vector &x, const std::vector &y, const st } // TODO - it should be possible to make this work by implementing -// a non-numpy alternative for `get_2darray()`. +// a non-numpy alternative for `detail::get_2darray()`. #ifndef WITHOUT_NUMPY template void plot_surface(const std::vector<::std::vector> &x, @@ -402,9 +445,9 @@ void plot_surface(const std::vector<::std::vector> &x, assert(y.size() == z.size()); // using numpy arrays - PyObject *xarray = get_2darray(x); - PyObject *yarray = get_2darray(y); - PyObject *zarray = get_2darray(z); + 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); @@ -463,6 +506,88 @@ void plot_surface(const std::vector<::std::vector> &x, } #endif // WITHOUT_NUMPY +template +void plot3(const std::vector &x, + const std::vector &y, + const std::vector &z, + const std::map &keywords = + std::map()) +{ + // 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 = + 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); + + 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) @@ -470,8 +595,8 @@ bool stem(const std::vector &x, const std::vector &y, const st assert(x.size() == y.size()); // using numpy arrays - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); // construct positional args PyObject* args = PyTuple_New(2); @@ -503,8 +628,8 @@ bool fill(const std::vector& x, const std::vector& y, const st assert(x.size() == y.size()); // using numpy arrays - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); // construct positional args PyObject* args = PyTuple_New(2); @@ -534,9 +659,9 @@ bool fill_between(const std::vector& x, const std::vector& y1, assert(x.size() == y2.size()); // using numpy arrays - PyObject* xarray = get_array(x); - PyObject* y1array = get_array(y1); - PyObject* y2array = get_array(y2); + PyObject* xarray = detail::get_array(x); + PyObject* y1array = detail::get_array(y1); + PyObject* y2array = detail::get_array(y2); // construct positional args PyObject* args = PyTuple_New(3); @@ -564,7 +689,7 @@ bool hist(const std::vector& y, long bins=10,std::string color="b", double alpha=1.0, bool cumulative=false) { - PyObject* yarray = get_array(y); + PyObject* yarray = detail::get_array(y); PyObject* kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "bins", PyLong_FromLong(bins)); @@ -588,74 +713,79 @@ bool hist(const std::vector& y, long bins=10,std::string color="b", } #ifndef WITHOUT_NUMPY - namespace internal { - inline void imshow(void *ptr, const NPY_TYPES type, const int rows, const int columns, const int colors, const std::map &keywords) - { - assert(type == NPY_UINT8 || type == NPY_FLOAT); - assert(colors == 1 || colors == 3 || colors == 4); - - detail::_interpreter::get(); //interpreter needs to be initialized for the numpy commands to work - - // 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"); - 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 = {}) - { - internal::imshow((void *) ptr, NPY_UINT8, rows, columns, colors, keywords); - } +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(); //interpreter needs to be initialized for the numpy commands to work + + // 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)); - inline void imshow(const float *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}) + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) { - internal::imshow((void *) ptr, NPY_FLOAT, rows, columns, colors, keywords); + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); } -#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())); - } + 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); +} - // 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); - } +} // 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); +} + +#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())); + } - internal::imshow(image2.data, npy_type, image2.rows, image2.cols, image2.channels(), keywords); + // 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 @@ -667,8 +797,8 @@ bool scatter(const std::vector& x, { assert(x.size() == y.size()); - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); PyObject* kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "s", PyLong_FromLong(s)); @@ -690,6 +820,62 @@ bool scatter(const std::vector& x, return res; } +template +bool boxplot(const std::vector>& data, + const std::vector& labels = {}, + const std::unordered_map & keywords = {}) +{ + 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; +} + +template +bool boxplot(const std::vector& data, + const std::unordered_map & keywords = {}) +{ + PyObject* vector = detail::get_array(data); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, vector); + + PyObject* kwargs = PyDict_New(); + 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; +} + template bool bar(const std::vector & x, const std::vector & y, @@ -697,8 +883,8 @@ bool bar(const std::vector & x, std::string ls = "-", double lw = 1.0, const std::map & keywords = {}) { - PyObject * xarray = get_array(x); - PyObject * yarray = get_array(y); + PyObject * xarray = detail::get_array(x); + PyObject * yarray = detail::get_array(y); PyObject * kwargs = PyDict_New(); @@ -767,7 +953,7 @@ inline bool subplots_adjust(const std::map& keywords = {}) template< typename Numeric> bool named_hist(std::string label,const std::vector& y, long bins=10, std::string color="b", double alpha=1.0) { - PyObject* yarray = get_array(y); + PyObject* yarray = detail::get_array(y); PyObject* kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "label", PyString_FromString(label.c_str())); @@ -793,8 +979,8 @@ bool plot(const std::vector& x, const std::vector& y, const { assert(x.size() == y.size()); - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); PyObject* pystring = PyString_FromString(s.c_str()); @@ -816,10 +1002,10 @@ bool quiver(const std::vector& x, const std::vector& y, cons { assert(x.size() == y.size() && x.size() == u.size() && u.size() == w.size()); - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); - PyObject* uarray = get_array(u); - PyObject* warray = get_array(w); + 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); @@ -850,8 +1036,8 @@ bool stem(const std::vector& x, const std::vector& y, const { assert(x.size() == y.size()); - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); PyObject* pystring = PyString_FromString(s.c_str()); @@ -875,8 +1061,8 @@ bool semilogx(const std::vector& x, const std::vector& y, co { assert(x.size() == y.size()); - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); PyObject* pystring = PyString_FromString(s.c_str()); @@ -898,8 +1084,8 @@ bool semilogy(const std::vector& x, const std::vector& y, co { assert(x.size() == y.size()); - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); PyObject* pystring = PyString_FromString(s.c_str()); @@ -921,8 +1107,8 @@ bool loglog(const std::vector& x, const std::vector& y, cons { assert(x.size() == y.size()); - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); PyObject* pystring = PyString_FromString(s.c_str()); @@ -944,9 +1130,9 @@ bool errorbar(const std::vector &x, const std::vector &y, co { assert(x.size() == y.size()); - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); - PyObject* yerrarray = get_array(yerr); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + PyObject* yerrarray = detail::get_array(yerr); // construct keyword args PyObject* kwargs = PyDict_New(); @@ -980,7 +1166,7 @@ bool named_plot(const std::string& name, const std::vector& y, const st PyObject* kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); - PyObject* yarray = get_array(y); + PyObject* yarray = detail::get_array(y); PyObject* pystring = PyString_FromString(format.c_str()); @@ -1004,8 +1190,8 @@ bool named_plot(const std::string& name, const std::vector& x, const st PyObject* kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); PyObject* pystring = PyString_FromString(format.c_str()); @@ -1029,8 +1215,8 @@ bool named_semilogx(const std::string& name, const std::vector& x, cons PyObject* kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); PyObject* pystring = PyString_FromString(format.c_str()); @@ -1054,8 +1240,8 @@ bool named_semilogy(const std::string& name, const std::vector& x, cons PyObject* kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); PyObject* pystring = PyString_FromString(format.c_str()); @@ -1079,8 +1265,8 @@ bool named_loglog(const std::string& name, const std::vector& x, const PyObject* kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); PyObject* pystring = PyString_FromString(format.c_str()); @@ -1136,6 +1322,27 @@ void text(Numeric x, Numeric y, const std::string& s = "") 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."); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, mappable); + + 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* 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 long figure(long number = -1) { @@ -1290,7 +1497,7 @@ inline void xticks(const std::vector &ticks, const std::vector &ticks, const std::vector& keywords, cons PyObject* args; args = PyTuple_New(1); PyTuple_SetItem(args, 0, PyString_FromString(axis.c_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* res = PyObject_Call(detail::_interpreter::get().s_python_function_tick_params, args, kwargs); - + Py_DECREF(args); Py_DECREF(kwargs); if (!res) throw std::runtime_error("Call to tick_params() failed"); - + Py_DECREF(res); } @@ -1493,6 +1700,29 @@ inline void axis(const std::string &axisstr) Py_DECREF(res); } +inline void axvline(double x, double ymin = 0., double ymax = 1., const std::map& keywords = std::map()) +{ + // 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)); + + // 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_axvline, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + + if(res) Py_DECREF(res); +} + inline void xlabel(const std::string &str, const std::map &keywords = {}) { PyObject* pystr = PyString_FromString(str.c_str()); @@ -1531,6 +1761,57 @@ inline void ylabel(const std::string &str, const std::map& keywords = {}) { + // 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!"); } + } + + PyObject* pystr = PyString_FromString(str.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pystr); + + 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 *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 *zlabel = PyObject_GetAttrString(ax, "set_zlabel"); + if (!zlabel) throw std::runtime_error("Attribute set_zlabel not found."); + Py_INCREF(zlabel); + + PyObject *res = PyObject_Call(zlabel, args, kwargs); + if (!res) throw std::runtime_error("Call to set_zlabel() failed."); + Py_DECREF(zlabel); + + Py_DECREF(ax); + Py_DECREF(args); + Py_DECREF(kwargs); + if (res) Py_DECREF(res); +} + inline void grid(bool flag) { PyObject* pyflag = flag ? Py_True : Py_False; @@ -1830,7 +2111,6 @@ inline bool plot(const std::vector& x, const std::vector& y, con /* * This class allows dynamic plots, ie changing the plotted data without clearing and re-plotting */ - class Plot { public: @@ -1844,8 +2124,8 @@ class Plot if(name != "") PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); PyObject* pystring = PyString_FromString(format.c_str()); @@ -1881,8 +2161,8 @@ class Plot assert(x.size() == y.size()); if(set_data_fct) { - PyObject* xarray = get_array(x); - PyObject* yarray = get_array(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);