From 61da78fbbe4644a8a50fdbfe31a578b2a116ff3e Mon Sep 17 00:00:00 2001 From: Ilya Makarov Date: Fri, 13 Dec 2019 02:28:20 +0700 Subject: [PATCH 01/14] Updated cmake readme section. --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 338bea7b..38391691 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) +``` + +**Legacy way(unrecommended):** + ```cmake find_package(PythonLibs 2.7) target_include_directories(myproject PRIVATE ${PYTHON_INCLUDE_DIRS}) From 7219c6187ffc967219953f04addce34c955649d4 Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Fri, 20 Dec 2019 14:55:57 +0100 Subject: [PATCH 02/14] Updated wording in README. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 38391691..a82b675d 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ 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):** +**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). @@ -224,7 +224,7 @@ target_include_directories(myproject PRIVATE ${Python2_INCLUDE_DIRS} ${Python2_N target_link_libraries(myproject Python2::Python Python2::NumPy) ``` -**Legacy way(unrecommended):** +**Alternative way (for CMake <= 3.11):** ```cmake find_package(PythonLibs 2.7) From f23347fca25219d1c42cbb91608b5556814bf572 Mon Sep 17 00:00:00 2001 From: Ilya Makarov Date: Wed, 18 Dec 2019 00:46:56 +0700 Subject: [PATCH 03/14] Added information about backend renderer issue on MacOS --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index a82b675d..72bab39f 100644 --- a/README.md +++ b/README.md @@ -288,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). From f1af639c4271616d0ae35ae234f4511577c53f30 Mon Sep 17 00:00:00 2001 From: Trevor Keller Date: Sun, 12 May 2019 02:08:12 -0400 Subject: [PATCH 04/14] implement colorbar; requires returned PyObject of mappable object catch call without mappable object stray { add keywords (shrink is particularly useful) --- Makefile | 3 ++- examples/colorbar.cpp | 32 ++++++++++++++++++++++++++++++++ matplotlibcpp.h | 41 +++++++++++++++++++++++++++++++++-------- 3 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 examples/colorbar.cpp diff --git a/Makefile b/Makefile index 4faeb0e5..418554cc 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,8 @@ WITHOUT_NUMPY := $(findstring $(CXXFLAGS), 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 \ +EXAMPLES := minimal basic modern animation nonblock xkcd quiver bar \ + fill_inbetween fill update subplot2grid colorbar \ $(if WITHOUT_NUMPY,,$(EXAMPLES_NUMPY)) # Prefix every example with 'examples/build/' 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& 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) + 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); @@ -613,18 +614,21 @@ bool hist(const std::vector& y, long bins=10,std::string color="b", Py_DECREF(kwargs); if (!res) throw std::runtime_error("Call to imshow() failed"); - Py_DECREF(res); + if (out) + *out = res; + else + Py_DECREF(res); } } - inline void imshow(const unsigned char *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}) + inline void imshow(const unsigned char *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}, PyObject** out = nullptr) { - internal::imshow((void *) ptr, NPY_UINT8, rows, columns, colors, keywords); + internal::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 = {}) + inline void imshow(const float *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}, PyObject** out = nullptr) { - internal::imshow((void *) ptr, NPY_FLOAT, rows, columns, colors, keywords); + internal::imshow((void *) ptr, NPY_FLOAT, rows, columns, colors, keywords, out); } #ifdef WITH_OPENCV @@ -1136,6 +1140,27 @@ void text(Numeric x, Numeric y, const std::string& s = "") Py_DECREF(res); } +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) { From de33a574574ac9d630f50f43f3a1bb02e8e14cfe Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Sat, 28 Mar 2020 01:07:53 +0100 Subject: [PATCH 05/14] Rename make variable CXXFLAGS -> EXTRA_FLAGS Since CXXFLAGS is supposed to hold user-provided flags, the build should not break when a user manually overrides these. --- Makefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 418554cc..7b791a53 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,15 @@ -# 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) +EXTRA_FLAGS += $(shell $(PYTHON_BIN) $(CURDIR)/numpy_flags.py) WITHOUT_NUMPY := $(findstring $(CXXFLAGS), WITHOUT_NUMPY) # Examples requiring numpy support to compile @@ -28,7 +28,7 @@ examples: $(EXAMPLE_TARGETS) # Assume every *.cpp file is a separate example $(EXAMPLE_TARGETS): examples/build/%: examples/%.cpp mkdir -p examples/build - $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS) + $(CXX) -o $@ $< $(EXTRA_FLAGS) $(CXXFLAGS) $(LDFLAGS) clean: rm -f ${EXAMPLE_TARGETS} From d283d47d089a74b095dc314275013b17d860fc3d Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Sat, 28 Mar 2020 01:10:08 +0100 Subject: [PATCH 06/14] Remove indentation around 'imshow()' functions The code had 1-2 extra levels of indentation. --- matplotlibcpp.h | 128 ++++++++++++++++++++++++------------------------ 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 595fc899..1c3adc34 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -589,77 +589,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, 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)); - - // 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); - } - } +namespace internal { - inline void imshow(const unsigned char *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}, PyObject** out = nullptr) - { - internal::imshow((void *) ptr, NPY_UINT8, rows, columns, colors, keywords, out); - } +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 - inline void imshow(const float *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}, PyObject** out = nullptr) + // 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) { - internal::imshow((void *) ptr, NPY_FLOAT, rows, columns, colors, keywords, out); + 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 internal + +inline void imshow(const unsigned char *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}, PyObject** out = nullptr) +{ + internal::imshow((void *) ptr, NPY_UINT8, rows, columns, colors, keywords, out); +} - internal::imshow(image2.data, npy_type, image2.rows, image2.cols, image2.channels(), keywords); +inline void imshow(const float *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}, PyObject** out = nullptr) +{ + internal::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); + } + + internal::imshow(image2.data, npy_type, image2.rows, image2.cols, image2.channels(), keywords); +} #endif // WITH_OPENCV #endif // WITHOUT_NUMPY From 1809d7a261d086cc7ab52383f772fe04c437d1b0 Mon Sep 17 00:00:00 2001 From: Florian Eich Date: Sun, 5 Jan 2020 19:14:16 +0100 Subject: [PATCH 07/14] Adds axvline to function scope --- matplotlibcpp.h | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 1c3adc34..fa9e58ae 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -70,6 +70,7 @@ 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_xticks; @@ -202,6 +203,7 @@ struct _interpreter { 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_xticks = safe_import(pymod, "xticks"); @@ -1411,21 +1413,21 @@ inline void tick_params(const std::map& 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); } @@ -1520,6 +1522,29 @@ inline void axis(const std::string &axisstr) Py_DECREF(res); } +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()); From 52816e2f8b7cf237ecf88d22d26b16f223e74318 Mon Sep 17 00:00:00 2001 From: Florian Eich Date: Mon, 6 Jan 2020 23:40:56 +0100 Subject: [PATCH 08/14] Adds boxplot, get_strarray and get_listlist --- matplotlibcpp.h | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index fa9e58ae..1836c4b7 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; @@ -197,6 +198,7 @@ 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"); @@ -326,6 +328,27 @@ PyObject* get_2darray(const std::vector<::std::vector>& v) return reinterpret_cast(varray); } +// sometimes, for labels and such, we need string arrays +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; +} + #else // fallback if we don't have numpy: copy every element of the given vector template @@ -698,6 +721,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 = 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", 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 = 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, From 9b23ca06b463a0d5b8c4dfdd3afd94cae5e8a4f3 Mon Sep 17 00:00:00 2001 From: Florian Eich Date: Mon, 6 Jan 2020 23:45:40 +0100 Subject: [PATCH 09/14] Adds vim tempfiles to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) 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* From 811ebfb2c9d96536480b38392e215ba4506c2f95 Mon Sep 17 00:00:00 2001 From: Brian Phung Date: Wed, 1 Jan 2020 12:12:21 -0700 Subject: [PATCH 10/14] Add 3D line plot and zlabel function. --- Makefile | 2 +- examples/lines3d.cpp | 30 ++++++++++ examples/lines3d.png | Bin 0 -> 76500 bytes matplotlibcpp.h | 135 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 examples/lines3d.cpp create mode 100644 examples/lines3d.png diff --git a/Makefile b/Makefile index 7b791a53..a4a8a28a 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ WITHOUT_NUMPY := $(findstring $(CXXFLAGS), 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 colorbar \ + fill_inbetween fill update subplot2grid colorbar lines3d \ $(if WITHOUT_NUMPY,,$(EXAMPLES_NUMPY)) # Prefix every example with 'examples/build/' diff --git a/examples/lines3d.cpp b/examples/lines3d.cpp new file mode 100644 index 00000000..f3c201c5 --- /dev/null +++ b/examples/lines3d.cpp @@ -0,0 +1,30 @@ +#include "../matplotlibcpp.h" + +#include + +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 0000000000000000000000000000000000000000..7a0c478a0a598d36abb24b2f1d458d65316a60e6 GIT binary patch literal 76500 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

>5!r2&I7XwI;6E?zG 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 &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 = get_array(x); + PyObject *yarray = get_array(y); + PyObject *zarray = 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) @@ -1662,6 +1746,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; From 41477fcdb6fc3ba2c4967dfada01609bfd620a1d Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Sun, 29 Mar 2020 20:46:52 +0200 Subject: [PATCH 11/14] Add experimental documentation target Add experimental `docs` target to create automated API documentation. Also moved all internal things into one unified namespace `detail`. --- Makefile | 6 +- matplotlibcpp.h | 142 ++++++++++++++++++++++++++---------------------- 2 files changed, 83 insertions(+), 65 deletions(-) diff --git a/Makefile b/Makefile index a4a8a28a..8df417f5 100644 --- a/Makefile +++ b/Makefile @@ -25,8 +25,12 @@ 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 $@ $< $(EXTRA_FLAGS) $(CXXFLAGS) $(LDFLAGS) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index da867048..ed4e3479 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -242,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; @@ -272,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 @@ -365,14 +375,19 @@ PyObject* get_array(const std::vector& v) #endif // WITHOUT_NUMPY +} // 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); @@ -396,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, @@ -430,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); @@ -522,9 +537,9 @@ void plot3(const std::vector &x, assert(x.size() == y.size()); assert(y.size() == z.size()); - PyObject *xarray = get_array(x); - PyObject *yarray = get_array(y); - PyObject *zarray = get_array(z); + 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); @@ -580,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); @@ -613,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); @@ -644,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); @@ -674,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)); @@ -698,7 +713,7 @@ bool hist(const std::vector& y, long bins=10,std::string color="b", } #ifndef WITHOUT_NUMPY -namespace internal { +namespace detail { inline void imshow(void *ptr, const NPY_TYPES type, const int rows, const int columns, const int colors, const std::map &keywords, PyObject** out) { @@ -730,16 +745,16 @@ inline void imshow(void *ptr, const NPY_TYPES type, const int rows, const int co Py_DECREF(res); } -} // namespace internal +} // 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) { - internal::imshow((void *) ptr, NPY_UINT8, rows, columns, colors, keywords, out); + 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) { - internal::imshow((void *) ptr, NPY_FLOAT, rows, columns, colors, keywords, out); + detail::imshow((void *) ptr, NPY_FLOAT, rows, columns, colors, keywords, out); } #ifdef WITH_OPENCV @@ -769,7 +784,7 @@ void imshow(const cv::Mat &image, const std::map &keyw cv::cvtColor(image2, image2, CV_BGRA2RGBA); } - internal::imshow(image2.data, npy_type, image2.rows, image2.cols, image2.channels(), keywords); + detail::imshow(image2.data, npy_type, image2.rows, image2.cols, image2.channels(), keywords); } #endif // WITH_OPENCV #endif // WITHOUT_NUMPY @@ -782,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)); @@ -810,7 +825,7 @@ bool boxplot(const std::vector>& data, const std::vector& labels = {}, const std::unordered_map & keywords = {}) { - PyObject* listlist = get_listlist(data); + PyObject* listlist = detail::get_listlist(data); PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, listlist); @@ -818,7 +833,7 @@ bool boxplot(const std::vector>& data, // kwargs needs the labels, if there are (the correct number of) labels if (!labels.empty() && labels.size() == data.size()) { - PyDict_SetItemString(kwargs, "labels", get_array(labels)); + PyDict_SetItemString(kwargs, "labels", detail::get_array(labels)); } // take care of the remaining keywords @@ -841,7 +856,7 @@ template bool boxplot(const std::vector& data, const std::unordered_map & keywords = {}) { - PyObject* vector = get_array(data); + PyObject* vector = detail::get_array(data); PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, vector); @@ -868,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(); @@ -938,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())); @@ -964,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()); @@ -987,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); @@ -1021,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()); @@ -1046,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()); @@ -1069,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()); @@ -1092,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()); @@ -1115,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(); @@ -1151,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()); @@ -1175,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()); @@ -1200,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()); @@ -1225,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()); @@ -1250,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()); @@ -1482,7 +1497,7 @@ inline void xticks(const std::vector &ticks, const std::vector &ticks, 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: @@ -2110,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()); @@ -2147,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); From 7f7b8528b4f0d4d6cbebc00cf7b39001cbb1a801 Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Tue, 31 Mar 2020 14:56:15 +0200 Subject: [PATCH 12/14] Fix numpy detection and provide some functions even when numpy is not available --- Makefile | 8 ++++---- matplotlibcpp.h | 28 ++++++++++++++-------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index 8df417f5..60e5f659 100644 --- a/Makefile +++ b/Makefile @@ -10,13 +10,13 @@ LDFLAGS += $(shell $(PYTHON_CONFIG) --libs) # Either finds numpy or set -DWITHOUT_NUMPY EXTRA_FLAGS += $(shell $(PYTHON_BIN) $(CURDIR)/numpy_flags.py) -WITHOUT_NUMPY := $(findstring $(CXXFLAGS), WITHOUT_NUMPY) +WITHOUT_NUMPY := $(findstring $(EXTRA_FLAGS), WITHOUT_NUMPY) # Examples requiring numpy support to compile -EXAMPLES_NUMPY := surface +EXAMPLES_NUMPY := surface colorbar EXAMPLES := minimal basic modern animation nonblock xkcd quiver bar \ - fill_inbetween fill update subplot2grid colorbar lines3d \ - $(if WITHOUT_NUMPY,,$(EXAMPLES_NUMPY)) + fill_inbetween fill update subplot2grid lines3d \ + $(if $(WITHOUT_NUMPY),,$(EXAMPLES_NUMPY)) # Prefix every example with 'examples/build/' EXAMPLE_TARGETS := $(patsubst %,examples/build/%,$(EXAMPLES)) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index ed4e3479..55597a70 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -340,6 +340,20 @@ PyObject* get_2darray(const std::vector<::std::vector>& v) 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; +} + +#endif // WITHOUT_NUMPY + // sometimes, for labels and such, we need string arrays PyObject * get_array(const std::vector& strings) { @@ -361,20 +375,6 @@ PyObject* get_listlist(const std::vector>& ll) return listlist; } -#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; -} - -#endif // WITHOUT_NUMPY - } // namespace detail /// Plot a line through the given x and y data points.. From ea527fdcefbc8137921ba1aaacf2ee7fbe5da109 Mon Sep 17 00:00:00 2001 From: tomotakaeru <38675462+tomotakaeru@users.noreply.github.com> Date: Sun, 5 Apr 2020 22:51:41 +0900 Subject: [PATCH 13/14] add "inline" to get_array, color_bar, axvline --- matplotlibcpp.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 55597a70..31838a5f 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -298,7 +298,7 @@ template <> struct select_npy_type { const static NPY_TYPES type = NPY template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; template -PyObject* get_array(const std::vector& v) +inline PyObject* get_array(const std::vector& v) { detail::_interpreter::get(); //interpreter needs to be initialized for the numpy commands to work NPY_TYPES type = select_npy_type::type; @@ -343,7 +343,7 @@ PyObject* get_2darray(const std::vector<::std::vector>& v) #else // fallback if we don't have numpy: copy every element of the given vector template -PyObject* get_array(const std::vector& v) +inline PyObject* get_array(const std::vector& v) { PyObject* list = PyList_New(v.size()); for(size_t i = 0; i < v.size(); ++i) { @@ -355,7 +355,7 @@ PyObject* get_array(const std::vector& v) #endif // WITHOUT_NUMPY // sometimes, for labels and such, we need string arrays -PyObject * get_array(const std::vector& strings) +inline PyObject * get_array(const std::vector& strings) { PyObject* list = PyList_New(strings.size()); for (std::size_t i = 0; i < strings.size(); ++i) { @@ -1322,7 +1322,7 @@ void text(Numeric x, Numeric y, const std::string& s = "") Py_DECREF(res); } -void colorbar(PyObject* mappable = NULL, const std::map& keywords = {}) +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."); @@ -1700,7 +1700,7 @@ inline void axis(const std::string &axisstr) Py_DECREF(res); } -void axvline(double x, double ymin = 0., double ymax = 1., const std::map& keywords = std::map()) +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); From 2843ebbe68f7f1d9718bc6a48bb2f6e16af95349 Mon Sep 17 00:00:00 2001 From: tomotakaeru <38675462+tomotakaeru@users.noreply.github.com> Date: Mon, 6 Apr 2020 09:26:36 +0900 Subject: [PATCH 14/14] delete unnecessary "inline" on get_array(vector) --- matplotlibcpp.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 31838a5f..0112344c 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -298,7 +298,7 @@ template <> struct select_npy_type { const static NPY_TYPES type = NPY template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; template -inline PyObject* get_array(const std::vector& v) +PyObject* get_array(const std::vector& v) { detail::_interpreter::get(); //interpreter needs to be initialized for the numpy commands to work NPY_TYPES type = select_npy_type::type; @@ -343,7 +343,7 @@ PyObject* get_2darray(const std::vector<::std::vector>& v) #else // fallback if we don't have numpy: copy every element of the given vector template -inline PyObject* get_array(const std::vector& v) +PyObject* get_array(const std::vector& v) { PyObject* list = PyList_New(v.size()); for(size_t i = 0; i < v.size(); ++i) {