diff --git a/CMakeLists.txt b/CMakeLists.txt index 7067417..4380252 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ find_package(pybind11 2.4.3 REQUIRED) # check matplotlib minor version execute_process( COMMAND - "python3" "-c" + ${Python3_EXECUTABLE} "-c" "import matplotlib; print(str(matplotlib.__version__))" RESULT_VARIABLE MATPLOTLIB_VERSION_CHECKING diff --git a/README.md b/README.md index b84bec4..3a7db9f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ It is supposed to provide the user with almost full access to matplotlib feature - [pybind11](https://github.com/pybind/pybind11) >= 2.4.3 - `sudo apt install pybind11-dev` (on Ubuntu20.04) - or manual install -- compatible with [matplotlib](https://matplotlib.org/stable/index.html) == 3.5.1 +- [matplotlib](https://matplotlib.org/stable/index.html) >= 3.4.0 - numpy for `mplot3d` - ([xtensor](https://github.com/xtensor-stack/xtensor) == 0.24.0 + [xtl](https://github.com/xtensor-stack/xtl), only for `gallery` demos) diff --git a/gallery/shapes_and_collections/patch_collection.cpp b/gallery/shapes_and_collections/patch_collection.cpp index 3450c9e..f516360 100644 --- a/gallery/shapes_and_collections/patch_collection.cpp +++ b/gallery/shapes_and_collections/patch_collection.cpp @@ -3,18 +3,19 @@ #include #include +#include #include #include -#include -#include +#include + #include namespace py = pybind11; using namespace py::literals; using namespace std; -using namespace matplotlibcpp17; +using namespace matplotlibcpp17::patches; using namespace matplotlibcpp17; int main() { @@ -22,27 +23,30 @@ int main() { auto plt = matplotlibcpp17::pyplot::import(); auto [fig, ax] = plt.subplots(); + const int resolution = 50; const int N = 3; - vector x = {0.7003673, 0.74275081, 0.70928001}, - y = {0.56674552, 0.97778533, 0.70633485}, - radii = {0.02479158, 0.01578834, 0.06976985}; + xt::xarray x = xt::random::rand({N}); + xt::xarray y = xt::random::rand({N}); + xt::xarray radii = 0.1 * xt::random::rand({N}); py::list patches; // instead of patches = [] for (int i = 0; i < N; ++i) { const double x1 = x[i], y1 = y[i], r = radii[i]; auto circle = patches::Circle(Args(py::make_tuple(x1, y1), r)); patches.append(circle.unwrap()); } - x = {0.71995667, 0.25774443, 0.34154678}; - y = {0.96876117, 0.6945071, 0.46638326}; - radii = {0.07028127, 0.05117859, 0.09287414}; - vector theta1 = {266.3169476, 224.07805212, 234.5563688}, - theta2 = {142.85074015, 195.56618216, 287.96383014}; + + x = xt::random::rand({N}); + y = xt::random::rand({N}); + radii = 0.1 * xt::random::rand({N}); + xt::xarray theta1 = 360.0 * xt::random::rand({N}); + xt::xarray theta2 = 360.0 * xt::random::rand({N}); for (int i = 0; i < N; ++i) { const double x1 = x[i], y1 = y[i], r = radii[i], th1 = theta1[i], th2 = theta2[i]; auto wedge = patches::Wedge(Args(py::make_tuple(x1, y1), r, th1, th2)); patches.append(wedge.unwrap()); } + patches.append( patches::Wedge(Args(py::make_tuple(0.3, 0.7), 0.1, 0, 360)).unwrap()); patches.append(patches::Wedge(Args(py::make_tuple(0.7, 0.8), 0.2, 0, 360), @@ -53,16 +57,30 @@ int main() { patches.append(patches::Wedge(Args(py::make_tuple(0.8, 0.3), 0.2, 45, 90), Kwargs("width"_a = 0.10)) .unwrap()); - // NOTE: Polygon take numpy array as argument, so skip it - vector colors_ = {90.63036451, 16.10182093, 74.36211347, 63.29741618, - 32.41800177, 92.23765324, 23.72264387, 82.39455709, - 75.06071403, 11.37844527}; - py::list colors = py::cast(colors_); + + for (int i = 0; i < N; ++i) { + auto poly__ = xt::random::rand({N, 2}); + vector> poly_(N); + // to vector> + for (int j = 0; j < N; ++j) { + poly_[j].resize(2); + poly_[j][0] = poly__(j, 0); + poly_[j][1] = poly__(j, 1); + } + // to numpy array + auto poly = py::array(py::cast(std::move(poly_))); + auto polygon = Polygon(Args(poly, true)); + patches.append(polygon.unwrap()); + } + + auto colors__ = 100.0 * xt::random::rand({patches.size()}); + vector colors_(colors__.begin(), colors__.end()); + py::array colors = py::cast(colors_); auto p = collections::PatchCollection(Args(patches), Kwargs("alpha"_a = 0.4)); p.set_array(Args(colors)); - // NOTE: error in python3.6.9 ? ax.add_collection(Args(p.unwrap())); fig.colorbar(Args(p.unwrap()), Kwargs("ax"_a = ax.unwrap())); + #if USE_GUI plt.show(); #else diff --git a/gallery/subplots_axes_and_figures/CMakeLists.txt b/gallery/subplots_axes_and_figures/CMakeLists.txt index 3390567..769495b 100644 --- a/gallery/subplots_axes_and_figures/CMakeLists.txt +++ b/gallery/subplots_axes_and_figures/CMakeLists.txt @@ -1,12 +1,15 @@ add_demo(align_labels_demo align_labels_demo.cpp) add_demo(gridspec_multicolumn gridspec_multicolumn.cpp) add_demo(multiple_figs_demo multiple_figs_demo.cpp) +add_demo(colorbar_placement colorbar_placement.cpp) +add_demo(subplots subplots.cpp) add_custom_target(subplots_axes_and_figures - DEPENDS align_labels_demo gridspec_multicolumn multiple_figs_demo + DEPENDS align_labels_demo gridspec_multicolumn multiple_figs_demo colorbar_placement COMMAND align_labels_demo COMMAND gridspec_multicolumn COMMAND multiple_figs_demo + COMMAND colorbar_placement COMMENT "subplots_axes_and_figures" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/gallery/subplots_axes_and_figures/colorbar_placement.cpp b/gallery/subplots_axes_and_figures/colorbar_placement.cpp new file mode 100644 index 0000000..1d14344 --- /dev/null +++ b/gallery/subplots_axes_and_figures/colorbar_placement.cpp @@ -0,0 +1,48 @@ +// example from +// https://matplotlib.org/stable/gallery/subplots_axes_and_figures/colorbar_placement.html + +#include +#include + +#include + +#include + +#include + +namespace py = pybind11; +using namespace py::literals; +using namespace std; +using namespace matplotlibcpp17; + +int main1() { + auto plt = matplotlibcpp17::pyplot::import(); + auto [fig, axs] = plt.subplots(2, 2); + const vector cmaps = {"RdBu_r", "viridis"}; + for (auto col : {0, 1}) { + for (auto row : {0, 1}) { + auto x_ = xt::random::randn({20, 20}) * (col + 1.0); + vector> x(20); + for (int i = 0; i < 20; ++i) { + x[i].resize(20); + for (int j = 0; j < 20; ++j) + x[i][j] = x_(i, j); + } + auto &ax = axs[col + row * 2]; + auto pcm = ax.pcolormesh(Args(x), Kwargs("cmap"_a = cmaps[col])); + fig.colorbar(Args(pcm.unwrap()), + Kwargs("ax"_a = ax.unwrap(), "shrink"_a = 0.6)); + } + } +#if USE_GUI + plt.show(); +#else + plt.savefig(Args("colorbar_placement1.png")); +#endif + return 0; +} + +int main() { + py::scoped_interpreter guard{}; + main1(); +} diff --git a/gallery/subplots_axes_and_figures/subplots.cpp b/gallery/subplots_axes_and_figures/subplots.cpp new file mode 100644 index 0000000..074e811 --- /dev/null +++ b/gallery/subplots_axes_and_figures/subplots.cpp @@ -0,0 +1,27 @@ +#include +#include + +#include + +namespace py = pybind11; +using namespace py::literals; +using namespace std; +using namespace matplotlibcpp17; + +int main() { + py::scoped_interpreter guard{}; + auto plt = matplotlibcpp17::pyplot::import(); + { + auto [fig, axs] = plt.subplots(3, 1); + std::cout << axs.size() << std::endl; + } + { + auto [fig, axs] = plt.subplots(1, 1); + std::cout << axs.size() << std::endl; + } + { + auto [fig, axs] = plt.subplots(3, 3); + std::cout << axs.size() << std::endl; + } + return 0; +} diff --git a/include/matplotlibcpp17/axes.h b/include/matplotlibcpp17/axes.h index df89efe..02a2672 100644 --- a/include/matplotlibcpp17/axes.h +++ b/include/matplotlibcpp17/axes.h @@ -123,6 +123,11 @@ struct DECL_STRUCT_ATTR Axes : public BaseWrapper { legend::Legend legend(const pybind11::tuple &args = pybind11::tuple(), const pybind11::dict &kwargs = pybind11::dict()); + // pcolormesh + collections::QuadMesh + pcolormesh(const pybind11::tuple &args = pybind11::tuple(), + const pybind11::dict &kwargs = pybind11::dict()); + // plot pybind11::object plot(const pybind11::tuple &args = pybind11::tuple(), const pybind11::dict &kwargs = pybind11::dict()); @@ -230,6 +235,7 @@ struct DECL_STRUCT_ATTR Axes : public BaseWrapper { LOAD_FUNC_ATTR(hist2d, self); LOAD_FUNC_ATTR(invert_yaxis, self); LOAD_FUNC_ATTR(legend, self); + LOAD_FUNC_ATTR(pcolormesh, self); LOAD_FUNC_ATTR(plot, self); // NOTE: only when called with projection='3d', `plot_surface`, `plot_wireframe`, `set_zlabel` prop exists. try { @@ -278,6 +284,7 @@ struct DECL_STRUCT_ATTR Axes : public BaseWrapper { pybind11::object hist2d_attr; pybind11::object invert_yaxis_attr; pybind11::object legend_attr; + pybind11::object pcolormesh_attr; pybind11::object plot_attr; pybind11::object plot_surface_attr; pybind11::object plot_wireframe_attr; @@ -478,6 +485,13 @@ legend::Legend Axes::legend(const pybind11::tuple &args, return legend::Legend(obj); } +// pcolormesh +collections::QuadMesh Axes::pcolormesh(const pybind11::tuple &args, + const pybind11::dict &kwargs) { + pybind11::object ret = pcolormesh_attr(*args, **kwargs); + return collections::QuadMesh(ret); +} + // plot pybind11::object Axes::plot(const pybind11::tuple &args, const pybind11::dict &kwargs) { diff --git a/include/matplotlibcpp17/collections.h b/include/matplotlibcpp17/collections.h index f488495..e9de566 100644 --- a/include/matplotlibcpp17/collections.h +++ b/include/matplotlibcpp17/collections.h @@ -73,6 +73,15 @@ pybind11::object PatchCollection::set_array(const pybind11::tuple &args, pybind11::object ret = set_array_attr(*args, **kwargs); return ret; } + +/** + * @brief A wrapper class for matplotlib.collections.QuadMesh + **/ +struct DECL_STRUCT_ATTR QuadMesh : public BaseWrapper { +public: + QuadMesh(pybind11::object quadmesh) { self = quadmesh; } +}; + } // namespace matplotlibcpp17::collections #endif /* MATPLOTLIBCPP17_COLLECTIONS_H */ diff --git a/include/matplotlibcpp17/patches.h b/include/matplotlibcpp17/patches.h index 39c0cc4..83ac366 100644 --- a/include/matplotlibcpp17/patches.h +++ b/include/matplotlibcpp17/patches.h @@ -74,6 +74,22 @@ struct DECL_STRUCT_ATTR Wedge : public BaseWrapper { pybind11::object wedge_attr; }; +/** + * @brief A wrapper class for matplotlib.patches.Polygon + **/ +struct DECL_STRUCT_ATTR Polygon : public BaseWrapper { +public: + Polygon(const pybind11::tuple &args = pybind11::tuple(), + const pybind11::dict &kwargs = pybind11::dict()) { + polygon_attr = + pybind11::module::import("matplotlib.patches").attr("Polygon"); + self = polygon_attr(*args, **kwargs); + } + +private: + pybind11::object polygon_attr; +}; + } // namespace matplotlibcpp17::patches #endif /* MATPLOTLIBCPP17_PATCHES_H */ diff --git a/include/matplotlibcpp17/pyplot.h b/include/matplotlibcpp17/pyplot.h index 7e2f43c..59a6aa2 100644 --- a/include/matplotlibcpp17/pyplot.h +++ b/include/matplotlibcpp17/pyplot.h @@ -300,18 +300,29 @@ PyPlot::subplots(const pybind11::dict &kwargs) { std::tuple> PyPlot::subplots(int r, int c, const pybind11::dict &kwargs) { + // subplots() returns [][] (if r > 1 && c > 1) else [] + // return []axes in row-major + // NOTE: equal to Axes.flat pybind11::tuple args = pybind11::make_tuple(r, c); pybind11::list ret = subplots_attr(*args, **kwargs); std::vector axes; pybind11::object fig = ret[0]; figure::Figure figure(fig); if (r == 1 and c == 1) { - pybind11::object ax = ret[1]; - axes.push_back(axes::Axes(ax)); + // python returns Axes + axes.push_back(axes::Axes(ret[1])); + } else if (r == 1 or c == 1) { + // python returns []Axes + pybind11::list axs = ret[1]; + for (int i = 0; i < r * c; ++i) + axes.push_back(axes::Axes(axs[i])); } else { + // python returns [][]Axes pybind11::list axs = ret[1]; for (pybind11::size_t i = 0; i < axs.size(); ++i) { - axes.push_back(axes::Axes(axs[i])); + pybind11::list axsi = axs[i]; + for (unsigned j = 0; j < axsi.size(); ++j) + axes.push_back(axes::Axes(axsi[j])); } } return {figure, axes};