diff --git a/CMakeLists.txt b/CMakeLists.txt index 97ce1a8..7067417 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,29 @@ project( find_package(Python3 COMPONENTS Interpreter Development REQUIRED) find_package(pybind11 2.4.3 REQUIRED) -set(matplotlibcpp17_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include") + +# check matplotlib minor version +execute_process( + COMMAND + "python3" "-c" + "import matplotlib; +print(str(matplotlib.__version__))" + RESULT_VARIABLE MATPLOTLIB_VERSION_CHECKING + OUTPUT_VARIABLE MATPLOTLIB_VERSION + ) + +if(NOT MATPLOTLIB_VERSION_CHECKING MATCHES 0) + message(FATAL_ERROR + "Could not check matplotlib.__version__") +endif() +message(STATUS "Detected matplotlib version is ${MATPLOTLIB_VERSION}") +if(${MATPLOTLIB_VERSION} VERSION_LESS 3.4) + message(WARNING "Detected matplotlib version is < 3.4.0") + set(MATPLOTLIB_MINOR_VER_GTE_4 0) +else() + set(MATPLOTLIB_MINOR_VER_GTE_4 1) +endif() + # gallery if(NOT DEFINED USE_GUI) @@ -20,6 +42,8 @@ if(NOT DEFINED ADD_DEMO) set(ADD_DEMO 1) endif() +set(matplotlibcpp17_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include") + function(add_demo name path) add_executable(${name} ${path}) target_include_directories(${name} PUBLIC @@ -31,9 +55,9 @@ endfunction() if(${ADD_DEMO}) find_package(Python3 COMPONENTS NumPy REQUIRED) - find_package(xtensor REQUIRED) + find_package(xtensor 0.24.0 REQUIRED) set(CMAKE_CXX_STANDARD 17) - set(CMAKE_CXX_FLAGS "-Wall -g -DUSE_GUI=${USE_GUI}") + set(CMAKE_CXX_FLAGS "-Wall -g -DUSE_GUI=${USE_GUI} -DMATPLOTLIB_MINOR_VER_GTE_4=${MATPLOTLIB_MINOR_VER_GTE_4}") add_subdirectory(gallery/lines_bars_and_markers) add_subdirectory(gallery/subplots_axes_and_figures) add_subdirectory(gallery/statistics) @@ -96,6 +120,9 @@ install(EXPORT ${PROJECT_NAME}_Targets install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/${PROJECT_NAME} DESTINATION include ) +set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") +# create .deb +include("${PROJECT_SOURCE_DIR}/cmake/package.cmake") # uninstall target ## actually it's just `xargs rm < install_manifest.txt` diff --git a/cmake/package.cmake b/cmake/package.cmake new file mode 100644 index 0000000..62ec0da --- /dev/null +++ b/cmake/package.cmake @@ -0,0 +1,23 @@ +# https://decovar.dev/blog/2021/09/23/cmake-cpack-package-deb-apt/ +set(CPACK_PACKAGE_NAME ${PROJECT_NAME}) +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${CMAKE_PROJECT_DESCRIPTION}) +set(CPACK_VERBATIM_VARIABLES YES) +set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) +SET(CPACK_OUTPUT_FILE_PREFIX "${PROJECT_BINARY_DIR}/") +set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) +set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) +set(CPACK_PACKAGE_CONTACT "example@example.com") +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Deb Example") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") +set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") +# package name for deb +set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) +set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) +# without this you won't be able to pack only specified component +set(CPACK_DEB_COMPONENT_INSTALL YES) + +include(CPack) + +# run cpack -G DEB to create .deb diff --git a/gallery/lines_bars_and_markers/CMakeLists.txt b/gallery/lines_bars_and_markers/CMakeLists.txt index 6f02e56..c936eb7 100644 --- a/gallery/lines_bars_and_markers/CMakeLists.txt +++ b/gallery/lines_bars_and_markers/CMakeLists.txt @@ -6,10 +6,11 @@ add_demo(fill_between_demo fill_between_demo.cpp) add_demo(fill_betweenx_demo fill_betweenx_demo.cpp) add_demo(scatter_with_legend scatter_with_legend.cpp) add_demo(scatter_hist scatter_hist.cpp) +add_demo(errorbar_limits_simple errorbar_limits_simple.cpp) +add_demo(errorbar_subsample errorbar_subsample.cpp) -# TODO: macro for this! add_custom_target(lines_bars_and_markers - DEPENDS bar_label_demo fill simple_plot scatter_symbol fill_between_demo fill_betweenx_demo scatter_with_legend scatter_hist + DEPENDS bar_label_demo fill simple_plot scatter_symbol fill_between_demo fill_betweenx_demo scatter_with_legend scatter_hist errorbar_limits_simple errorbar_subsample COMMAND bar_label_demo COMMAND fill COMMAND simple_plot @@ -18,6 +19,8 @@ add_custom_target(lines_bars_and_markers COMMAND fill_betweenx_demo COMMAND scatter_with_legend COMMAND scatter_hist + COMMAND errorbar_limits_simple + COMMAND errorbar_subsample COMMENT "running lines_bars_and_markers" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../images" ) diff --git a/gallery/lines_bars_and_markers/errorbar_limits_simple.cpp b/gallery/lines_bars_and_markers/errorbar_limits_simple.cpp new file mode 100644 index 0000000..963135f --- /dev/null +++ b/gallery/lines_bars_and_markers/errorbar_limits_simple.cpp @@ -0,0 +1,54 @@ +// example +// https://matplotlib.org/stable/gallery/lines_bars_and_markers/errorbar_limits_simple.html + +#include +#include + +#include + +#include +#include + +#include + +namespace py = pybind11; +using namespace py::literals; +using namespace std; +using namespace matplotlibcpp17; + +int main() { + py::scoped_interpreter guard{}; + auto plt = pyplot::import(); + auto fig = plt.figure(); + auto x_ = xt::arange(0.0, 10.0, 1.0); + auto y_ = 2.5 * xt::sin(x_ / 20 * M_PI); + auto y1_ = y_ + 1.0, y2_ = y_ + 2.0, y3_ = y_ + 3.0; + auto yerr_ = xt::linspace(0.05, 0.2, 10); + vector x(x_.begin(), x_.end()), y(y_.begin(), y_.end()), + yerr(yerr_.begin(), yerr_.end()), y3(y3_.begin(), y3_.end()), + y2(y2_.begin(), y2_.end()), y1(y1_.begin(), y1_.end()); + plt.errorbar(Args(x, y3), + Kwargs("yerr"_a = yerr, "label"_a = "both limits (default)")); + plt.errorbar(Args(x, y2), Kwargs("yerr"_a = yerr, "uplims"_a = true, + "label"_a = "uplims=True")); + plt.errorbar(Args(x, y1), + Kwargs("yerr"_a = yerr, "uplims"_a = true, "lolims"_a = true, + "label"_a = "uplims=True, lolims=True")); + + vector upperlimits, lowerlimits; + for (auto i : {0, 1, 2, 3, 4}) { + upperlimits.push_back(true); + upperlimits.push_back(false); + lowerlimits.push_back(false); + lowerlimits.push_back(true); + } + plt.errorbar(Args(x, y), Kwargs("yerr"_a = yerr, "uplims"_a = upperlimits, + "lolims"_a = lowerlimits, + "label"_a = "subsets of uplims and lolims")); + plt.legend(Args(), Kwargs("loc"_a = "lower right")); +#if USE_GUI + plt.show(); +#else + plt.savefig(Args("errorbar_limits_simple.png")); +#endif +} diff --git a/gallery/lines_bars_and_markers/errorbar_subsample.cpp b/gallery/lines_bars_and_markers/errorbar_subsample.cpp new file mode 100644 index 0000000..4b54b1f --- /dev/null +++ b/gallery/lines_bars_and_markers/errorbar_subsample.cpp @@ -0,0 +1,54 @@ +// example from +// https://matplotlib.org/stable/gallery/lines_bars_and_markers/errorbar_subsample.html + +#include +#include + +#include + +#include +#include + +#include + +namespace py = pybind11; +using namespace py::literals; +using namespace std; +using namespace matplotlibcpp17; + +int main() { + auto x_ = xt::arange(0.1, 4.0, 0.1); + auto y1_ = xt::exp(-1.0 * x_); + auto y2_ = xt::exp(-0.5 * x_); + auto y1err_ = 0.1 + 0.1 * xt::sqrt(x_); + auto y2err_ = 0.1 + 0.1 * xt::sqrt(x_ / 2.0); + vector x(x_.begin(), x_.end()), y1(y1_.begin(), y1_.end()), + y2(y2_.begin(), y2_.end()), y1err(y1err_.begin(), y1err_.end()), + y2err(y2err_.begin(), y2err_.end()); + + py::scoped_interpreter guard{}; + auto plt = pyplot::import(); + auto [fig, axs] = plt.subplots( + 1, 3, Kwargs("sharex"_a = true, "figsize"_a = py::make_tuple(12, 6))); + auto ax0 = axs[0], ax1 = axs[1], ax2 = axs[2]; + ax0.set_title(Args("all errorbars")); + ax0.errorbar(Args(x, y1), Kwargs("yerr"_a = y1err)); + ax0.errorbar(Args(x, y1), Kwargs("yerr"_a = y2err)); + + ax1.set_title(Args("only every 6th errorbar")); + ax1.errorbar(Args(x, y1), Kwargs("yerr"_a = y1err, "errorevery"_a = 6)); + ax1.errorbar(Args(x, y2), Kwargs("yerr"_a = y2err, "errorevery"_a = 6)); + + ax2.set_title(Args("second seris shifted by 3")); + ax2.errorbar(Args(x, y1), + Kwargs("yerr"_a = y1err, "errorevery"_a = py::make_tuple(0, 6))); + ax2.errorbar(Args(x, y2), + Kwargs("yerr"_a = y2err, "errorevery"_a = py::make_tuple(3, 6))); + + fig.suptitle(Args("Errorbar subsampling")); +#if USE_GUI + plt.show(); +#else + plt.savefig(Args("erorbar_subsample.png")); +#endif +} diff --git a/gallery/mplot3d/CMakeLists.txt b/gallery/mplot3d/CMakeLists.txt index 4c48bc4..c2cc39f 100644 --- a/gallery/mplot3d/CMakeLists.txt +++ b/gallery/mplot3d/CMakeLists.txt @@ -2,13 +2,15 @@ add_demo(lines3d lines3d.cpp) add_demo(lorenz_attractor lorenz_attractor.cpp) add_demo(contour3d contour3d.cpp) add_demo(subplot3d subplot3d.cpp) +add_demo(errorbar3d errorbar3d.cpp) add_custom_target(mplot3d - DEPENDS lines3d lorenz_attractor contour3d subplot3d + DEPENDS lines3d lorenz_attractor contour3d subplot3d errorbar3d COMMAND lines3d COMMAND lorenz_attractor COMMAND contour3d COMMAND subplot3d + COMMAND errorbar3d COMMENT "running mplot3d" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../images" ) diff --git a/gallery/mplot3d/errorbar3d.cpp b/gallery/mplot3d/errorbar3d.cpp new file mode 100644 index 0000000..b5bf7c3 --- /dev/null +++ b/gallery/mplot3d/errorbar3d.cpp @@ -0,0 +1,49 @@ +// example from https://matplotlib.org/stable/gallery/mplot3d/errorbar3d.html + +#include +#include + +#include + +#include +#include + +#include +#include + +namespace py = pybind11; +using namespace py::literals; +using namespace std; +using namespace matplotlibcpp17; + +int main() { + py::scoped_interpreter guard{}; + auto plt = pyplot::import(); + auto ax = plt.figure().add_subplot(Args(), Kwargs("projection"_a = "3d")); + auto t_ = xt::arange(0.0, 2 * M_PI + 0.1, 0.01); + auto x_ = xt::sin(1.0 * t_); + auto y_ = xt::cos(3.0 * t_); + auto z_ = xt::sin(5.0 * t_); + vector t(t_.begin(), t_.end()), x(x_.begin(), x_.end()), + y(y_.begin(), y_.end()), z(z_.begin(), z_.end()); + + const int estep = 15; + vector i(t_.shape()[0]), zuplims, zlolims; + std::iota(i.begin(), i.end(), t_.shape()[0]); + std::transform(i.begin(), i.end(), std::back_inserter(zuplims), [](int i) { + return (i % 15 == 0) and ((i / estep) % 3 == 0); + }); + std::transform(i.begin(), i.end(), std::back_inserter(zlolims), [](int i) { + return (i % 15 == 0) and ((i / estep) % 3 == 2); + }); + + ax.errorbar(Args(x, y, z, 0.2), + Kwargs("zuplims"_a = zuplims, "zlolims"_a = zlolims, + "errorevery"_a = estep)); + + ax.set_xlabel(Args("X label")); + ax.set_ylabel(Args("Y label")); + ax.set_zlabel(Args("Z label")); + + plt.show(); +} diff --git a/gallery/shapes_and_collections/patch_collection.cpp b/gallery/shapes_and_collections/patch_collection.cpp index 41e4554..3450c9e 100644 --- a/gallery/shapes_and_collections/patch_collection.cpp +++ b/gallery/shapes_and_collections/patch_collection.cpp @@ -60,6 +60,7 @@ int main() { py::list 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 diff --git a/gallery/statistics/CMakeLists.txt b/gallery/statistics/CMakeLists.txt index 577ba1e..be84ebb 100644 --- a/gallery/statistics/CMakeLists.txt +++ b/gallery/statistics/CMakeLists.txt @@ -1,8 +1,10 @@ add_demo(hist hist.cpp) +add_demo(errorbar errorbar.cpp) add_custom_target(statitics - DEPENDS hist + DEPENDS hist errorbar COMMAND hist + COMMAND errorbar COMMENT "running hist" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../images" ) diff --git a/gallery/statistics/errorbar.cpp b/gallery/statistics/errorbar.cpp new file mode 100644 index 0000000..28f74f8 --- /dev/null +++ b/gallery/statistics/errorbar.cpp @@ -0,0 +1,31 @@ +// example from https://matplotlib.org/stable/gallery/statistics/errorbar.html + +#include +#include + +#include + +#include + +#include + +namespace py = pybind11; +using namespace py::literals; +using namespace std; +using namespace matplotlibcpp17; + +int main() { + py::scoped_interpreter guard{}; + auto plt = pyplot::import(); + auto x_ = xt::arange(0.1, 4.0, 0.5); + auto y_ = xt::exp(-x_); + vector x(x_.begin(), x_.end()), y(y_.begin(), y_.end()); + + auto [fig, ax] = plt.subplots(); + ax.errorbar(Args(x, y), Kwargs("xerr"_a = 0.2, "yerr"_a = 0.4)); +#if USE_GUI + plt.show(); +#else + plt.savefig(Args("erorrbar.png")); +#endif +} diff --git a/include/matplotlibcpp17/axes.h b/include/matplotlibcpp17/axes.h index 5ea63cb..df89efe 100644 --- a/include/matplotlibcpp17/axes.h +++ b/include/matplotlibcpp17/axes.h @@ -69,6 +69,10 @@ struct DECL_STRUCT_ATTR Axes : public BaseWrapper { pybind11::object contour(const pybind11::tuple &args = pybind11::tuple(), const pybind11::dict &kwargs = pybind11::dict()); + // errorbar + pybind11::object errorbar(const pybind11::tuple &args = pybind11::tuple(), + const pybind11::dict &kwargs = pybind11::dict()); + // fill pybind11::object fill(const pybind11::tuple &args = pybind11::tuple(), const pybind11::dict &kwargs = pybind11::dict()); @@ -206,9 +210,14 @@ struct DECL_STRUCT_ATTR Axes : public BaseWrapper { LOAD_FUNC_ATTR(add_patch, self); LOAD_FUNC_ATTR(axhline, self); LOAD_FUNC_ATTR(bar, self); +#if MATPLOTLIB_MINOR_VER_GTE_4 LOAD_FUNC_ATTR(bar_label, self); +#else + WARN_MSG("Not loading bar_label because matplotlib version is < 3.4.0"); +#endif LOAD_FUNC_ATTR(barh, self); LOAD_FUNC_ATTR(contour, self); + LOAD_FUNC_ATTR(errorbar, self); LOAD_FUNC_ATTR(fill, self); LOAD_FUNC_ATTR(fill_between, self); LOAD_FUNC_ATTR(fill_betweenx, self); @@ -227,9 +236,11 @@ struct DECL_STRUCT_ATTR Axes : public BaseWrapper { plot_surface_attr = self.attr("plot_surface"); plot_wireframe_attr = self.attr("plot_wireframe"); set_zlabel_attr = self.attr("set_zlabel"); - std::cout << "Loaded Axes3D." << std::endl; + INFO_MSG("Loaded Axes3D"); + projection_3d = true; + } catch (...) { + projection_3d = false; } - catch(...) {} LOAD_FUNC_ATTR(quiver, self); LOAD_FUNC_ATTR(quiverkey, self); LOAD_FUNC_ATTR(scatter, self); @@ -254,6 +265,7 @@ struct DECL_STRUCT_ATTR Axes : public BaseWrapper { pybind11::object bar_label_attr; pybind11::object barh_attr; pybind11::object contour_attr; + pybind11::object errorbar_attr; pybind11::object fill_attr; pybind11::object fill_between_attr; pybind11::object fill_betweenx_attr; @@ -285,6 +297,7 @@ struct DECL_STRUCT_ATTR Axes : public BaseWrapper { pybind11::object set_zlabel_attr; pybind11::object text_attr; pybind11::object tick_params_attr; + bool projection_3d; }; // add_artist @@ -325,8 +338,14 @@ container::BarContainer Axes::bar(const pybind11::tuple &args, // bar_label pybind11::object Axes::bar_label(const pybind11::tuple &args, const pybind11::dict &kwargs) { +#if MATPLOTLIB_MINOR_VER_GTE_4 pybind11::object ret = bar_label_attr(*args, **kwargs); return ret; +#else + ERROR_MSG( + "Call to bar_label is invalid because matplotlib version is < 3.4.0"); + std::exit(0); +#endif } // barh @@ -343,6 +362,24 @@ pybind11::object Axes::contour(const pybind11::tuple &args, return obj; } +// errorbar +pybind11::object Axes::errorbar(const pybind11::tuple &args, + const pybind11::dict &kwargs) { + if (projection_3d) { +#if MATPLOTLIB_MINOR_VER_GTE_4 + pybind11::object obj = errorbar_attr(*args, **kwargs); + return obj; +#else + ERROR_MSG("Call to errorbar with projection='3d' is invalid because " + "matplotlib version is < 3.4.0"); + std::exit(0); +#endif + } else { + pybind11::object obj = errorbar_attr(*args, **kwargs); + return obj; + } +} + // fill pybind11::object Axes::fill(const pybind11::tuple &args, const pybind11::dict &kwargs) { diff --git a/include/matplotlibcpp17/common.h b/include/matplotlibcpp17/common.h index 639030a..a1029f7 100644 --- a/include/matplotlibcpp17/common.h +++ b/include/matplotlibcpp17/common.h @@ -8,6 +8,26 @@ #define DECL_STRUCT_ATTR __attribute__((visibility("hidden"))) +#include + +#define INFO_MSG(msg) \ + do { \ + std::cout << "Info [" __FILE__ << "@" << __LINE__ << "]: "; \ + std::cout << #msg << std::endl; \ + } while (0) + +#define WARN_MSG(msg) \ + do { \ + std::cout << "Warn [" __FILE__ << "@" << __LINE__ << "]: "; \ + std::cout << #msg << std::endl; \ + } while (0) + +#define ERROR_MSG(msg) \ + do { \ + std::cerr << "Error [" __FILE__ << "@" << __LINE__ << "]: "; \ + std::cerr << #msg << std::endl; \ + } while (0) + #include namespace matplotlibcpp17 { diff --git a/include/matplotlibcpp17/pyplot.h b/include/matplotlibcpp17/pyplot.h index d188626..7e2f43c 100644 --- a/include/matplotlibcpp17/pyplot.h +++ b/include/matplotlibcpp17/pyplot.h @@ -41,6 +41,10 @@ struct DECL_STRUCT_ATTR PyPlot { pybind11::object clf(const pybind11::tuple &args = pybind11::tuple(), const pybind11::dict &kwargs = pybind11::dict()); + // errorbar + pybind11::object errorbar(const pybind11::tuple &args = pybind11::tuple(), + const pybind11::dict &kwargs = pybind11::dict()); + // figaspect std::tuple figaspect(const pybind11::tuple &args = pybind11::tuple(), @@ -118,6 +122,7 @@ struct DECL_STRUCT_ATTR PyPlot { LOAD_FUNC_ATTR(axis, mod); LOAD_FUNC_ATTR(cla, mod); LOAD_FUNC_ATTR(clf, mod); + LOAD_FUNC_ATTR(errorbar, mod); LOAD_FUNC_ATTR(figaspect, mod); LOAD_FUNC_ATTR(figure, mod); LOAD_FUNC_ATTR(gca, mod); @@ -141,6 +146,7 @@ struct DECL_STRUCT_ATTR PyPlot { pybind11::object axis_attr; pybind11::object cla_attr; pybind11::object clf_attr; + pybind11::object errorbar_attr; pybind11::object figaspect_attr; pybind11::object figure_attr; pybind11::object gca_attr; @@ -187,6 +193,13 @@ pybind11::object PyPlot::clf(const pybind11::tuple &args, return ret; } +// errorbar +pybind11::object PyPlot::errorbar(const pybind11::tuple &args, + const pybind11::dict &kwargs) { + pybind11::object ret = errorbar_attr(*args, **kwargs); + return ret; +} + // figaspect std::tuple PyPlot::figaspect(const pybind11::tuple &args, const pybind11::dict &kwargs) {