From 717e98e752260245407c5329846f5d62605eff08 Mon Sep 17 00:00:00 2001 From: quangtung97 Date: Sat, 5 May 2018 09:03:25 +0700 Subject: [PATCH 001/110] Add figure_size --- README.md | 2 ++ examples/basic.cpp | 8 +++++--- examples/basic.png | Bin 23897 -> 37402 bytes matplotlibcpp.h | 20 ++++++++++++++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3188b96..d57e5f5 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ int main() z.at(i) = log(i); } + // Set the size of output image = 1200x780 pixels + plt::figure_size(1200, 780); // Plot line from given x and y data. Color is selected automatically. plt::plot(x, y); // Plot a red dashed line from given x and y data. diff --git a/examples/basic.cpp b/examples/basic.cpp index 2b02f27..152c274 100644 --- a/examples/basic.cpp +++ b/examples/basic.cpp @@ -15,9 +15,11 @@ int main() y.at(i) = sin(2*M_PI*i/360.0); z.at(i) = log(i); } - - // Plot line from given x and y data. Color is selected automatically. - plt::plot(x, y); + + // Set the size of output image = 1200x780 pixels + plt::figure_size(1200, 780); + // Plot line from given x and y data. Color is selected automatically. + plt::plot(x, y); // Plot a red dashed line from given x and y data. plt::plot(x, w,"r--"); // Plot a line whose name will show up as "log(x)" in the legend. diff --git a/examples/basic.png b/examples/basic.png index eef82a22f12ee18ddabaf32fae68b57cd89a061e..4d87ff01a24d2368ac80b73e14587e9bd58f7bf9 100644 GIT binary patch literal 37402 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%0AEFGg;{EZPhaF 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 PVaZA+ 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 h|8maf^hCk$%ht6E{5NearP}}i diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 95d3772..f9f7026 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -751,6 +751,26 @@ inline void figure() Py_DECREF(res); } +inline void figure_size(size_t w, size_t h) +{ + const size_t dpi = 100; + PyObject* size = PyTuple_New(2); + PyTuple_SetItem(size, 0, PyFloat_FromDouble((double)w / dpi)); + PyTuple_SetItem(size, 1, PyFloat_FromDouble((double)h / dpi)); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "figsize", size); + PyDict_SetItemString(kwargs, "dpi", PyLong_FromSize_t(dpi)); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_figure, + detail::_interpreter::get().s_python_empty_tuple, kwargs); + + Py_DECREF(kwargs); + + if(!res) throw std::runtime_error("Call to figure_size() failed."); + Py_DECREF(res); +} + inline void legend() { PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_legend, detail::_interpreter::get().s_python_empty_tuple); From 331029fd158f860cc15e02c3dee04e3bc0fab075 Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Sat, 22 Sep 2018 11:23:17 +0100 Subject: [PATCH 002/110] Add support for xticks and yticks --- matplotlibcpp.h | 98 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index f9f7026..e4d7dd2 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -51,6 +51,8 @@ struct _interpreter { PyObject *s_python_function_axis; PyObject *s_python_function_xlabel; PyObject *s_python_function_ylabel; + PyObject *s_python_function_xticks; + PyObject *s_python_function_yticks; PyObject *s_python_function_grid; PyObject *s_python_function_clf; PyObject *s_python_function_errorbar; @@ -149,6 +151,8 @@ struct _interpreter { s_python_function_axis = PyObject_GetAttrString(pymod, "axis"); s_python_function_xlabel = PyObject_GetAttrString(pymod, "xlabel"); s_python_function_ylabel = PyObject_GetAttrString(pymod, "ylabel"); + s_python_function_xticks = PyObject_GetAttrString(pymod, "xticks"); + s_python_function_yticks = PyObject_GetAttrString(pymod, "yticks"); s_python_function_grid = PyObject_GetAttrString(pymod, "grid"); s_python_function_xlim = PyObject_GetAttrString(pymod, "xlim"); s_python_function_ion = PyObject_GetAttrString(pymod, "ion"); @@ -849,6 +853,100 @@ inline double* ylim() return arr; } +template +inline void xticks(const std::vector &ticks, const std::vector &labels = {}, const std::map& keywords = {}) +{ + assert(labels.size() == 0 || ticks.size() == labels.size()); + + // using numpy array + PyObject* ticksarray = get_array(ticks); + + PyObject* args; + if(labels.size() == 0) { + // construct positional args + args = PyTuple_New(1); + PyTuple_SetItem(args, 0, ticksarray); + } else { + // make tuple of tick labels + PyObject* labelstuple = PyTuple_New(labels.size()); + for (size_t i = 0; i < labels.size(); i++) + PyTuple_SetItem(labelstuple, i, PyUnicode_FromString(labels[i].c_str())); + + // construct positional args + args = PyTuple_New(2); + PyTuple_SetItem(args, 0, ticksarray); + PyTuple_SetItem(args, 1, labelstuple); + } + + // 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_xticks, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + if(!res) throw std::runtime_error("Call to xticks() failed"); + + Py_DECREF(res); +} + +template +inline void xticks(const std::vector &ticks, const std::map& keywords) +{ + xticks(ticks, {}, keywords); +} + +template +inline void yticks(const std::vector &ticks, const std::vector &labels = {}, const std::map& keywords = {}) +{ + assert(labels.size() == 0 || ticks.size() == labels.size()); + + // using numpy array + PyObject* ticksarray = get_array(ticks); + + PyObject* args; + if(labels.size() == 0) { + // construct positional args + args = PyTuple_New(1); + PyTuple_SetItem(args, 0, ticksarray); + } else { + // make tuple of tick labels + PyObject* labelstuple = PyTuple_New(labels.size()); + for (size_t i = 0; i < labels.size(); i++) + PyTuple_SetItem(labelstuple, i, PyUnicode_FromString(labels[i].c_str())); + + // construct positional args + args = PyTuple_New(2); + PyTuple_SetItem(args, 0, ticksarray); + PyTuple_SetItem(args, 1, labelstuple); + } + + // 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_yticks, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + if(!res) throw std::runtime_error("Call to yticks() failed"); + + Py_DECREF(res); +} + +template +inline void yticks(const std::vector &ticks, const std::map& keywords) +{ + yticks(ticks, {}, keywords); +} + inline void subplot(long nrows, long ncols, long plot_number) { // construct positional args From 6e3fcf65c846a79cd6cd573ab41fd354135fd99c Mon Sep 17 00:00:00 2001 From: Chachay Date: Sun, 18 Feb 2018 22:20:45 +0900 Subject: [PATCH 003/110] Update windows/VS2017 support --- contrib/CMakeLists.txt | 8 ++++++-- contrib/README.md | 15 ++++++++++++++- contrib/WinBuild.cmd | 23 +++++++++++++++++++---- examples/xkcd.cpp | 3 ++- 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index ba14b86..9bb8db1 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -1,10 +1,11 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.7) project (MatplotlibCPP_Test) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) include_directories(${PYTHONHOME}/include) +include_directories(${PYTHONHOME}/Lib/site-packages/numpy/core/include) link_directories(${PYTHONHOME}/libs) add_definitions(-DMATPLOTLIBCPP_PYTHON_HEADER=Python.h) @@ -12,10 +13,13 @@ add_definitions(-DMATPLOTLIBCPP_PYTHON_HEADER=Python.h) # message(STATUS "*** dump start cmake variables ***") # get_cmake_property(_variableNames VARIABLES) # foreach(_variableName ${_variableNames}) - # message(STATUS "${_variableName}=${${_variableName}}") +# message(STATUS "${_variableName}=${${_variableName}}") # endforeach() # message(STATUS "*** dump end ***") add_executable(minimal ${CMAKE_CURRENT_SOURCE_DIR}/../examples/minimal.cpp) add_executable(basic ${CMAKE_CURRENT_SOURCE_DIR}/../examples/basic.cpp) add_executable(modern ${CMAKE_CURRENT_SOURCE_DIR}/../examples/modern.cpp) +add_executable(animation ${CMAKE_CURRENT_SOURCE_DIR}/../examples/animation.cpp) +add_executable(nonblock ${CMAKE_CURRENT_SOURCE_DIR}/../examples/nonblock.cpp) +add_executable(xkcd ${CMAKE_CURRENT_SOURCE_DIR}/../examples/xkcd.cpp) diff --git a/contrib/README.md b/contrib/README.md index efc0a50..0af8515 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -8,12 +8,25 @@ contributors are not required to and may be unable to check whether their changes break any of them. ## Windows support +Tested on the following environment +* Windows 10 - 64bit +* Anaconda 4.3 (64 bit) +* Python 3.6.0 +* CMake 3.9.4 +* Visual Studio 2017, 2015, 2013 ### Configuring and Building Samples +1. Edit WinBuild.cmd for your environment(Line:5-7) + if NOT DEFINED MSVC_VERSION set MSVC_VERSION=[Your Visual Studio Version(12, 14, 15)] + if NOT DEFINED CMAKE_CONFIG set CMAKE_CONFIG=Release + if NOT DEFINED PYTHONHOME set PYTHONHOME=[Your Python Path] +2. Run WinBuild.cmd to build ```cmd > cd contrib > WinBuild.cmd ``` - The `WinBuild.cmd` will set up temporal ENV variables and build binaries in (matplotlib root)/examples with the Release configuration. + +3. Find exe files in examples/build/Release +Note: platforms folder is necessary to make qt works. diff --git a/contrib/WinBuild.cmd b/contrib/WinBuild.cmd index 4e3b450..9dfd627 100644 --- a/contrib/WinBuild.cmd +++ b/contrib/WinBuild.cmd @@ -1,9 +1,14 @@ @echo off @setlocal EnableDelayedExpansion -if NOT DEFINED MSVC_VERSION set MSVC_VERSION=14 +REM ------Set Your Environment------------------------------- +if NOT DEFINED MSVC_VERSION set MSVC_VERSION=15 if NOT DEFINED CMAKE_CONFIG set CMAKE_CONFIG=Release if NOT DEFINED PYTHONHOME set PYTHONHOME=C:/Users/%username%/Anaconda3 +REM --------------------------------------------------------- + +set KEY_NAME="HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7" +set VALUE_NAME=15.0 if "%MSVC_VERSION%"=="14" ( if "%processor_architecture%" == "AMD64" ( @@ -14,13 +19,23 @@ if "%MSVC_VERSION%"=="14" ( ) else if "%MSVC_VERSION%"=="12" ( if "%processor_architecture%" == "AMD64" ( set CMAKE_GENERATOR=Visual Studio 12 2013 Win64 - ) else ( set CMAKE_GENERATOR=Visual Studio 12 2013 ) +) else if "%MSVC_VERSION%"=="15" ( + if "%processor_architecture%" == "AMD64" ( + set CMAKE_GENERATOR=Visual Studio 15 2017 Win64 + ) else ( + set CMAKE_GENERATOR=Visual Studio 15 2017 + ) +) +if "%MSVC_VERSION%"=="15" ( + for /F "usebackq tokens=1,2,*" %%A in (`REG QUERY %KEY_NAME% /v %VALUE_NAME%`) do ( + set batch_file=%%CVC\Auxiliary\Build\vcvarsall.bat + ) +) else ( + set batch_file=!VS%MSVC_VERSION%0COMNTOOLS!..\..\VC\vcvarsall.bat ) - -set batch_file=!VS%MSVC_VERSION%0COMNTOOLS!..\..\VC\vcvarsall.bat call "%batch_file%" %processor_architecture% pushd .. diff --git a/examples/xkcd.cpp b/examples/xkcd.cpp index 9bf6454..fa41cfc 100644 --- a/examples/xkcd.cpp +++ b/examples/xkcd.cpp @@ -1,6 +1,7 @@ +#define _USE_MATH_DEFINES +#include #include "../matplotlibcpp.h" #include -#include namespace plt = matplotlibcpp; From 16a7fd66c3939d792395c683e3a0cc3ce18cb00b Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Wed, 3 Oct 2018 11:52:42 +0100 Subject: [PATCH 004/110] Fix implementation of errorbar() At the moment, the errorbar() function takes an optional parameter s, which is presumably supposed to correspond to the same parameter for e.g. the plot function. The only problem is that a) this argument is unused in the function and b) matplotlib's errorbar() doesn't take a parameter like this anyway. I've replaced the s argument with an optional keywords argument, as for other functions, which allows for setting colour, marker style etc. --- matplotlibcpp.h | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index e4d7dd2..94bc5e1 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -576,7 +576,7 @@ bool loglog(const std::vector& x, const std::vector& y, cons } template -bool errorbar(const std::vector &x, const std::vector &y, const std::vector &yerr, const std::string &s = "") +bool errorbar(const std::vector &x, const std::vector &y, const std::vector &yerr, const std::map &keywords = {}) { assert(x.size() == y.size()); @@ -584,12 +584,15 @@ bool errorbar(const std::vector &x, const std::vector &y, co PyObject* yarray = get_array(y); PyObject* yerrarray = get_array(yerr); - PyObject *kwargs = PyDict_New(); + // 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())); + } PyDict_SetItemString(kwargs, "yerr", yerrarray); - PyObject *pystring = PyString_FromString(s.c_str()); - PyObject *plot_args = PyTuple_New(2); PyTuple_SetItem(plot_args, 0, xarray); PyTuple_SetItem(plot_args, 1, yarray); @@ -766,7 +769,7 @@ inline void figure_size(size_t w, size_t h) PyDict_SetItemString(kwargs, "figsize", size); PyDict_SetItemString(kwargs, "dpi", PyLong_FromSize_t(dpi)); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_figure, + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_figure, detail::_interpreter::get().s_python_empty_tuple, kwargs); Py_DECREF(kwargs); @@ -890,7 +893,7 @@ inline void xticks(const std::vector &ticks, const std::vector &ticks, const std::vector Date: Tue, 2 Oct 2018 10:43:50 +0100 Subject: [PATCH 005/110] Add quiver function for 4 vectors of uniform length --- matplotlibcpp.h | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 94bc5e1..c4b2767 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -37,6 +37,7 @@ struct _interpreter { PyObject *s_python_function_save; PyObject *s_python_function_figure; PyObject *s_python_function_plot; + PyObject *s_python_function_quiver; PyObject *s_python_function_semilogx; PyObject *s_python_function_semilogy; PyObject *s_python_function_loglog; @@ -139,6 +140,7 @@ struct _interpreter { s_python_function_pause = PyObject_GetAttrString(pymod, "pause"); s_python_function_figure = PyObject_GetAttrString(pymod, "figure"); s_python_function_plot = PyObject_GetAttrString(pymod, "plot"); + s_python_function_quiver = PyObject_GetAttrString(pymod, "quiver"); s_python_function_semilogx = PyObject_GetAttrString(pymod, "semilogx"); s_python_function_semilogy = PyObject_GetAttrString(pymod, "semilogy"); s_python_function_loglog = PyObject_GetAttrString(pymod, "loglog"); @@ -170,6 +172,7 @@ struct _interpreter { || !s_python_function_pause || !s_python_function_figure || !s_python_function_plot + || !s_python_function_quiver || !s_python_function_semilogx || !s_python_function_semilogy || !s_python_function_loglog @@ -200,6 +203,7 @@ struct _interpreter { || !PyFunction_Check(s_python_function_pause) || !PyFunction_Check(s_python_function_figure) || !PyFunction_Check(s_python_function_plot) + || !PyFunction_Check(s_python_function_quiver) || !PyFunction_Check(s_python_function_semilogx) || !PyFunction_Check(s_python_function_semilogy) || !PyFunction_Check(s_python_function_loglog) @@ -481,6 +485,39 @@ bool plot(const std::vector& x, const std::vector& y, const return res; } +template +bool quiver(const std::vector& x, const std::vector& y, const std::vector& u, const std::vector& w, const std::map& keywords = {}) +{ + assert(x.size() == y.size() && x.size() == u.size() && u.size() == w.size()); + + PyObject* xarray = get_array(x); + PyObject* yarray = get_array(y); + PyObject* uarray = get_array(u); + PyObject* warray = get_array(w); + + PyObject* plot_args = PyTuple_New(4); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, uarray); + PyTuple_SetItem(plot_args, 3, warray); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_quiver, plot_args, kwargs); + + Py_DECREF(plot_args); + if (res) + Py_DECREF(res); + + return res; +} + template bool stem(const std::vector& x, const std::vector& y, const std::string& s = "") { From c1ad253e74742f81aa715197393fcb56f1716f39 Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Fri, 5 Oct 2018 18:45:07 +0100 Subject: [PATCH 006/110] Add example of quiver plot --- examples/quiver.cpp | 20 ++++++++++++++++++++ examples/quiver.png | Bin 0 -> 39752 bytes 2 files changed, 20 insertions(+) create mode 100644 examples/quiver.cpp create mode 100644 examples/quiver.png diff --git a/examples/quiver.cpp b/examples/quiver.cpp new file mode 100644 index 0000000..ea3c3ec --- /dev/null +++ b/examples/quiver.cpp @@ -0,0 +1,20 @@ +#include "../matplotlibcpp.h" + +namespace plt = matplotlibcpp; + +int main() +{ + // u and v are respectively the x and y components of the arrows we're plotting + std::vector x, y, u, v; + for (int i = -5; i <= 5; i++) { + for (int j = -5; j <= 5; j++) { + x.push_back(i); + u.push_back(-i); + y.push_back(j); + v.push_back(-j); + } + } + + plt::quiver(x, y, u, v); + plt::show(); +} \ No newline at end of file diff --git a/examples/quiver.png b/examples/quiver.png new file mode 100644 index 0000000000000000000000000000000000000000..9d7be1eec0f554fbd57490d5d83b3e93debd7c95 GIT binary patch literal 39752 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 Date: Sat, 6 Oct 2018 09:14:21 +0200 Subject: [PATCH 007/110] Update README and Makefile for quiver plot example. --- Makefile | 7 +++++-- README.md | 44 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index e75d134..368b9ff 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -examples: minimal basic modern animation nonblock xkcd +examples: minimal basic modern animation nonblock xkcd quiver minimal: examples/minimal.cpp matplotlibcpp.h cd examples && g++ -DWITHOUT_NUMPY minimal.cpp -I/usr/include/python2.7 -lpython2.7 -o minimal -std=c++11 @@ -15,8 +15,11 @@ animation: examples/animation.cpp matplotlibcpp.h nonblock: examples/nonblock.cpp matplotlibcpp.h cd examples && g++ nonblock.cpp -I/usr/include/python2.7 -lpython2.7 -o nonblock -std=c++11 +quiver: examples/quiver.cpp matplotlibcpp.h + cd examples && g++ quiver.cpp -I/usr/include/python2.7 -lpython2.7 -o quiver -std=c++11 + xkcd: examples/xkcd.cpp matplotlibcpp.h cd examples && g++ xkcd.cpp -I/usr/include/python2.7 -lpython2.7 -o xkcd -std=c++11 clean: - rm -f examples/{minimal,basic,modern,animation,nonblock,xkcd} + rm -f examples/{minimal,basic,modern,animation,nonblock,xkcd,quiver} diff --git a/README.md b/README.md index d57e5f5..d73ab8a 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ int main() z.at(i) = log(i); } - // Set the size of output image = 1200x780 pixels + // Set the size of output image to 1200x780 pixels plt::figure_size(1200, 780); // Plot line from given x and y data. Color is selected automatically. plt::plot(x, y); @@ -49,9 +49,10 @@ int main() plt::plot(x, w,"r--"); // Plot a line whose name will show up as "log(x)" in the legend. plt::named_plot("log(x)", x, z); - // Set x-axis to interval [0,1000000] plt::xlim(0, 1000*1000); + // Add graph title + plt::title("Sample figure"); // Enable legend. plt::legend(); // Save the image (file format is determined by the extension) @@ -60,7 +61,9 @@ int main() ``` g++ basic.cpp -I/usr/include/python2.7 -lpython2.7 -Result: ![Basic example](./examples/basic.png) +**Result:** + +![Basic example](./examples/basic.png) matplotlib-cpp doesn't require C++11, but will enable some additional syntactic sugar when available: ```cpp @@ -93,7 +96,9 @@ int main() ``` g++ modern.cpp -std=c++11 -I/usr/include/python2.7 -lpython -Result: ![Modern example](./examples/modern.png) +**Result:** + +![Modern example](./examples/modern.png) Or some *funny-looking xkcd-styled* example: ```cpp @@ -123,7 +128,36 @@ int main() { **Result:** -![Minimal example](./examples/xkcd.png) +![xkcd example](./examples/xkcd.png) + +When working with vector fields, you might be interested in quiver plots: +```cpp +#include "../matplotlibcpp.h" + +namespace plt = matplotlibcpp; + +int main() +{ + // u and v are respectively the x and y components of the arrows we're plotting + std::vector x, y, u, v; + for (int i = -5; i <= 5; i++) { + for (int j = -5; j <= 5; j++) { + x.push_back(i); + u.push_back(-i); + y.push_back(j); + v.push_back(-j); + } + } + + plt::quiver(x, y, u, v); + plt::show(); +} +``` + g++ quiver.cpp -std=c++11 -I/usr/include/python2.7 -lpython2.7 + +**Result:** + +![quiver example](./examples/quiver.png) Installation ------------ From 00a4766f78f8aa3643182bdf356a1817c368d21a Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Sat, 6 Oct 2018 09:18:07 +0200 Subject: [PATCH 008/110] Fix indentation in basic example, --- examples/basic.cpp | 53 +++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/examples/basic.cpp b/examples/basic.cpp index 152c274..2dc34c7 100644 --- a/examples/basic.cpp +++ b/examples/basic.cpp @@ -7,33 +7,38 @@ namespace plt = matplotlibcpp; int main() { - // Prepare data. - int n = 5000; - std::vector x(n), y(n), z(n), w(n,2); - for(int i=0; i x(n), y(n), z(n), w(n,2); + for(int i=0; i Date: Tue, 30 Oct 2018 17:54:13 +0000 Subject: [PATCH 009/110] Add missing Py_DECREF to quiver() --- matplotlibcpp.h | 1 + 1 file changed, 1 insertion(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index c4b2767..4f4d7f3 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -511,6 +511,7 @@ bool quiver(const std::vector& x, const std::vector& y, cons PyObject* res = PyObject_Call( detail::_interpreter::get().s_python_function_quiver, plot_args, kwargs); + Py_DECREF(kwargs); Py_DECREF(plot_args); if (res) Py_DECREF(res); From b60d3384ae0de80fe33f915c56865eff263c4d67 Mon Sep 17 00:00:00 2001 From: Cooper Harasyn Date: Fri, 5 Oct 2018 16:05:25 -0400 Subject: [PATCH 010/110] Added text and suptitle --- examples/subplot.cpp | 31 +++++++++++++++++++++++++++++++ matplotlibcpp.h | 43 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 examples/subplot.cpp diff --git a/examples/subplot.cpp b/examples/subplot.cpp new file mode 100644 index 0000000..bee322e --- /dev/null +++ b/examples/subplot.cpp @@ -0,0 +1,31 @@ +#define _USE_MATH_DEFINES +#include +#include "../matplotlibcpp.h" + +using namespace std; +namespace plt = matplotlibcpp; + +int main() +{ + // Prepare data + int n = 500; + std::vector x(n), y(n), z(n), w(n,2); + for(int i=0; i& y, const std::string& format = "") return stem(x, y, format); } +template +void text(Numeric x, Numeric y, const std::string& format = "") +{ + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(x)); + PyTuple_SetItem(args, 1, PyFloat_FromDouble(y)); + PyTuple_SetItem(args, 2, PyString_FromString(format.c_str())); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_text, args); + if(!res) throw std::runtime_error("Call to text() failed."); + + Py_DECREF(args); + Py_DECREF(res); +} + + inline void figure() { PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, detail::_interpreter::get().s_python_empty_tuple); @@ -807,7 +831,7 @@ inline void figure_size(size_t w, size_t h) PyDict_SetItemString(kwargs, "figsize", size); PyDict_SetItemString(kwargs, "dpi", PyLong_FromSize_t(dpi)); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_figure, + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_figure, detail::_interpreter::get().s_python_empty_tuple, kwargs); Py_DECREF(kwargs); @@ -931,7 +955,7 @@ inline void xticks(const std::vector &ticks, const std::vector &ticks, const std::vector Date: Fri, 5 Oct 2018 16:08:40 -0400 Subject: [PATCH 011/110] Fixed whitespace --- matplotlibcpp.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index db4cd4b..4b8b239 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -831,7 +831,7 @@ inline void figure_size(size_t w, size_t h) PyDict_SetItemString(kwargs, "figsize", size); PyDict_SetItemString(kwargs, "dpi", PyLong_FromSize_t(dpi)); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_figure, + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_figure, detail::_interpreter::get().s_python_empty_tuple, kwargs); Py_DECREF(kwargs); @@ -955,7 +955,7 @@ inline void xticks(const std::vector &ticks, const std::vector &ticks, const std::vector Date: Fri, 5 Oct 2018 16:10:17 -0400 Subject: [PATCH 012/110] Chose a better name for the parameter to text --- matplotlibcpp.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 4b8b239..564f4a9 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -797,12 +797,12 @@ bool stem(const std::vector& y, const std::string& format = "") } template -void text(Numeric x, Numeric y, const std::string& format = "") +void text(Numeric x, Numeric y, const std::string& s = "") { PyObject* args = PyTuple_New(3); PyTuple_SetItem(args, 0, PyFloat_FromDouble(x)); PyTuple_SetItem(args, 1, PyFloat_FromDouble(y)); - PyTuple_SetItem(args, 2, PyString_FromString(format.c_str())); + PyTuple_SetItem(args, 2, PyString_FromString(s.c_str())); PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_text, args); if(!res) throw std::runtime_error("Call to text() failed."); From b1242079bc0b75c70d5c014244774a9995e2cc1a Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Wed, 31 Oct 2018 10:34:06 +0000 Subject: [PATCH 013/110] Add support for ginput() function --- matplotlibcpp.h | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 564f4a9..979bb2c 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -47,6 +47,7 @@ struct _interpreter { PyObject *s_python_function_legend; PyObject *s_python_function_xlim; PyObject *s_python_function_ion; + PyObject *s_python_function_ginput; PyObject *s_python_function_ylim; PyObject *s_python_function_title; PyObject *s_python_function_axis; @@ -160,6 +161,7 @@ struct _interpreter { s_python_function_grid = PyObject_GetAttrString(pymod, "grid"); s_python_function_xlim = PyObject_GetAttrString(pymod, "xlim"); s_python_function_ion = PyObject_GetAttrString(pymod, "ion"); + s_python_function_ginput = PyObject_GetAttrString(pymod, "ginput"); s_python_function_save = PyObject_GetAttrString(pylabmod, "savefig"); s_python_function_annotate = PyObject_GetAttrString(pymod,"annotate"); s_python_function_clf = PyObject_GetAttrString(pymod, "clf"); @@ -191,6 +193,7 @@ struct _interpreter { || !s_python_function_grid || !s_python_function_xlim || !s_python_function_ion + || !s_python_function_ginput || !s_python_function_save || !s_python_function_clf || !s_python_function_annotate @@ -225,6 +228,7 @@ struct _interpreter { || !PyFunction_Check(s_python_function_grid) || !PyFunction_Check(s_python_function_xlim) || !PyFunction_Check(s_python_function_ion) + || !PyFunction_Check(s_python_function_ginput) || !PyFunction_Check(s_python_function_save) || !PyFunction_Check(s_python_function_clf) || !PyFunction_Check(s_python_function_tight_layout) @@ -1214,6 +1218,40 @@ inline void clf() { Py_DECREF(res); } +inline std::vector> ginput(const int numClicks = 1, const std::map& keywords = {}) +{ + PyObject *args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyLong_FromLong(numClicks)); + + // 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_ginput, args, kwargs); + + Py_DECREF(kwargs); + Py_DECREF(args); + if (!res) throw std::runtime_error("Call to ginput() failed."); + + const size_t len = PyList_Size(res); + std::vector> out; + out.reserve(len); + for (size_t i = 0; i < len; i++) { + PyObject *current = PyList_GetItem(res, i); + std::array position; + position[0] = PyFloat_AsDouble(PyTuple_GetItem(current, 0)); + position[1] = PyFloat_AsDouble(PyTuple_GetItem(current, 1)); + out.push_back(position); + } + Py_DECREF(res); + + return out; +} + // Actually, is there any reason not to call this automatically for every plot? inline void tight_layout() { PyObject *res = PyObject_CallObject( From dcb23221b2be96f822be85652c5240550e7e19ca Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Tue, 9 Oct 2018 12:46:35 +0100 Subject: [PATCH 014/110] Return figure number from figure function --- matplotlibcpp.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 979bb2c..3240f5f 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -816,12 +816,20 @@ void text(Numeric x, Numeric y, const std::string& s = "") } -inline void figure() +inline long figure() { PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, detail::_interpreter::get().s_python_empty_tuple); if(!res) throw std::runtime_error("Call to figure() failed."); + PyObject* num = PyObject_GetAttrString(res, "number"); + if (!num) throw std::runtime_error("Could not get number attribute of figure object"); + + const long figureNumber = PyLong_AsLong(num); + + Py_DECREF(num); Py_DECREF(res); + + return figureNumber; } inline void figure_size(size_t w, size_t h) From c92ad4cd185ad9daa2ff4ed7521f38f3d80dce39 Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Tue, 9 Oct 2018 12:55:17 +0100 Subject: [PATCH 015/110] Optionally pass figure number to figure() as argument --- matplotlibcpp.h | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 3240f5f..4e9d5af 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -816,14 +816,27 @@ void text(Numeric x, Numeric y, const std::string& s = "") } -inline long figure() +inline long figure(long number = -1) { - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, detail::_interpreter::get().s_python_empty_tuple); + PyObject *res; + if (number == -1) + res = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, detail::_interpreter::get().s_python_empty_tuple); + else { + assert(number > 0); + + // Make sure interpreter is initialised + detail::_interpreter::get(); + + PyObject *args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyLong_FromLong(number)); + res = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, args); + Py_DECREF(args); + } + if(!res) throw std::runtime_error("Call to figure() failed."); PyObject* num = PyObject_GetAttrString(res, "number"); if (!num) throw std::runtime_error("Could not get number attribute of figure object"); - const long figureNumber = PyLong_AsLong(num); Py_DECREF(num); From 073dd98f6299d96133e2bfc17cd16480e8676b1e Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Tue, 9 Oct 2018 13:02:12 +0100 Subject: [PATCH 016/110] Add fignum_exists() function --- matplotlibcpp.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 4e9d5af..e92ecb8 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -36,6 +36,7 @@ struct _interpreter { PyObject *s_python_function_pause; PyObject *s_python_function_save; PyObject *s_python_function_figure; + PyObject *s_python_function_fignum_exists; PyObject *s_python_function_plot; PyObject *s_python_function_quiver; PyObject *s_python_function_semilogx; @@ -142,6 +143,7 @@ struct _interpreter { s_python_function_draw = PyObject_GetAttrString(pymod, "draw"); s_python_function_pause = PyObject_GetAttrString(pymod, "pause"); s_python_function_figure = PyObject_GetAttrString(pymod, "figure"); + s_python_function_fignum_exists = PyObject_GetAttrString(pymod, "fignum_exists"); s_python_function_plot = PyObject_GetAttrString(pymod, "plot"); s_python_function_quiver = PyObject_GetAttrString(pymod, "quiver"); s_python_function_semilogx = PyObject_GetAttrString(pymod, "semilogx"); @@ -177,6 +179,7 @@ struct _interpreter { || !s_python_function_draw || !s_python_function_pause || !s_python_function_figure + || !s_python_function_fignum_exists || !s_python_function_plot || !s_python_function_quiver || !s_python_function_semilogx @@ -211,6 +214,7 @@ struct _interpreter { || !PyFunction_Check(s_python_function_draw) || !PyFunction_Check(s_python_function_pause) || !PyFunction_Check(s_python_function_figure) + || !PyFunction_Check(s_python_function_fignum_exists) || !PyFunction_Check(s_python_function_plot) || !PyFunction_Check(s_python_function_quiver) || !PyFunction_Check(s_python_function_semilogx) @@ -845,6 +849,23 @@ inline long figure(long number = -1) return figureNumber; } +inline bool fignum_exists(long number) +{ + // Make sure interpreter is initialised + detail::_interpreter::get(); + + PyObject *args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyLong_FromLong(number)); + PyObject *res = PyObject_CallObject(detail::_interpreter::get().s_python_function_fignum_exists, args); + if(!res) throw std::runtime_error("Call to fignum_exists() failed."); + + bool ret = PyObject_IsTrue(res); + Py_DECREF(res); + Py_DECREF(args); + + return ret; +} + inline void figure_size(size_t w, size_t h) { const size_t dpi = 100; From 78719eb5f404d068bbb8d70bedacdd199e3d3fb7 Mon Sep 17 00:00:00 2001 From: Matthew Mosley Date: Sun, 14 Oct 2018 16:01:37 -0400 Subject: [PATCH 017/110] added support for scatter plots --- matplotlibcpp.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index e92ecb8..ccd298a 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -44,6 +44,7 @@ struct _interpreter { PyObject *s_python_function_loglog; PyObject *s_python_function_fill_between; PyObject *s_python_function_hist; + PyObject *s_python_function_scatter; PyObject *s_python_function_subplot; PyObject *s_python_function_legend; PyObject *s_python_function_xlim; @@ -151,6 +152,7 @@ struct _interpreter { s_python_function_loglog = PyObject_GetAttrString(pymod, "loglog"); s_python_function_fill_between = PyObject_GetAttrString(pymod, "fill_between"); s_python_function_hist = PyObject_GetAttrString(pymod,"hist"); + s_python_function_scatter = PyObject_GetAttrString(pymod,"scatter"); s_python_function_subplot = PyObject_GetAttrString(pymod, "subplot"); s_python_function_legend = PyObject_GetAttrString(pymod, "legend"); s_python_function_ylim = PyObject_GetAttrString(pymod, "ylim"); @@ -454,6 +456,31 @@ bool hist(const std::vector& y, long bins=10,std::string color="b", dou return res; } +template +bool scatter(const std::vector& x, + const std::vector& y, + const double s=1.0) // The marker size in points**2 +{ + assert(x.size() == y.size()); + + PyObject* xarray = get_array(x); + PyObject* yarray = get_array(y); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "s", PyLong_FromLong(s)); + + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_scatter, plot_args, kwargs); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; +} + template< typename Numeric> bool named_hist(std::string label,const std::vector& y, long bins=10, std::string color="b", double alpha=1.0) { From 8baa5821cedf51a71e939513127b06e954965a13 Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Fri, 11 Jan 2019 19:48:41 +0100 Subject: [PATCH 018/110] Add missing Py_DECREF in scatter function. --- matplotlibcpp.h | 1 + 1 file changed, 1 insertion(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index ccd298a..7eed22b 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -476,6 +476,7 @@ bool scatter(const std::vector& x, PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_scatter, plot_args, kwargs); Py_DECREF(plot_args); + Py_DECREF(kwargs); if(res) Py_DECREF(res); return res; From 94f6c01021813eb4beb28580f4d5aa8246e94105 Mon Sep 17 00:00:00 2001 From: "weijun.liu" Date: Wed, 5 Dec 2018 23:03:03 +0800 Subject: [PATCH 019/110] Add bar plot and subplots_adjust --- Makefile | 7 +++-- contrib/CMakeLists.txt | 1 + examples/bar.cpp | 18 ++++++++++++ examples/bar.png | Bin 0 -> 11952 bytes matplotlibcpp.h | 63 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 examples/bar.cpp create mode 100644 examples/bar.png diff --git a/Makefile b/Makefile index 368b9ff..32568dd 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -examples: minimal basic modern animation nonblock xkcd quiver +examples: minimal basic modern animation nonblock xkcd quiver bar minimal: examples/minimal.cpp matplotlibcpp.h cd examples && g++ -DWITHOUT_NUMPY minimal.cpp -I/usr/include/python2.7 -lpython2.7 -o minimal -std=c++11 @@ -21,5 +21,8 @@ quiver: examples/quiver.cpp matplotlibcpp.h xkcd: examples/xkcd.cpp matplotlibcpp.h cd examples && g++ xkcd.cpp -I/usr/include/python2.7 -lpython2.7 -o xkcd -std=c++11 +bar: examples/bar.cpp matplotlibcpp.h + cd examples && g++ bar.cpp -I/usr/include/python2.7 -lpython2.7 -o bar -std=c++11 + clean: - rm -f examples/{minimal,basic,modern,animation,nonblock,xkcd,quiver} + rm -f examples/{minimal,basic,modern,animation,nonblock,xkcd,quiver,bar} diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index 9bb8db1..edb40b1 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -23,3 +23,4 @@ add_executable(modern ${CMAKE_CURRENT_SOURCE_DIR}/../examples/modern.cpp) add_executable(animation ${CMAKE_CURRENT_SOURCE_DIR}/../examples/animation.cpp) add_executable(nonblock ${CMAKE_CURRENT_SOURCE_DIR}/../examples/nonblock.cpp) add_executable(xkcd ${CMAKE_CURRENT_SOURCE_DIR}/../examples/xkcd.cpp) +add_executable(bar ${CMAKE_CURRENT_SOURCE_DIR}/../examples/bar.cpp) diff --git a/examples/bar.cpp b/examples/bar.cpp new file mode 100644 index 0000000..86423ad --- /dev/null +++ b/examples/bar.cpp @@ -0,0 +1,18 @@ +#define _USE_MATH_DEFINES + +#include +#include +#include "../matplotlibcpp.h" +namespace plt = matplotlibcpp; + +int main(int argc, char **argv) { + std::vector test_data; + for (int i = 0; i < 20; i++) { + test_data.push_back(i); + } + + plt::bar(test_data); + plt::show(); + + return (0); +} diff --git a/examples/bar.png b/examples/bar.png new file mode 100644 index 0000000000000000000000000000000000000000..be6af0fa449d932fd42ca9cadd8a83851e52d488 GIT binary patch literal 11952 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 uITLK#^nu+e;34w& y, long bins=10,std::string color="b", dou PyDict_SetItemString(kwargs, "color", PyString_FromString(color.c_str())); PyDict_SetItemString(kwargs, "alpha", PyFloat_FromDouble(alpha)); - PyObject* plot_args = PyTuple_New(1); PyTuple_SetItem(plot_args, 0, yarray); @@ -482,6 +490,59 @@ bool scatter(const std::vector& x, return res; } +template< typename Numeric> +bool bar(const std::vector& y, std::string ec = "black", std::string ls = "-", double lw = 1.0, + const std::map& keywords = {}) +{ + PyObject* yarray = get_array(y); + + std::vector x; + for (int i = 0; i < y.size(); i++) + x.push_back(i); + + PyObject* xarray = get_array(x); + + PyObject* kwargs = PyDict_New(); + + PyDict_SetItemString(kwargs, "ec", PyString_FromString(ec.c_str())); + PyDict_SetItemString(kwargs, "ls", PyString_FromString(ls.c_str())); + PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); + + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_bar, plot_args, kwargs); + + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + + return res; +} + +inline bool subplots_adjust(const std::map& keywords = {}) +{ + + PyObject* kwargs = PyDict_New(); + for (std::map::const_iterator it = + keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyFloat_FromDouble(it->second)); + } + + + PyObject* plot_args = PyTuple_New(0); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_subplots_adjust, plot_args, kwargs); + + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + + return res; +} + template< typename Numeric> bool named_hist(std::string label,const std::vector& y, long bins=10, std::string color="b", double alpha=1.0) { From c19e00ca4ca8ca1056f9b0f4000d9c6a9a259361 Mon Sep 17 00:00:00 2001 From: "weijun.liu" Date: Wed, 5 Dec 2018 23:04:50 +0800 Subject: [PATCH 020/110] Add -std=c++11 for basic in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 32568dd..4cbe943 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ minimal: examples/minimal.cpp matplotlibcpp.h cd examples && g++ -DWITHOUT_NUMPY minimal.cpp -I/usr/include/python2.7 -lpython2.7 -o minimal -std=c++11 basic: examples/basic.cpp matplotlibcpp.h - cd examples && g++ basic.cpp -I/usr/include/python2.7 -lpython2.7 -o basic + cd examples && g++ basic.cpp -I/usr/include/python2.7 -lpython2.7 -o basic -std=c++11 modern: examples/modern.cpp matplotlibcpp.h cd examples && g++ modern.cpp -I/usr/include/python2.7 -lpython2.7 -o modern -std=c++11 From 65933e3d402c525cfd5b25f6529cef761efc579b Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Fri, 11 Jan 2019 20:06:58 +0100 Subject: [PATCH 021/110] Officially require C++11. As it turns out, support for building with a c++98 compiler was broken several months (years?) ago, but nobody noticed so far. This commit updates the README and header file to officially document that situation. --- README.md | 12 +++++++++++- matplotlibcpp.h | 12 +++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d73ab8a..fd1760b 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ int main() ![Basic example](./examples/basic.png) -matplotlib-cpp doesn't require C++11, but will enable some additional syntactic sugar when available: +Alternatively, matplotlib-cpp also supports some C++11-powered syntactic sugar: ```cpp #include #include "matplotlibcpp.h" @@ -186,6 +186,16 @@ find_package(PythonLibs 2.7) target_include_directories(myproject PRIVATE ${PYTHON_INCLUDE_DIRS}) target_link_libraries(myproject ${PYTHON_LIBRARIES}) ``` + +# C++11 + +Currently, c++11 is required to build matplotlib-cpp. The last working commit that did +not have this requirement was `717e98e752260245407c5329846f5d62605eff08`. + +Note that support for c++98 was dropped more or less accidentally, so if you have to work +with an ancient compiler and still want to enjoy the latest additional features, I'd +probably merge a PR that restores support. + # Python 3 This library supports both python2 and python3 (although the python3 support is probably far less tested, diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 662cf9f..3ce86b7 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -6,11 +6,8 @@ #include #include #include -#include // requires c++11 support - -#if __cplusplus > 199711L || _MSC_VER > 1800 -# include -#endif +#include // requires c++11 support +#include #include @@ -1394,8 +1391,7 @@ inline void tight_layout() { Py_DECREF(res); } -#if __cplusplus > 199711L || _MSC_VER > 1800 -// C++11-exclusive content starts here (variadic plot() and initializer list support) +// Support for variadic plot() and initializer lists: namespace detail { @@ -1524,6 +1520,4 @@ inline bool plot(const std::vector& x, const std::vector& y, con return plot(x,y,keywords); } -#endif - } // end namespace matplotlibcpp From 69e58fe25bae25d5e6630d569c583f2a7b0094ec Mon Sep 17 00:00:00 2001 From: Austin Schuh Date: Fri, 7 Dec 2018 21:30:08 +1100 Subject: [PATCH 022/110] Add plot_surface It probably hard-codes too many defaults, but it works well enough for my purposes. --- README.md | 26 +++++++++++ matplotlibcpp.h | 114 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 138 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fd1760b..ef2f0f9 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,32 @@ int main() ![quiver example](./examples/quiver.png) +When working with 3d functions, you might be interested in 3d plots: +```cpp +#include "../matplotlibcpp.h" + +namespace plt = matplotlibcpp; + +int main() +{ + std::vector> x, y, z; + for (double i = -5; i <= 5; i += 0.25) { + std::vector x_row, y_row, z_row; + for (double j = -5; j <= 5; j += 0.25) { + x_row.push_back(i); + y_row.push_back(j); + z_row.push_back(::std::sin(::std::hypot(x, y))); + } + x.push_back(x_row); + y.push_back(y_row); + z.push_back(z_row); + } + + plt::plot_surface(x, y, z); + plt::show(); +} +``` + Installation ------------ diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 3ce86b7..5d2f0e1 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -59,6 +59,7 @@ struct _interpreter { PyObject *s_python_function_errorbar; PyObject *s_python_function_annotate; PyObject *s_python_function_tight_layout; + PyObject *s_python_colormap; PyObject *s_python_empty_tuple; PyObject *s_python_function_stem; PyObject *s_python_function_xkcd; @@ -115,9 +116,13 @@ struct _interpreter { PyObject* matplotlibname = PyString_FromString("matplotlib"); PyObject* pyplotname = PyString_FromString("matplotlib.pyplot"); + PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); + PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); PyObject* pylabname = PyString_FromString("pylab"); - if (!pyplotname || !pylabname || !matplotlibname) { - throw std::runtime_error("couldnt create string"); + PyObject* cmname = PyString_FromString("matplotlib.cm"); + if (!pyplotname || !pylabname || !matplotlibname || !mpl_toolkits || + !axis3d || !cmname) { + throw std::runtime_error("couldnt create string"); } PyObject* matplotlib = PyImport_Import(matplotlibname); @@ -134,11 +139,22 @@ struct _interpreter { Py_DECREF(pyplotname); if (!pymod) { throw std::runtime_error("Error loading module matplotlib.pyplot!"); } + s_python_colormap = PyImport_Import(cmname); + Py_DECREF(cmname); + if (!s_python_colormap) { throw std::runtime_error("Error loading module matplotlib.cm!"); } PyObject* pylabmod = PyImport_Import(pylabname); Py_DECREF(pylabname); if (!pylabmod) { throw std::runtime_error("Error loading module pylab!"); } + PyObject* mpl_toolkitsmod = PyImport_Import(mpl_toolkits); + Py_DECREF(mpl_toolkitsmod); + if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } + + PyObject* axis3dmod = PyImport_Import(axis3d); + Py_DECREF(axis3dmod); + if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } + s_python_function_show = PyObject_GetAttrString(pymod, "show"); s_python_function_close = PyObject_GetAttrString(pymod, "close"); s_python_function_draw = PyObject_GetAttrString(pymod, "draw"); @@ -325,6 +341,30 @@ PyObject* get_array(const std::vector& v) return varray; } +template +PyObject* get_2darray(const std::vector<::std::vector>& v) +{ + detail::_interpreter::get(); //interpreter needs to be initialized for the numpy commands to work + if (v.size() < 1) throw std::runtime_error("get_2d_array v too small"); + + npy_intp vsize[2] = {static_cast(v.size()), + static_cast(v[0].size())}; + + PyArrayObject *varray = + (PyArrayObject *)PyArray_SimpleNew(2, vsize, NPY_DOUBLE); + + double *vd_begin = static_cast(PyArray_DATA(varray)); + + for (const ::std::vector &v_row : v) { + if (v_row.size() != static_cast(vsize[1])) + throw std::runtime_error("Missmatched array size"); + std::copy(v_row.begin(), v_row.end(), vd_begin); + vd_begin += vsize[1]; + } + + return reinterpret_cast(varray); +} + #else // fallback if we don't have numpy: copy every element of the given vector template @@ -369,6 +409,76 @@ bool plot(const std::vector &x, const std::vector &y, const st return res; } +template +void plot_surface(const std::vector<::std::vector> &x, + const std::vector<::std::vector> &y, + const std::vector<::std::vector> &z, + const std::map &keywords = + std::map()) { + assert(x.size() == y.size()); + assert(y.size() == z.size()); + + // using numpy arrays + PyObject *xarray = get_2darray(x); + PyObject *yarray = get_2darray(y); + PyObject *zarray = get_2darray(z); + + // construct positional args + PyObject *args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + PyTuple_SetItem(args, 2, zarray); + + // Build up the kw args. + PyObject *kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "rstride", PyInt_FromLong(1)); + PyDict_SetItemString(kwargs, "cstride", PyInt_FromLong(1)); + + PyObject *python_colormap_coolwarm = PyObject_GetAttrString( + detail::_interpreter::get().s_python_colormap, "coolwarm"); + + PyDict_SetItemString(kwargs, "cmap", python_colormap_coolwarm); + + for (std::map::const_iterator it = keywords.begin(); + it != keywords.end(); ++it) { + 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 *plot_surface = PyObject_GetAttrString(axis, "plot_surface"); + if (!plot_surface) throw std::runtime_error("No surface"); + Py_INCREF(plot_surface); + PyObject *res = PyObject_Call(plot_surface, args, kwargs); + if (!res) throw std::runtime_error("failed surface"); + Py_DECREF(plot_surface); + + Py_DECREF(axis); + Py_DECREF(args); + Py_DECREF(kwargs); + if (res) Py_DECREF(res); +} + template bool stem(const std::vector &x, const std::vector &y, const std::map& keywords) { From eae3824b077354854eeb3a5f4b9a2da1767cc150 Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Fri, 11 Jan 2019 20:17:33 +0100 Subject: [PATCH 023/110] Add picture and cpp file for new surface example. --- Makefile | 5 ++++- README.md | 6 +++++- examples/surface.cpp | 24 ++++++++++++++++++++++++ examples/surface.png | Bin 0 -> 106199 bytes 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 examples/surface.cpp create mode 100644 examples/surface.png diff --git a/Makefile b/Makefile index 4cbe943..3c4ac36 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -examples: minimal basic modern animation nonblock xkcd quiver bar +examples: minimal basic modern animation nonblock xkcd quiver bar surface minimal: examples/minimal.cpp matplotlibcpp.h cd examples && g++ -DWITHOUT_NUMPY minimal.cpp -I/usr/include/python2.7 -lpython2.7 -o minimal -std=c++11 @@ -24,5 +24,8 @@ xkcd: examples/xkcd.cpp matplotlibcpp.h bar: examples/bar.cpp matplotlibcpp.h cd examples && g++ bar.cpp -I/usr/include/python2.7 -lpython2.7 -o bar -std=c++11 +surface: examples/surface.cpp matplotlibcpp.h + cd examples && g++ surface.cpp -I/usr/include/python2.7 -lpython2.7 -o surface -std=c++11 + clean: rm -f examples/{minimal,basic,modern,animation,nonblock,xkcd,quiver,bar} diff --git a/README.md b/README.md index ef2f0f9..338bea7 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ int main() for (double j = -5; j <= 5; j += 0.25) { x_row.push_back(i); y_row.push_back(j); - z_row.push_back(::std::sin(::std::hypot(x, y))); + z_row.push_back(::std::sin(::std::hypot(i, j))); } x.push_back(x_row); y.push_back(y_row); @@ -185,6 +185,10 @@ int main() } ``` +**Result:** + +![surface example](./examples/surface.png) + Installation ------------ diff --git a/examples/surface.cpp b/examples/surface.cpp new file mode 100644 index 0000000..4865f06 --- /dev/null +++ b/examples/surface.cpp @@ -0,0 +1,24 @@ +#include "../matplotlibcpp.h" + +#include + +namespace plt = matplotlibcpp; + +int main() +{ + std::vector> x, y, z; + for (double i = -5; i <= 5; i += 0.25) { + std::vector x_row, y_row, z_row; + for (double j = -5; j <= 5; j += 0.25) { + x_row.push_back(i); + y_row.push_back(j); + z_row.push_back(::std::sin(::std::hypot(i, j))); + } + x.push_back(x_row); + y.push_back(y_row); + z.push_back(z_row); + } + + plt::plot_surface(x, y, z); + plt::show(); +} diff --git a/examples/surface.png b/examples/surface.png new file mode 100644 index 0000000000000000000000000000000000000000..6fc5fc76ba92343fee46f0951487a2463404ba24 GIT binary patch literal 106199 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 z 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�JR2!IvjUdLn*#_snfCTdZ!O=Gk8W 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 Date: Fri, 11 Jan 2019 20:30:18 +0100 Subject: [PATCH 024/110] Load mplot3d module lazily. Load the mplot3d module lazily when `plot_surface()` is called, to avoid introducing accidental dependencies. --- matplotlibcpp.h | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 5d2f0e1..db636f8 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -116,12 +116,9 @@ struct _interpreter { PyObject* matplotlibname = PyString_FromString("matplotlib"); PyObject* pyplotname = PyString_FromString("matplotlib.pyplot"); - PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); - PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); - PyObject* pylabname = PyString_FromString("pylab"); PyObject* cmname = PyString_FromString("matplotlib.cm"); - if (!pyplotname || !pylabname || !matplotlibname || !mpl_toolkits || - !axis3d || !cmname) { + PyObject* pylabname = PyString_FromString("pylab"); + if (!pyplotname || !pylabname || !matplotlibname || !cmname) { throw std::runtime_error("couldnt create string"); } @@ -147,14 +144,6 @@ struct _interpreter { Py_DECREF(pylabname); if (!pylabmod) { throw std::runtime_error("Error loading module pylab!"); } - PyObject* mpl_toolkitsmod = PyImport_Import(mpl_toolkits); - Py_DECREF(mpl_toolkitsmod); - if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } - - PyObject* axis3dmod = PyImport_Import(axis3d); - Py_DECREF(axis3dmod); - if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } - s_python_function_show = PyObject_GetAttrString(pymod, "show"); s_python_function_close = PyObject_GetAttrString(pymod, "close"); s_python_function_draw = PyObject_GetAttrString(pymod, "draw"); @@ -414,7 +403,29 @@ void plot_surface(const std::vector<::std::vector> &x, const std::vector<::std::vector> &y, const std::vector<::std::vector> &z, const std::map &keywords = - std::map()) { + std::map()) +{ + // 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()); From d89878b950e2587fee24c943a61944bf254d509c Mon Sep 17 00:00:00 2001 From: Tobias Tschirch Date: Mon, 11 Mar 2019 22:39:05 +0100 Subject: [PATCH 025/110] Add fill plot with example --- Makefile | 10 ++++++++-- examples/fill.cpp | 35 +++++++++++++++++++++++++++++++++++ examples/fill.png | Bin 0 -> 62995 bytes matplotlibcpp.h | 37 +++++++++++++++++++++++++++++++++++-- 4 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 examples/fill.cpp create mode 100644 examples/fill.png diff --git a/Makefile b/Makefile index 3c4ac36..50a7af8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -examples: minimal basic modern animation nonblock xkcd quiver bar surface +examples: minimal basic modern animation nonblock xkcd quiver bar surface fill_inbetween fill minimal: examples/minimal.cpp matplotlibcpp.h cd examples && g++ -DWITHOUT_NUMPY minimal.cpp -I/usr/include/python2.7 -lpython2.7 -o minimal -std=c++11 @@ -27,5 +27,11 @@ bar: examples/bar.cpp matplotlibcpp.h surface: examples/surface.cpp matplotlibcpp.h cd examples && g++ surface.cpp -I/usr/include/python2.7 -lpython2.7 -o surface -std=c++11 +fill_inbetween: examples/fill_inbetween.cpp matplotlibcpp.h + cd examples && g++ fill_inbetween.cpp -I/usr/include/python2.7 -lpython2.7 -o fill_inbetween -std=c++11 + +fill: examples/fill.cpp matplotlibcpp.h + cd examples && g++ fill.cpp -I/usr/include/python2.7 -lpython2.7 -o fill -std=c++11 + clean: - rm -f examples/{minimal,basic,modern,animation,nonblock,xkcd,quiver,bar} + rm -f examples/{minimal,basic,modern,animation,nonblock,xkcd,quiver,bar,surface,fill_inbetween,fill} diff --git a/examples/fill.cpp b/examples/fill.cpp new file mode 100644 index 0000000..6059b47 --- /dev/null +++ b/examples/fill.cpp @@ -0,0 +1,35 @@ +#define _USE_MATH_DEFINES +#include "../matplotlibcpp.h" +#include + +using namespace std; +namespace plt = matplotlibcpp; + +// Example fill plot taken from: +// https://matplotlib.org/gallery/misc/fill_spiral.html +int main() { + // Prepare data. + vector theta; + for (double d = 0; d < 8 * M_PI; d += 0.1) + theta.push_back(d); + + const int a = 1; + const double b = 0.2; + + for (double dt = 0; dt < 2 * M_PI; dt += M_PI/2.0) { + vector x1, y1, x2, y2; + for (double th : theta) { + x1.push_back( a*cos(th + dt) * exp(b*th) ); + y1.push_back( a*sin(th + dt) * exp(b*th) ); + + x2.push_back( a*cos(th + dt + M_PI/4.0) * exp(b*th) ); + y2.push_back( a*sin(th + dt + M_PI/4.0) * exp(b*th) ); + } + + x1.insert(x1.end(), x2.rbegin(), x2.rend()); + y1.insert(y1.end(), y2.rbegin(), y2.rend()); + + plt::fill(x1, y1, {}); + } + plt::show(); +} diff --git a/examples/fill.png b/examples/fill.png new file mode 100644 index 0000000000000000000000000000000000000000..aa1fc0db3665bb4e38800974b85b238318851341 GIT binary patch literal 62995 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 G{eJ)oR^-kA literal 0 HcmV?d00001 diff --git a/matplotlibcpp.h b/matplotlibcpp.h index db636f8..4034f4f 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -39,6 +39,7 @@ struct _interpreter { PyObject *s_python_function_semilogx; PyObject *s_python_function_semilogy; PyObject *s_python_function_loglog; + PyObject *s_python_function_fill; PyObject *s_python_function_fill_between; PyObject *s_python_function_hist; PyObject *s_python_function_scatter; @@ -155,6 +156,7 @@ struct _interpreter { s_python_function_semilogx = PyObject_GetAttrString(pymod, "semilogx"); s_python_function_semilogy = PyObject_GetAttrString(pymod, "semilogy"); s_python_function_loglog = PyObject_GetAttrString(pymod, "loglog"); + s_python_function_fill = PyObject_GetAttrString(pymod, "fill"); s_python_function_fill_between = PyObject_GetAttrString(pymod, "fill_between"); s_python_function_hist = PyObject_GetAttrString(pymod,"hist"); s_python_function_scatter = PyObject_GetAttrString(pymod,"scatter"); @@ -194,6 +196,7 @@ struct _interpreter { || !s_python_function_semilogx || !s_python_function_semilogy || !s_python_function_loglog + || !s_python_function_fill || !s_python_function_fill_between || !s_python_function_subplot || !s_python_function_legend @@ -231,6 +234,7 @@ struct _interpreter { || !PyFunction_Check(s_python_function_semilogx) || !PyFunction_Check(s_python_function_semilogy) || !PyFunction_Check(s_python_function_loglog) + || !PyFunction_Check(s_python_function_fill) || !PyFunction_Check(s_python_function_fill_between) || !PyFunction_Check(s_python_function_subplot) || !PyFunction_Check(s_python_function_legend) @@ -523,6 +527,36 @@ bool stem(const std::vector &x, const std::vector &y, const st return res; } +template< typename Numeric > +bool fill(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); + + // construct positional args + PyObject* args = PyTuple_New(2); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for (auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_fill, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + + if (res) Py_DECREF(res); + + return res; +} + template< typename Numeric > bool fill_between(const std::vector& x, const std::vector& y1, const std::vector& y2, const std::map& keywords) { @@ -542,8 +576,7 @@ bool fill_between(const std::vector& x, const std::vector& y1, // construct keyword args PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) - { + 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())); } From d42a969cc10978a5e4490d50a51e301ce2c5e7d7 Mon Sep 17 00:00:00 2001 From: Patric Schmitz Date: Thu, 24 Jan 2019 16:54:19 +0100 Subject: [PATCH 026/110] Add portability #defines PyLong_FromLong, PyUnicode_FromString --- matplotlibcpp.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 4034f4f..356f9f2 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -18,6 +18,8 @@ #if PY_MAJOR_VERSION >= 3 # define PyString_FromString PyUnicode_FromString +# define PyInt_FromLong PyLong_FromLong +# define PyString_FromString PyUnicode_FromString #endif From 05087cf86ea6100a6a723a9b65d97dc90566b497 Mon Sep 17 00:00:00 2001 From: Patric Schmitz Date: Thu, 24 Jan 2019 16:55:11 +0100 Subject: [PATCH 027/110] Additional debug output if python module loading fails --- matplotlibcpp.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 356f9f2..6c145ef 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -122,12 +122,15 @@ struct _interpreter { PyObject* cmname = PyString_FromString("matplotlib.cm"); PyObject* pylabname = PyString_FromString("pylab"); if (!pyplotname || !pylabname || !matplotlibname || !cmname) { - throw std::runtime_error("couldnt create string"); + throw std::runtime_error("couldnt create string"); } PyObject* matplotlib = PyImport_Import(matplotlibname); Py_DECREF(matplotlibname); - if (!matplotlib) { throw std::runtime_error("Error loading module matplotlib!"); } + if (!matplotlib) { + PyErr_Print(); + throw std::runtime_error("Error loading module matplotlib!"); + } // matplotlib.use() must be called *before* pylab, matplotlib.pyplot, // or matplotlib.backends is imported for the first time From d184e14673541ff9bae42331cd1cc8a03e2e63c9 Mon Sep 17 00:00:00 2001 From: Patric Schmitz Date: Thu, 24 Jan 2019 16:55:40 +0100 Subject: [PATCH 028/110] Add cumulative parameter to hist() --- matplotlibcpp.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 6c145ef..826854b 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -595,7 +595,8 @@ bool fill_between(const std::vector& x, const std::vector& y1, } template< typename Numeric> -bool hist(const std::vector& y, long bins=10,std::string color="b", double alpha=1.0) +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); @@ -604,6 +605,7 @@ bool hist(const std::vector& y, long bins=10,std::string color="b", dou PyDict_SetItemString(kwargs, "bins", PyLong_FromLong(bins)); PyDict_SetItemString(kwargs, "color", PyString_FromString(color.c_str())); PyDict_SetItemString(kwargs, "alpha", PyFloat_FromDouble(alpha)); + PyDict_SetItemString(kwargs, "cumulative", cumulative ? Py_True : Py_False); PyObject* plot_args = PyTuple_New(1); From f64f1f6583e468abf2871b3137e9012a80162357 Mon Sep 17 00:00:00 2001 From: Paul Jurczak Date: Wed, 13 Mar 2019 01:21:03 -0600 Subject: [PATCH 029/110] Added missing header VS2017 failed to compile due to missing `#include ` --- matplotlibcpp.h | 1 + 1 file changed, 1 insertion(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 826854b..ff722c2 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include From 8babc98e0f3d1b33008e827f8b9267a1bd65c470 Mon Sep 17 00:00:00 2001 From: William Lee Date: Wed, 20 Mar 2019 10:36:47 +0800 Subject: [PATCH 030/110] Add kwargs for title, xlabel, ylabel --- matplotlibcpp.h | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index ff722c2..431ba3a 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1321,29 +1321,41 @@ inline void subplot(long nrows, long ncols, long plot_number) Py_DECREF(res); } -inline void title(const std::string &titlestr) +inline void title(const std::string &titlestr, const std::map &keywords = {}) { PyObject* pytitlestr = PyString_FromString(titlestr.c_str()); PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, pytitlestr); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_title, args); + PyObject* kwargs = PyDict_New(); + for (auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_title, args, kwargs); if(!res) throw std::runtime_error("Call to title() failed."); Py_DECREF(args); + Py_DECREF(kwargs); Py_DECREF(res); } -inline void suptitle(const std::string &suptitlestr) +inline void suptitle(const std::string &suptitlestr, const std::map &keywords = {}) { PyObject* pysuptitlestr = PyString_FromString(suptitlestr.c_str()); PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, pysuptitlestr); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_suptitle, args); + PyObject* kwargs = PyDict_New(); + for (auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_suptitle, args, kwargs); if(!res) throw std::runtime_error("Call to suptitle() failed."); Py_DECREF(args); + Py_DECREF(kwargs); Py_DECREF(res); } @@ -1360,29 +1372,41 @@ inline void axis(const std::string &axisstr) Py_DECREF(res); } -inline void xlabel(const std::string &str) +inline void xlabel(const std::string &str, const std::map &keywords = {}) { PyObject* pystr = PyString_FromString(str.c_str()); PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, pystr); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_xlabel, args); + PyObject* kwargs = PyDict_New(); + for (auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_xlabel, args, kwargs); if(!res) throw std::runtime_error("Call to xlabel() failed."); Py_DECREF(args); + Py_DECREF(kwargs); Py_DECREF(res); } -inline void ylabel(const std::string &str) +inline void ylabel(const std::string &str, const std::map& keywords = {}) { PyObject* pystr = PyString_FromString(str.c_str()); PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, pystr); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_ylabel, args); + PyObject* kwargs = PyDict_New(); + for (auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_ylabel, args, kwargs); if(!res) throw std::runtime_error("Call to ylabel() failed."); Py_DECREF(args); + Py_DECREF(kwargs); Py_DECREF(res); } From 07a73fad22b185de42844dd110da7eb14197b833 Mon Sep 17 00:00:00 2001 From: Olivier Kermorgant Date: Wed, 13 Mar 2019 18:21:31 +0100 Subject: [PATCH 031/110] Add dynamic plot class --- Makefile | 7 ++- examples/update.cpp | 112 ++++++++++++++++++++++++++++++++++++++++++++ matplotlibcpp.h | 104 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 examples/update.cpp diff --git a/Makefile b/Makefile index 50a7af8..1df85e6 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -examples: minimal basic modern animation nonblock xkcd quiver bar surface fill_inbetween fill +examples: minimal basic modern animation nonblock xkcd quiver bar surface fill_inbetween fill update minimal: examples/minimal.cpp matplotlibcpp.h cd examples && g++ -DWITHOUT_NUMPY minimal.cpp -I/usr/include/python2.7 -lpython2.7 -o minimal -std=c++11 @@ -32,6 +32,9 @@ fill_inbetween: examples/fill_inbetween.cpp matplotlibcpp.h fill: examples/fill.cpp matplotlibcpp.h cd examples && g++ fill.cpp -I/usr/include/python2.7 -lpython2.7 -o fill -std=c++11 + +update: examples/update.cpp matplotlibcpp.h + cd examples && g++ update.cpp -I/usr/include/python2.7 -lpython2.7 -o update -std=c++11 clean: - rm -f examples/{minimal,basic,modern,animation,nonblock,xkcd,quiver,bar,surface,fill_inbetween,fill} + rm -f examples/{minimal,basic,modern,animation,nonblock,xkcd,quiver,bar,surface,fill_inbetween,fill,update} diff --git a/examples/update.cpp b/examples/update.cpp new file mode 100644 index 0000000..7027ba4 --- /dev/null +++ b/examples/update.cpp @@ -0,0 +1,112 @@ +#define _USE_MATH_DEFINES +#include +#include "../matplotlibcpp.h" +#include + +namespace plt = matplotlibcpp; + +void update_window(const double x, const double y, const double t, + std::vector &xt, std::vector &yt) +{ + const double target_length = 300; + const double half_win = (target_length/(2.*sqrt(1.+t*t))); + + xt[0] = x - half_win; + xt[1] = x + half_win; + yt[0] = y - half_win*t; + yt[1] = y + half_win*t; +} + + +int main() +{ + + bool use_dynamic_plot = false; + bool timeit = true; + + size_t n = 1000; + std::vector x, y; + + const double w = 0.05; + const double a = n/2; + + for(size_t i=0; i xt(2), yt(2); + + plt::title("Sample figure"); + + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + + if(use_dynamic_plot) + { + plt::xlim(x.front(), x.back()); + plt::ylim(-a,a); + plt::axis("equal"); + + // plot sin once and for all + plt::named_plot("sin", x, y); + + // prepare plotting the tangent + plt::Plot plot("tangent"); + + plt::legend(); + + for(size_t i=0; i + (end-start).count(); + if(use_dynamic_plot) + std::cout << "dynamic"; + else + std::cout << "static"; + + std::cout << " : " << elapsed_seconds/1000 << " ms\n"; +} diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 431ba3a..2797295 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1439,7 +1439,7 @@ inline void show(const bool block = true) PyObject *kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "block", Py_False); res = PyObject_Call( detail::_interpreter::get().s_python_function_show, detail::_interpreter::get().s_python_empty_tuple, kwargs); - Py_DECREF(kwargs); + Py_DECREF(kwargs); } @@ -1706,4 +1706,106 @@ inline bool plot(const std::vector& x, const std::vector& y, con return plot(x,y,keywords); } +/* + * This class allows dynamic plots, ie changing the plotted data without clearing and re-plotting + */ + +class Plot +{ +public: + // default initialization with plot label, some data and format + template + Plot(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { + + assert(x.size() == y.size()); + + PyObject* kwargs = PyDict_New(); + if(name != "") + PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + + PyObject* xarray = get_array(x); + PyObject* yarray = get_array(y); + + PyObject* pystring = PyString_FromString(format.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, plot_args, kwargs); + + Py_DECREF(kwargs); + Py_DECREF(plot_args); + + if(res) + { + line= PyList_GetItem(res, 0); + + if(line) + set_data_fct = PyObject_GetAttrString(line,"set_data"); + else + Py_DECREF(line); + Py_DECREF(res); + } + } + + // shorter initialization with name or format only + // basically calls line, = plot([], []) + Plot(const std::string& name = "", const std::string& format = "") + : Plot(name, std::vector(), std::vector(), format) {} + + template + bool update(const std::vector& x, const std::vector& y) { + assert(x.size() == y.size()); + if(set_data_fct) + { + PyObject* xarray = get_array(x); + PyObject* yarray = get_array(y); + + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + + PyObject* res = PyObject_CallObject(set_data_fct, plot_args); + if (res) Py_DECREF(res); + return res; + } + return false; + } + + // clears the plot but keep it available + bool clear() { + return update(std::vector(), std::vector()); + } + + // definitely remove this line + void remove() { + if(line) + { + auto remove_fct = PyObject_GetAttrString(line,"remove"); + PyObject* args = PyTuple_New(0); + PyObject* res = PyObject_CallObject(remove_fct, args); + if (res) Py_DECREF(res); + } + decref(); + } + + ~Plot() { + decref(); + } +private: + + void decref() { + if(line) + Py_DECREF(line); + if(set_data_fct) + Py_DECREF(set_data_fct); + } + + + PyObject* line = nullptr; + PyObject* set_data_fct = nullptr; +}; + } // end namespace matplotlibcpp From c48ed308d1065464365a9287a8dadeacb2fd2c4b Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Sun, 7 Apr 2019 12:39:03 +0200 Subject: [PATCH 032/110] Reduced example for dynamic plot class. Removed timing and comparison code paths from the example for the dynamic plot class, to make it easier to spot the essential usage to the casual reader. --- examples/update.cpp | 88 ++++++++++----------------------------------- 1 file changed, 18 insertions(+), 70 deletions(-) diff --git a/examples/update.cpp b/examples/update.cpp index 7027ba4..4b7be46 100644 --- a/examples/update.cpp +++ b/examples/update.cpp @@ -20,93 +20,41 @@ void update_window(const double x, const double y, const double t, int main() { - - bool use_dynamic_plot = false; - bool timeit = true; - size_t n = 1000; std::vector x, y; const double w = 0.05; const double a = n/2; - for(size_t i=0; i xt(2), yt(2); - plt::title("Sample figure"); - - std::chrono::time_point start, end; - start = std::chrono::system_clock::now(); - - if(use_dynamic_plot) - { - plt::xlim(x.front(), x.back()); - plt::ylim(-a,a); - plt::axis("equal"); - - // plot sin once and for all - plt::named_plot("sin", x, y); - - // prepare plotting the tangent - plt::Plot plot("tangent"); - - plt::legend(); - - for(size_t i=0; i - (end-start).count(); - if(use_dynamic_plot) - std::cout << "dynamic"; - else - std::cout << "static"; - - std::cout << " : " << elapsed_seconds/1000 << " ms\n"; + } } From 94c0215d6aa87c6b394ab2990e958dfe57c0d001 Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Mon, 8 Apr 2019 11:38:01 +0200 Subject: [PATCH 033/110] Fixed type in update example. --- examples/update.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/update.cpp b/examples/update.cpp index 4b7be46..64f4906 100644 --- a/examples/update.cpp +++ b/examples/update.cpp @@ -33,7 +33,7 @@ int main() std::vector xt(2), yt(2); - plt::title("Tangent of a since curve"); + plt::title("Tangent of a sine curve"); plt::xlim(x.front(), x.back()); plt::ylim(-a, a); plt::axis("equal"); From 4ace1ad946e2782cac0539be96cee6ec5e9d8ba8 Mon Sep 17 00:00:00 2001 From: jul Date: Thu, 30 May 2019 21:10:19 +0200 Subject: [PATCH 034/110] fix the figsize segfault bug plt::figure_size is known to sometimes cause segfaults if called without a preceding plt::figure as suggested in the issues this is fixed by calling detail::_interpreter::get() at the beginning of the function --- matplotlibcpp.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 2797295..30b21ee 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1116,6 +1116,9 @@ inline bool fignum_exists(long number) inline void figure_size(size_t w, size_t h) { + // Make sure interpreter is initialised + detail::_interpreter::get(); + const size_t dpi = 100; PyObject* size = PyTuple_New(2); PyTuple_SetItem(size, 0, PyFloat_FromDouble((double)w / dpi)); From 019cd237a6be26df33aa1a7efa77dfba171d4fc0 Mon Sep 17 00:00:00 2001 From: Trevor Keller Date: Sun, 12 May 2019 12:35:33 -0400 Subject: [PATCH 035/110] check functions with narrower error message scope --- matplotlibcpp.h | 165 +++++++++++++++--------------------------------- 1 file changed, 51 insertions(+), 114 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 30b21ee..8194ec8 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -84,6 +84,18 @@ struct _interpreter { return ctx; } + PyObject* safe_import(PyObject* module, std::string fname) { + PyObject* fn = PyObject_GetAttrString(module, fname.c_str()); + + if (!fn) + throw std::runtime_error(std::string("Couldn't find required function: ") + fname); + + if (!PyFunction_Check(fn)) + throw std::runtime_error(fname + std::string(" is unexpectedly not a PyFunction.")); + + return fn; + } + private: #ifndef WITHOUT_NUMPY @@ -151,120 +163,45 @@ struct _interpreter { Py_DECREF(pylabname); if (!pylabmod) { throw std::runtime_error("Error loading module pylab!"); } - s_python_function_show = PyObject_GetAttrString(pymod, "show"); - s_python_function_close = PyObject_GetAttrString(pymod, "close"); - s_python_function_draw = PyObject_GetAttrString(pymod, "draw"); - s_python_function_pause = PyObject_GetAttrString(pymod, "pause"); - s_python_function_figure = PyObject_GetAttrString(pymod, "figure"); - s_python_function_fignum_exists = PyObject_GetAttrString(pymod, "fignum_exists"); - s_python_function_plot = PyObject_GetAttrString(pymod, "plot"); - s_python_function_quiver = PyObject_GetAttrString(pymod, "quiver"); - s_python_function_semilogx = PyObject_GetAttrString(pymod, "semilogx"); - s_python_function_semilogy = PyObject_GetAttrString(pymod, "semilogy"); - s_python_function_loglog = PyObject_GetAttrString(pymod, "loglog"); - s_python_function_fill = PyObject_GetAttrString(pymod, "fill"); - s_python_function_fill_between = PyObject_GetAttrString(pymod, "fill_between"); - s_python_function_hist = PyObject_GetAttrString(pymod,"hist"); - s_python_function_scatter = PyObject_GetAttrString(pymod,"scatter"); - s_python_function_subplot = PyObject_GetAttrString(pymod, "subplot"); - s_python_function_legend = PyObject_GetAttrString(pymod, "legend"); - s_python_function_ylim = PyObject_GetAttrString(pymod, "ylim"); - s_python_function_title = PyObject_GetAttrString(pymod, "title"); - s_python_function_axis = PyObject_GetAttrString(pymod, "axis"); - s_python_function_xlabel = PyObject_GetAttrString(pymod, "xlabel"); - s_python_function_ylabel = PyObject_GetAttrString(pymod, "ylabel"); - s_python_function_xticks = PyObject_GetAttrString(pymod, "xticks"); - s_python_function_yticks = PyObject_GetAttrString(pymod, "yticks"); - s_python_function_grid = PyObject_GetAttrString(pymod, "grid"); - s_python_function_xlim = PyObject_GetAttrString(pymod, "xlim"); - s_python_function_ion = PyObject_GetAttrString(pymod, "ion"); - s_python_function_ginput = PyObject_GetAttrString(pymod, "ginput"); - s_python_function_save = PyObject_GetAttrString(pylabmod, "savefig"); - s_python_function_annotate = PyObject_GetAttrString(pymod,"annotate"); - s_python_function_clf = PyObject_GetAttrString(pymod, "clf"); - s_python_function_errorbar = PyObject_GetAttrString(pymod, "errorbar"); - s_python_function_tight_layout = PyObject_GetAttrString(pymod, "tight_layout"); - s_python_function_stem = PyObject_GetAttrString(pymod, "stem"); - s_python_function_xkcd = PyObject_GetAttrString(pymod, "xkcd"); - s_python_function_text = PyObject_GetAttrString(pymod, "text"); - s_python_function_suptitle = PyObject_GetAttrString(pymod, "suptitle"); - s_python_function_bar = PyObject_GetAttrString(pymod,"bar"); - s_python_function_subplots_adjust = PyObject_GetAttrString(pymod,"subplots_adjust"); - - if( !s_python_function_show - || !s_python_function_close - || !s_python_function_draw - || !s_python_function_pause - || !s_python_function_figure - || !s_python_function_fignum_exists - || !s_python_function_plot - || !s_python_function_quiver - || !s_python_function_semilogx - || !s_python_function_semilogy - || !s_python_function_loglog - || !s_python_function_fill - || !s_python_function_fill_between - || !s_python_function_subplot - || !s_python_function_legend - || !s_python_function_ylim - || !s_python_function_title - || !s_python_function_axis - || !s_python_function_xlabel - || !s_python_function_ylabel - || !s_python_function_grid - || !s_python_function_xlim - || !s_python_function_ion - || !s_python_function_ginput - || !s_python_function_save - || !s_python_function_clf - || !s_python_function_annotate - || !s_python_function_errorbar - || !s_python_function_errorbar - || !s_python_function_tight_layout - || !s_python_function_stem - || !s_python_function_xkcd - || !s_python_function_text - || !s_python_function_suptitle - || !s_python_function_bar - || !s_python_function_subplots_adjust - ) { throw std::runtime_error("Couldn't find required function!"); } - - if ( !PyFunction_Check(s_python_function_show) - || !PyFunction_Check(s_python_function_close) - || !PyFunction_Check(s_python_function_draw) - || !PyFunction_Check(s_python_function_pause) - || !PyFunction_Check(s_python_function_figure) - || !PyFunction_Check(s_python_function_fignum_exists) - || !PyFunction_Check(s_python_function_plot) - || !PyFunction_Check(s_python_function_quiver) - || !PyFunction_Check(s_python_function_semilogx) - || !PyFunction_Check(s_python_function_semilogy) - || !PyFunction_Check(s_python_function_loglog) - || !PyFunction_Check(s_python_function_fill) - || !PyFunction_Check(s_python_function_fill_between) - || !PyFunction_Check(s_python_function_subplot) - || !PyFunction_Check(s_python_function_legend) - || !PyFunction_Check(s_python_function_annotate) - || !PyFunction_Check(s_python_function_ylim) - || !PyFunction_Check(s_python_function_title) - || !PyFunction_Check(s_python_function_axis) - || !PyFunction_Check(s_python_function_xlabel) - || !PyFunction_Check(s_python_function_ylabel) - || !PyFunction_Check(s_python_function_grid) - || !PyFunction_Check(s_python_function_xlim) - || !PyFunction_Check(s_python_function_ion) - || !PyFunction_Check(s_python_function_ginput) - || !PyFunction_Check(s_python_function_save) - || !PyFunction_Check(s_python_function_clf) - || !PyFunction_Check(s_python_function_tight_layout) - || !PyFunction_Check(s_python_function_errorbar) - || !PyFunction_Check(s_python_function_stem) - || !PyFunction_Check(s_python_function_xkcd) - || !PyFunction_Check(s_python_function_text) - || !PyFunction_Check(s_python_function_suptitle) - || !PyFunction_Check(s_python_function_bar) - || !PyFunction_Check(s_python_function_subplots_adjust) - ) { throw std::runtime_error("Python object is unexpectedly not a PyFunction."); } + s_python_function_show = safe_import(pymod, "show"); + s_python_function_close = safe_import(pymod, "close"); + s_python_function_draw = safe_import(pymod, "draw"); + s_python_function_pause = safe_import(pymod, "pause"); + s_python_function_figure = safe_import(pymod, "figure"); + s_python_function_fignum_exists = safe_import(pymod, "fignum_exists"); + s_python_function_plot = safe_import(pymod, "plot"); + s_python_function_quiver = safe_import(pymod, "quiver"); + s_python_function_semilogx = safe_import(pymod, "semilogx"); + s_python_function_semilogy = safe_import(pymod, "semilogy"); + s_python_function_loglog = safe_import(pymod, "loglog"); + s_python_function_fill = safe_import(pymod, "fill"); + s_python_function_fill_between = safe_import(pymod, "fill_between"); + s_python_function_hist = safe_import(pymod,"hist"); + s_python_function_scatter = safe_import(pymod,"scatter"); + s_python_function_subplot = safe_import(pymod, "subplot"); + 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_xlabel = safe_import(pymod, "xlabel"); + s_python_function_ylabel = safe_import(pymod, "ylabel"); + s_python_function_xticks = safe_import(pymod, "xticks"); + s_python_function_yticks = safe_import(pymod, "yticks"); + s_python_function_grid = safe_import(pymod, "grid"); + s_python_function_xlim = safe_import(pymod, "xlim"); + s_python_function_ion = safe_import(pymod, "ion"); + s_python_function_ginput = safe_import(pymod, "ginput"); + s_python_function_save = safe_import(pylabmod, "savefig"); + s_python_function_annotate = safe_import(pymod,"annotate"); + s_python_function_clf = safe_import(pymod, "clf"); + s_python_function_errorbar = safe_import(pymod, "errorbar"); + s_python_function_tight_layout = safe_import(pymod, "tight_layout"); + s_python_function_stem = safe_import(pymod, "stem"); + s_python_function_xkcd = safe_import(pymod, "xkcd"); + s_python_function_text = safe_import(pymod, "text"); + s_python_function_suptitle = safe_import(pymod, "suptitle"); + s_python_function_bar = safe_import(pymod,"bar"); + s_python_function_subplots_adjust = safe_import(pymod,"subplots_adjust"); s_python_empty_tuple = PyTuple_New(0); } From 20e090ef9e1cab8bd28ca629e34538b297a43ffd Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Tue, 6 Nov 2018 12:04:28 +0000 Subject: [PATCH 036/110] Add support for imshow() This PR adds support for the imshow() function to matplotlibcpp. imshow() displays an image in the current figure and allows for setting extra parameters such as the colormap used. I've also added a version of the function which takes an OpenCV matrix as an input (in a separate commit), which provides a nicer interface. This requires OpenCV, but I've made this functionality optional: you have to explicitly define WITH_OPENCV if you want it. --- matplotlibcpp.h | 80 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 8194ec8..132cc34 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -15,6 +15,10 @@ #ifndef WITHOUT_NUMPY # define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION # include + +# ifdef WITH_OPENCV +# include +# endif // WITH_OPENCV #endif // WITHOUT_NUMPY #if PY_MAJOR_VERSION >= 3 @@ -45,6 +49,7 @@ struct _interpreter { PyObject *s_python_function_fill; PyObject *s_python_function_fill_between; PyObject *s_python_function_hist; + PyObject *s_python_function_imshow; PyObject *s_python_function_scatter; PyObject *s_python_function_subplot; PyObject *s_python_function_legend; @@ -202,6 +207,9 @@ struct _interpreter { s_python_function_suptitle = safe_import(pymod, "suptitle"); s_python_function_bar = safe_import(pymod,"bar"); 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); } @@ -560,6 +568,78 @@ bool hist(const std::vector& y, long bins=10,std::string color="b", return res; } +#ifndef WITHOUT_NUMPY + namespace internal { + 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); + } + } + + 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); + } + + void imshow(const float *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}) + { + internal::imshow((void *) ptr, NPY_FLOAT, rows, columns, colors, keywords); + } + +#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 + template bool scatter(const std::vector& x, const std::vector& y, From bc7e4576b2123bf9851e4e779228e2374ef9862c Mon Sep 17 00:00:00 2001 From: Trevor Keller Date: Wed, 8 May 2019 00:45:29 -0400 Subject: [PATCH 037/110] Added basic example for imshow. This addresses comments on #79. --- Makefile | 9 ++++++--- examples/imshow.cpp | 29 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 examples/imshow.cpp diff --git a/Makefile b/Makefile index 1df85e6..f9e5366 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -examples: minimal basic modern animation nonblock xkcd quiver bar surface fill_inbetween fill update +examples: minimal basic modern animation nonblock xkcd quiver bar surface fill_inbetween fill update imshow minimal: examples/minimal.cpp matplotlibcpp.h cd examples && g++ -DWITHOUT_NUMPY minimal.cpp -I/usr/include/python2.7 -lpython2.7 -o minimal -std=c++11 @@ -32,9 +32,12 @@ fill_inbetween: examples/fill_inbetween.cpp matplotlibcpp.h fill: examples/fill.cpp matplotlibcpp.h cd examples && g++ fill.cpp -I/usr/include/python2.7 -lpython2.7 -o fill -std=c++11 - + update: examples/update.cpp matplotlibcpp.h cd examples && g++ update.cpp -I/usr/include/python2.7 -lpython2.7 -o update -std=c++11 +imshow: examples/imshow.cpp matplotlibcpp.h + cd examples && g++ imshow.cpp -I/usr/include/python2.7 -lpython2.7 -o imshow -std=c++11 + clean: - rm -f examples/{minimal,basic,modern,animation,nonblock,xkcd,quiver,bar,surface,fill_inbetween,fill,update} + rm -f examples/{minimal,basic,modern,animation,nonblock,xkcd,quiver,bar,surface,fill_inbetween,fill,update,imshow} diff --git a/examples/imshow.cpp b/examples/imshow.cpp new file mode 100644 index 0000000..b11661e --- /dev/null +++ b/examples/imshow.cpp @@ -0,0 +1,29 @@ +#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 Date: Fri, 19 Apr 2019 16:12:57 +0200 Subject: [PATCH 038/110] Add support for plot(y,keywords) There is a convenient function plot(y,format) already, which however doesn't allow to set additional parameters like labels, so add a similar function that supports keywords. --- matplotlibcpp.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 132cc34..e626c4c 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1061,6 +1061,14 @@ bool plot(const std::vector& y, const std::string& format = "") return plot(x,y,format); } +template +bool plot(const std::vector& y, const std::map& keywords) +{ + std::vector x(y.size()); + for(size_t i=0; i bool stem(const std::vector& y, const std::string& format = "") { From 2772217011029f9dee286a77f0d29f6508004bf0 Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Sun, 28 Jul 2019 13:21:58 +0100 Subject: [PATCH 039/110] Fix for OpenCV 4 In the latest version of OpenCV, several constants were removed in favour of enum classes. Fix by #define'ing the ones we need. --- matplotlibcpp.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index e626c4c..ae78c98 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -17,8 +17,17 @@ # include # ifdef WITH_OPENCV -# include +# include # endif // WITH_OPENCV + +/* + * A bunch of constants were removed in OpenCV 4 in favour of enum classes, so + * define the ones we need here. + */ +# if CV_MAJOR_VERSION > 3 +# define CV_BGR2RGB cv::COLOR_BGR2RGB +# define CV_BGRA2RGBA cv::COLOR_BGRA2RGBA +# endif #endif // WITHOUT_NUMPY #if PY_MAJOR_VERSION >= 3 From 7fa134ea1514a4181ddcd3f0e897490c8fabdbeb Mon Sep 17 00:00:00 2001 From: Florian Eich Date: Mon, 22 Jul 2019 11:29:59 +0200 Subject: [PATCH 040/110] Fixes compiler warnings --- matplotlibcpp.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index ae78c98..16e94f0 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -677,12 +677,12 @@ bool scatter(const std::vector& x, template< typename Numeric> bool bar(const std::vector& y, std::string ec = "black", std::string ls = "-", double lw = 1.0, - const std::map& keywords = {}) + __attribute__((unused)) const std::map& keywords = {}) { PyObject* yarray = get_array(y); std::vector x; - for (int i = 0; i < y.size(); i++) + for (std::size_t i = 0; i < y.size(); i++) x.push_back(i); PyObject* xarray = get_array(x); From f4ad842e70cc56a38f3e4cd852968c7c1cecc9a7 Mon Sep 17 00:00:00 2001 From: Florian Eich Date: Tue, 23 Jul 2019 00:02:17 +0200 Subject: [PATCH 041/110] Implements bar slightly more closely to matplotlib --- matplotlibcpp.h | 65 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 16e94f0..6a428d4 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -675,35 +675,56 @@ bool scatter(const std::vector& x, return res; } -template< typename Numeric> -bool bar(const std::vector& y, std::string ec = "black", std::string ls = "-", double lw = 1.0, - __attribute__((unused)) const std::map& keywords = {}) -{ - PyObject* yarray = get_array(y); - - std::vector x; - for (std::size_t i = 0; i < y.size(); i++) - x.push_back(i); +template +bool bar(const std::vector & x, + const std::vector & y, + std::string ec = "black", + std::string ls = "-", + double lw = 1.0, + const std::map & keywords = {}) { + PyObject * xarray = get_array(x); + PyObject * yarray = get_array(y); + + PyObject * kwargs = PyDict_New(); + + PyDict_SetItemString(kwargs, "ec", PyString_FromString(ec.c_str())); + PyDict_SetItemString(kwargs, "ls", PyString_FromString(ls.c_str())); + PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); + + for (std::map::const_iterator it = + keywords.begin(); + it != keywords.end(); + ++it) { + PyDict_SetItemString( + kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } - PyObject* xarray = get_array(x); + PyObject * plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); - PyObject* kwargs = PyDict_New(); + PyObject * res = PyObject_Call( + detail::_interpreter::get().s_python_function_bar, plot_args, kwargs); - PyDict_SetItemString(kwargs, "ec", PyString_FromString(ec.c_str())); - PyDict_SetItemString(kwargs, "ls", PyString_FromString(ls.c_str())); - PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if (res) Py_DECREF(res); - PyObject* plot_args = PyTuple_New(2); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); + return res; +} - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_bar, plot_args, kwargs); +template +bool bar(const std::vector & y, + std::string ec = "black", + std::string ls = "-", + double lw = 1.0, + const std::map & keywords = {}) { + using T = typename std::remove_reference::type::value_type; - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if(res) Py_DECREF(res); + std::vector x; + for (std::size_t i = 0; i < y.size(); i++) { x.push_back(i); } - return res; + return bar(x, y, ec, ls, lw, keywords); } inline bool subplots_adjust(const std::map& keywords = {}) From d3137c53b16419b80a2aa35fe2f20d36b77d75f3 Mon Sep 17 00:00:00 2001 From: pm-twice <47174886+pm-twice@users.noreply.github.com> Date: Wed, 25 Sep 2019 22:59:14 +0900 Subject: [PATCH 042/110] Fix for imshow multiple definition error #48 When multiple files include matplotlibcpp.h and another file load these files in the same time, compile error of multiple definition in imshow occurs. This fix add inline modifier to imshow(). --- matplotlibcpp.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 6a428d4..93f19c8 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -579,7 +579,7 @@ bool hist(const std::vector& y, long bins=10,std::string color="b", #ifndef WITHOUT_NUMPY namespace internal { - 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) { assert(type == NPY_UINT8 || type == NPY_FLOAT); assert(colors == 1 || colors == 3 || colors == 4); @@ -607,12 +607,12 @@ bool hist(const std::vector& y, long bins=10,std::string color="b", } } - 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 = {}) { internal::imshow((void *) ptr, NPY_UINT8, rows, columns, colors, keywords); } - 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 = {}) { internal::imshow((void *) ptr, NPY_FLOAT, rows, columns, colors, keywords); } From dbe8e4c63b2b82d3103d9d4854001334047717af Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Sun, 28 Jul 2019 12:58:29 +0100 Subject: [PATCH 043/110] Makefile: Better numpy handling and make building more generic Changes: * Use system version of python by default (overridable with PYTHON_BIN) * Find where numpy headers live on system * Remove boilerplate from Makefile and just build all examples/*.cpp files with appropriate flags --- Makefile | 52 +++++++++++++++++--------------------------------- numpy_flags.py | 12 ++++++++++++ 2 files changed, 29 insertions(+), 35 deletions(-) create mode 100644 numpy_flags.py diff --git a/Makefile b/Makefile index f9e5366..a251d64 100644 --- a/Makefile +++ b/Makefile @@ -1,43 +1,25 @@ -examples: minimal basic modern animation nonblock xkcd quiver bar surface fill_inbetween fill update imshow -minimal: examples/minimal.cpp matplotlibcpp.h - cd examples && g++ -DWITHOUT_NUMPY minimal.cpp -I/usr/include/python2.7 -lpython2.7 -o minimal -std=c++11 +# Use C++11 +CXXFLAGS += -std=c++11 -basic: examples/basic.cpp matplotlibcpp.h - cd examples && g++ basic.cpp -I/usr/include/python2.7 -lpython2.7 -o basic -std=c++11 +# 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) +LDFLAGS += $(shell $(PYTHON_CONFIG) --libs) -modern: examples/modern.cpp matplotlibcpp.h - cd examples && g++ modern.cpp -I/usr/include/python2.7 -lpython2.7 -o modern -std=c++11 +# Either finds numpy or set -DWITHOUT_NUMPY +CURRENT_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +CXXFLAGS += $(shell $(PYTHON_BIN) $(CURRENT_DIR)/numpy_flags.py) -animation: examples/animation.cpp matplotlibcpp.h - cd examples && g++ animation.cpp -I/usr/include/python2.7 -lpython2.7 -o animation -std=c++11 +# Assume every *.cpp file is a separate example +SOURCES ?= $(wildcard examples/*.cpp) +EXECUTABLES := $(foreach exec,$(basename $(SOURCES)),$(exec)) -nonblock: examples/nonblock.cpp matplotlibcpp.h - cd examples && g++ nonblock.cpp -I/usr/include/python2.7 -lpython2.7 -o nonblock -std=c++11 +.PHONY: examples -quiver: examples/quiver.cpp matplotlibcpp.h - cd examples && g++ quiver.cpp -I/usr/include/python2.7 -lpython2.7 -o quiver -std=c++11 - -xkcd: examples/xkcd.cpp matplotlibcpp.h - cd examples && g++ xkcd.cpp -I/usr/include/python2.7 -lpython2.7 -o xkcd -std=c++11 - -bar: examples/bar.cpp matplotlibcpp.h - cd examples && g++ bar.cpp -I/usr/include/python2.7 -lpython2.7 -o bar -std=c++11 - -surface: examples/surface.cpp matplotlibcpp.h - cd examples && g++ surface.cpp -I/usr/include/python2.7 -lpython2.7 -o surface -std=c++11 - -fill_inbetween: examples/fill_inbetween.cpp matplotlibcpp.h - cd examples && g++ fill_inbetween.cpp -I/usr/include/python2.7 -lpython2.7 -o fill_inbetween -std=c++11 - -fill: examples/fill.cpp matplotlibcpp.h - cd examples && g++ fill.cpp -I/usr/include/python2.7 -lpython2.7 -o fill -std=c++11 - -update: examples/update.cpp matplotlibcpp.h - cd examples && g++ update.cpp -I/usr/include/python2.7 -lpython2.7 -o update -std=c++11 - -imshow: examples/imshow.cpp matplotlibcpp.h - cd examples && g++ imshow.cpp -I/usr/include/python2.7 -lpython2.7 -o imshow -std=c++11 +examples: $(EXECUTABLES) clean: - rm -f examples/{minimal,basic,modern,animation,nonblock,xkcd,quiver,bar,surface,fill_inbetween,fill,update,imshow} + rm -f ${EXECUTABLES} diff --git a/numpy_flags.py b/numpy_flags.py new file mode 100644 index 0000000..56fd95c --- /dev/null +++ b/numpy_flags.py @@ -0,0 +1,12 @@ +from os import path + +try: + from numpy import __file__ as numpyloc + + # Get numpy directory + numpy_dir = path.dirname(numpyloc) + + # Print the result of joining this to core and include + print("-I" + path.join(numpy_dir, "core", "include")) +except: + print("-DWITHOUT_NUMPY") From 1c2a51f50b72e8b400f2353cd8cc512efae0df74 Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Sun, 28 Jul 2019 13:01:58 +0100 Subject: [PATCH 044/110] Add .gitignore file for example binaries --- examples/.gitignore | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 examples/.gitignore diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..3da8ad6 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,14 @@ +animation +bar +basic +fill +fill_inbetween +imshow +minimal +modern +nonblock +quiver +subplot +surface +update +xkcd From 6ec72daa6b5ba4e396bee3fdc562f89b9fda2744 Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Sun, 28 Jul 2019 13:30:09 +0100 Subject: [PATCH 045/110] Fix: Allow for building examples individually You don't necessarily want to build all of them. Now you can do, e.g.: make examples/minimal --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index a251d64..15c9f69 100644 --- a/Makefile +++ b/Makefile @@ -21,5 +21,8 @@ EXECUTABLES := $(foreach exec,$(basename $(SOURCES)),$(exec)) examples: $(EXECUTABLES) +$(EXECUTABLES): %: %.cpp + $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS) + clean: rm -f ${EXECUTABLES} From 1ac91f7593630cfd76a400b8531986c9d7d6f6b4 Mon Sep 17 00:00:00 2001 From: Trevor Keller Date: Sun, 12 May 2019 00:45:55 -0400 Subject: [PATCH 046/110] subplot2grid implementation and example --- examples/subplot2grid.cpp | 44 +++++++++++++++++++++++++++++++++++++++ matplotlibcpp.h | 33 ++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 examples/subplot2grid.cpp diff --git a/examples/subplot2grid.cpp b/examples/subplot2grid.cpp new file mode 100644 index 0000000..f590e51 --- /dev/null +++ b/examples/subplot2grid.cpp @@ -0,0 +1,44 @@ +#define _USE_MATH_DEFINES +#include +#include "../matplotlibcpp.h" + +using namespace std; +namespace plt = matplotlibcpp; + +int main() +{ + // Prepare data + int n = 500; + std::vector x(n), u(n), v(n), w(n); + for(int i=0; i &x, const std::vector &y, const st return res; } +// TODO - it should be possible to make this work by implementing +// a non-numpy alternative for `get_2darray()`. +#ifndef WITHOUT_NUMPY template void plot_surface(const std::vector<::std::vector> &x, const std::vector<::std::vector> &y, @@ -453,6 +458,8 @@ void plot_surface(const std::vector<::std::vector> &x, Py_DECREF(kwargs); if (res) Py_DECREF(res); } +#endif // WITHOUT_NUMPY + template bool stem(const std::vector &x, const std::vector &y, const std::map& keywords) @@ -1073,7 +1080,6 @@ bool named_loglog(const std::string& name, const std::vector& x, const PyTuple_SetItem(plot_args, 0, xarray); PyTuple_SetItem(plot_args, 1, yarray); PyTuple_SetItem(plot_args, 2, pystring); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_loglog, plot_args, kwargs); Py_DECREF(kwargs); @@ -1379,6 +1385,31 @@ inline void subplot(long nrows, long ncols, long plot_number) Py_DECREF(res); } +void subplot2grid(long nrows, long ncols, long rowid=0, long colid=0, long rowspan=1, long colspan=1) +{ + PyObject* shape = PyTuple_New(2); + PyTuple_SetItem(shape, 0, PyLong_FromLong(nrows)); + PyTuple_SetItem(shape, 1, PyLong_FromLong(ncols)); + + PyObject* loc = PyTuple_New(2); + PyTuple_SetItem(loc, 0, PyLong_FromLong(rowid)); + PyTuple_SetItem(loc, 1, PyLong_FromLong(colid)); + + PyObject* args = PyTuple_New(4); + PyTuple_SetItem(args, 0, shape); + PyTuple_SetItem(args, 1, loc); + PyTuple_SetItem(args, 2, PyLong_FromLong(rowspan)); + PyTuple_SetItem(args, 3, PyLong_FromLong(colspan)); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_subplot2grid, args); + if(!res) throw std::runtime_error("Call to subplot2grid() failed."); + + Py_DECREF(shape); + Py_DECREF(loc); + Py_DECREF(args); + Py_DECREF(res); +} + inline void title(const std::string &titlestr, const std::map &keywords = {}) { PyObject* pytitlestr = PyString_FromString(titlestr.c_str()); From 5adfbe031e9a9a50b9f4f054aceba4cfd805fa52 Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Fri, 25 Oct 2019 03:25:48 +0200 Subject: [PATCH 047/110] Rearrange Makefile. --- Makefile | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 15c9f69..4faeb0e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,3 @@ - # Use C++11 CXXFLAGS += -std=c++11 @@ -10,19 +9,25 @@ CXXFLAGS += $(PYTHON_INCLUDE) LDFLAGS += $(shell $(PYTHON_CONFIG) --libs) # Either finds numpy or set -DWITHOUT_NUMPY -CURRENT_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) -CXXFLAGS += $(shell $(PYTHON_BIN) $(CURRENT_DIR)/numpy_flags.py) +CXXFLAGS += $(shell $(PYTHON_BIN) $(CURDIR)/numpy_flags.py) +WITHOUT_NUMPY := $(findstring $(CXXFLAGS), WITHOUT_NUMPY) -# Assume every *.cpp file is a separate example -SOURCES ?= $(wildcard examples/*.cpp) -EXECUTABLES := $(foreach exec,$(basename $(SOURCES)),$(exec)) +# 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)) + +# Prefix every example with 'examples/build/' +EXAMPLE_TARGETS := $(patsubst %,examples/build/%,$(EXAMPLES)) .PHONY: examples -examples: $(EXECUTABLES) +examples: $(EXAMPLE_TARGETS) -$(EXECUTABLES): %: %.cpp +# Assume every *.cpp file is a separate example +$(EXAMPLE_TARGETS): examples/build/%: examples/%.cpp + mkdir -p examples/build $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS) clean: - rm -f ${EXECUTABLES} + rm -f ${EXAMPLE_TARGETS} From 97729fd2ccf71873479a32e6577e0e58554e00dd Mon Sep 17 00:00:00 2001 From: Matthieu Zins Date: Sun, 24 Nov 2019 22:06:49 +0100 Subject: [PATCH 048/110] Add possibility to pass additional parameters to scatter (like color or label) --- matplotlibcpp.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 82dbc6b..ac28937 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -659,7 +659,8 @@ bool hist(const std::vector& y, long bins=10,std::string color="b", template bool scatter(const std::vector& x, const std::vector& y, - const double s=1.0) // The marker size in points**2 + const double s=1.0, // The marker size in points**2 + const std::unordered_map & keywords = {}) { assert(x.size() == y.size()); @@ -668,6 +669,10 @@ bool scatter(const std::vector& x, PyObject* kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "s", PyLong_FromLong(s)); + for (const auto& it : keywords) + { + PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); + } PyObject* plot_args = PyTuple_New(2); PyTuple_SetItem(plot_args, 0, xarray); From 6fb6a91568380614b7576587a4e19680b08e3fa2 Mon Sep 17 00:00:00 2001 From: Matthieu Zins Date: Wed, 27 Nov 2019 07:09:27 +0100 Subject: [PATCH 049/110] Fix multiple definition of subplot2grid --- matplotlibcpp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index ac28937..8ae5b84 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1390,7 +1390,7 @@ inline void subplot(long nrows, long ncols, long plot_number) Py_DECREF(res); } -void subplot2grid(long nrows, long ncols, long rowid=0, long colid=0, long rowspan=1, long colspan=1) +inline void subplot2grid(long nrows, long ncols, long rowid=0, long colid=0, long rowspan=1, long colspan=1) { PyObject* shape = PyTuple_New(2); PyTuple_SetItem(shape, 0, PyLong_FromLong(nrows)); From 8297ae81a09cd48c02bf7615596663db7a3345dc Mon Sep 17 00:00:00 2001 From: belre9 Date: Mon, 18 Nov 2019 18:43:13 +0900 Subject: [PATCH 050/110] Implemented wrapper corresponding to tick_params in matplotlib --- matplotlibcpp.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 8ae5b84..c4d5d14 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -73,6 +73,7 @@ struct _interpreter { PyObject *s_python_function_ylabel; PyObject *s_python_function_xticks; PyObject *s_python_function_yticks; + PyObject *s_python_function_tick_params; PyObject *s_python_function_grid; PyObject *s_python_function_clf; PyObject *s_python_function_errorbar; @@ -203,6 +204,7 @@ struct _interpreter { s_python_function_ylabel = safe_import(pymod, "ylabel"); 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_grid = safe_import(pymod, "grid"); s_python_function_xlim = safe_import(pymod, "xlim"); s_python_function_ion = safe_import(pymod, "ion"); @@ -1375,6 +1377,30 @@ inline void yticks(const std::vector &ticks, const std::map& keywords, const std::string axis = "both") +{ + // construct positional args + 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); +} + inline void subplot(long nrows, long ncols, long plot_number) { // construct positional args From d68740570c7c04bbdbdf9960aa5fb36a76ef0c94 Mon Sep 17 00:00:00 2001 From: Marcus Davi <37623906+Marcus-Davi@users.noreply.github.com> Date: Wed, 4 Dec 2019 00:37:19 -0300 Subject: [PATCH 051/110] Added #include "unordered_map" to main header Simply following README.md steps produced an error where "unordered_map" types were not found; Fixed by simply including it on the main header file. --- matplotlibcpp.h | 1 + 1 file changed, 1 insertion(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index c4d5d14..477bf75 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -9,6 +9,7 @@ #include #include // requires c++11 support #include +#include #include From 61da78fbbe4644a8a50fdbfe31a578b2a116ff3e Mon Sep 17 00:00:00 2001 From: Ilya Makarov Date: Fri, 13 Dec 2019 02:28:20 +0700 Subject: [PATCH 052/110] Updated cmake readme section. --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 338bea7..3839169 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 053/110] Updated wording in README. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3839169..a82b675 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 054/110] 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 a82b675..72bab39 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 055/110] 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 4faeb0e..418554c 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 0000000..f53e01d --- /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 056/110] 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 418554c..7b791a5 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 057/110] 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 595fc89..1c3adc3 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 058/110] Adds axvline to function scope --- matplotlibcpp.h | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 1c3adc3..fa9e58a 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 059/110] Adds boxplot, get_strarray and get_listlist --- matplotlibcpp.h | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index fa9e58a..1836c4b 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 060/110] Adds vim tempfiles to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 7622be7..1c4a1b0 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 061/110] 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 7b791a5..a4a8a28 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 0000000..f3c201c --- /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 z)iL?%uB+rvNL1TeGBMlnD|asKGj8?vzIMM@N93R9*I(x6*HsnxJpQVorlsJ z5z|kb`^@B*NGLa|MePE}@L_$!3uvx=UYnAy+MaVMPG&clYO9V&-7M9MN&QjlsIvCl zQU)%i@e9zJ&}c!3T`Ipblkq->h^9oFQMX&jK5tGi2F8c@FS- zG$VlPa6?B6nRIPZphWr<5%>ilA0+I9b@_G0LD~eR^Ff^UFmgK~>W2&>*hrAT3+PwN zZ7MCcBoSar0vH%H_Hx+9iplTTb;tU`Jebc`#_nI9Pf?}YQ*251^6lF(8KszQD;mP~ z4)6zb8eq^=)z-dSi5tv=#1OW>f6szd32_H>TNn}AUjz?yZ+i=xqf$Q4->giSJb3k7 z&To^kkdc)&2k?3yfPbK@09*(Hg&smy)0jo<#euIiW~c2#Z5(`~59GU~+-H^-oh42< zX?NC_uEvMIHt1TS28(7=a&yN`PO1Z--Zwm)3qeHi$t6C(Z=ptcg6r?D_K=$Bdyq7j z*lNlX{BoQpj*f>#e;I;bIbJ&QWqmfQEWS&2=g@h@MZglYygR=tK$pPHFb)7I_>TT^3^E3Gm1hlSm|>})>}B*T42601|Cg?ub=9!t&X5xd2! z9;2_0*Xva-4|*`XRT()?8|(ibnPEOO%TE9eJLrr6R#w&59|1{)>9IxVBRFMz3ExhF zYg6400Buz@X%`-1auo+XJ-o!oSNShR^FKwnG~Tr8AY`r*tDX zGsx=e0FkQu_k#R7u(+Mgje5P)Dcwcc&VCTk!NPvE97E@U>A_$0BFnrBu#CslRA&x!nxE zHDfZL$*k?Es;R9--;bb0{RXt~om=n*PAo3UyStxd?We#gb~=+kzfGP}?mVZS+~2A@ z)s|OrU==DFM~8O45*HVT!bS3PnLY;Efb9-ERt&M1^(CeRKOBOp#dyUEH-%C{&gSI& zQtwtBMB#c@Ci20@g_kZSJzaRTPKWS2oM3=v^*7LK-3cCB5YO`=h_aM6tu2?$)j1OS zpv)*5ELu1<0J(nspvWtqv++EN$uM%fmfT#)?&u9^GQqT`L*LO*eV zF+j$nZhkK;m4v7v0H=;PwY9Z@PSq9(d6xR-IAv)ak}Q$IhftDkr|PSyv?K34_l#}~ zp^r>-7r|3p+R>)a&|ANM%yF>~<8Jfu@!y3s&LNjqP-6|wiF~zT9b9GLwOL!aau3gc zuQnDTa5{!1bg{aD@>QkKSgGZGJob+1-B%>R4%9wb{SE-=&*JrovHv) zJFfG!wwl)Xs>K0qevzKw$*04l$9XJ5LT};^w6MCEjnSVjpZ&gx_&pU+!Vp$BW{Q$| zUvQXqwoZVHOYKwVFE>HQu&?wL*Ev)~x#p#_;oXJ`V5hV-9lUu+V+MGyAZjaB_)YSR zfw$17cEoML14PDY7X#8gh=6Ph%2C1NTOxnPB2G7XTkDr=0;9a@-F}Wqok2EbFUTiR zXsH&)$H5eKUJ*x57maKQY(U2Sd3yTY(!TjxPZ-D*0-7ROxv^>Q!vnP3a|9z7kV!X* zyxVOUKOeMUk@0h{LVlx->F3q8gzFTe=9cwzENxT#`bXm%t&D6_7c_Yu+@uO{V<;$g zHbihSx`k3PeV;A`^yJb zth$DV}zI!KtHr;K-8__WipR0P?_6YyjAc)N13EQiOJ{ zL}rac8lSuQu+d;Wpl&LN~HBy+pfQ-HynTUP_r2rh}S5l zhpdR2d;>xsP91p#1&K+AqwH)Ua?cVhpXcnKHaD9$IH!~clCsiLE}aC@ntqPU6}I%# zYB>HjD8;#UFcKz_V@<;28!k1;I$Nhxp#G4Drw3$2Ip2!ibRqRcAk8kGGSt;&-fABe zr#d{9{&RWDXm4-t;rU`<+ zC359%*SO1rOk&U}MkXTzvv3HI;9G;sqXZS9eJdJh#k$g#=jvcbs_c5kho*Eb=mwy| zppt<+8W4wksH=0E?w1yPORtImQ6{p{CMBh&qYHpqU7MRjCoGukMBQ;Mo%waouHYA| z)&6Ofc_~}+-Na>!UNq7;Uo5ht`qyircZQ_HeyrVJM1u!Ue}*Ob@Rc?$8jkQx_mxK` ziBIF#uWb)*Sef#t2ic8BHuj@wS<@2~IfS2zQBo2;LKZwYKv6TVQQdXP#=Z$mlmsCLz+r$W`q}C((T( zHEAaYLkIqw5g;Wczkn>U4=Jt=DLl^?mhy6Q1K>a$pPlWq_c|u~dilGy_vd0^z`s|j z-1A#+PY>`}<2H*O9UXxg?D}|5AQ}ufUDJo)$ddw0zAq;Cl&gN6DFhD#4QTxjm6b7z zi@I!02cqEGBY7il`&1yKr7m8{Kv!-iF)d$5%KiHyW15L;I&b)KlVd7jb#w?R+$%NC z1Rf&R#1SmX0(G#6x)|h9ktAE#*I$2C20=6(Zx@)ieJlnHp1xuu6G6`M+CZEQ;@wP^TZ*Y@few|>;vh-Q9%6` z(46iVTnTvm{*hT;ju+D1anf=k1oy1-w)bggVom4%$#Dl3yTk7v+7LkrrWxSNQZ_d? zTZ8_HAbVpY-2R}^wL|a}_Je2zc4#om@o&CntWSQ@RbM5U4Yo&uv5!^CYs>4@eE8Z1 z29W1BTtuKui^`GMlXZ)feQ=qAy||OlDu&t*D#-M)6gNqG(R>xShg6#O%&S@NP+gdq zA+8OM0RSIhB|-4(8}-tisMg=82Q9X_$6XoZBolFba<8aF-HV#RmJ7#Stn7=$WciLc zuat$}*1z>3S8q%Recp9E+Vg&OoMpKrJrfw`mSZM%SCc}-D;09B4C!~>FE7sAdnE|p zfkEy2Of#uko09@cJy_FW4KITu{e_!bA~5lX>oH2IdO|VxKrxZ=vM4`4zaC<_L8eLd z_;CtobPnNWm4S%lV=dUtj=|p_gxu$@3jxh*GAN1S#IQD? zas$y|R9f0F5X7b|r13Q9Hq|y+A^(qCk1*=@lm{VP=+R>P1#c9}}QIm^d}e_0I9Ww|gX2B^mm` zm=MvTt0T6~%Tr_PdTYdnJ_8^?KYsks?wM+zkoOHM^y_|I=J-pM)(JmQF+b$Yy^Hio zeci9==k@Rz>#}L8M_`9bYdm{~)I@z0>|8GqBd4H1 z>Nk}cksN#0eLp8j3L;GO9XT#-gXI1+`_lZEvjZ6;;Rn4l?>Xk)kW;uglb;rpzaNkX zaai+@6nP_O2_Ec#?WdLQBvPT5di9x!$rqp1)QpL+S-8>3Z?>OP-dCV^K8c#!-f&pp zQ>spO%N-3pT?kH9zx?%O_{Ya!&t&;5D@4LzhEjogbW{}Q&O07l4Dcz?mLnDVFiRnO zY)JN=`HL3;ggb?tsEoCE$4(GnFRh@k0i+n{2^8_Y0Zk~@7=%#z0U!XuTkZyppbRRO z!AJ1UKJj-!ggm`R}^gnik=;1t>ET##%N1 zP~t7OFU36i?&kQpd!m^B-4NUBA>r3U8tFE?h{*UXV=+tml)*|1rqR4y!I0q8v*YLI z^yGq50x^mg>u*WtYM(FuuBJpJP{-5903wzoz-#YAvnA>N>18NMWBvj9*LnNrEERQD zVpKDBOgFW$NO6Kd0@`Kz&OvP$fY|v_u%ooul-t2dPLk&%4~M0)I%8#x3dcF5iS(CA z42C3*&9-`rz>W_78c0@Vn&R~U^Mq`!i~NiLyd1Q~P<%rR2y+k>m;pSj2L=#jM9;tie-5xud#!0urR z0|BX0x;7uNZr~SOYctf|Or(BKiBD(XRXEP095Oym$vOZn4jwMRbe_}iwWWxeSaPxW zh_i^wRvsnYdECg)ySMeT&Uc!?`~a=_WlG3+{c96EOY-U`p1-bn_+b*aOEZN`RAJkv>nrR*12V8x>wbz0+xAQYL;mA^d_G|^re57HE zFew6=!yoXno| zrXKO~5@2Bbl;?9>Yf9q1{vh>U#b+Ml7XvGCo;S8uNtWZjGV#Qky{k%#GEuZ`lJT#L zx>ik(UPzthn$ULmlw$PmJ*_YHe_Qwz+G{QlkFhe#wVrCklZEyAPB29#3y1`h-L%fV zu6*G+Sg**qX4Uv*1bR;-u4ltD`miLOtUc^m)0wBRwtwy-x6dF1zi*wiUNsZG{q?$8 z5C)nczf8@|xj;UqS|FaQdbo1io9{alO<~Z#(#H7224EoB+-TSa=v>(LRPmlfq>wQfoqjobfc!$M1uh8U^p+)o*IH1B3f$Rcqf=zu0UPc zAW`v+k-&^yT7lB>DQZ=i7Qe^W4zewCaif#EdnyS*6Ed-w7L4)<<2~5*R*QM}$$55~ z!e<=1`>b$Ztl$psl+}4wrP$n&+O3M1=S;S73Fmh>H*1v&=2DwYTv^Sww>GkVcHft> z@2e>_Mn~{Zlu%dfNKM2$(W!TjP(1&`*JY3o5voo$hUyRhVAjCI|;3h+i!2?DY4B6)=#m4lDe=ptu;YDe4uH zr2tP*avOq35hzn2FF+jcD5oY)jR0T>-ahzopyI@H4i=oo0aNOK?8~E^;UQ7hXXXi6 z8Wr`_DZ@l_x>uW{MQjw0TiwxoDt@S=`nCmjD>U{iHujI$8<3$sDe&oVd2nXw<)ljA zuN)Rtau8Xnrkb9h`0Oe1RCoRQhADg9zJ>?(vg!f_@OgQ% zB@G!z;vz&lSaUj2{bMvzv&zwLMKdFd>!zB7i7&)pku0J;w0&2e8kv&?je^l*gFw`o}TW z0fBvC!8oj#E6AJn$ydHY4UJwGRo@#CChKJs8JI#2@=*7STk`yzkKZJw>0i&+yR#Jf z{bk}0zUIsb56k%Y%~hA=;D;7$iuv_?egli@u}>H7jd)0g(-UZsArsZ)METSGClD2p zj@5%3(bNIZaRPk?laPum>DzmzoFaUm?*_h1XO5HoM5I}u4$OB&MMdAp2tAk-sB#rp z-XdS8)tljnhA(!|Cgld2!f~_MTN$)aHj6%GT#ob*@{w0yja#5C!_EY9+>Fs)7;lng z<$qHVMaq|tzk?z%sfy!s2}7^&@K&ise8Z095ZiSI+t9hDP_4yKoiCvyfA-o*a5+4P z5}Q$*iErO>M__TV7XOM%h=@wa%L7+*H0y(IZJl7QYC`ke$b@)b$=t*>c=M>zIqfR~pv`UGfYj43KYnuV{xo(y<9KY=qV%{~>+6O4 zlQP5`Ea6wdvQNc~8fcG_gU2nIWf`8=x`^}ZI1=yC5^$2Db5-g>SpAEPM5^eIkB(*2 zuN5gXA~pLi0{wM5xM<`!?`dcKu)AUZfvhcn4rC(2HfKSTL7)6&`N7pNuy#nH3P_;< zWGB=qj(>!T`#QDTnh~y9SiSzw=!i+zFG6K zhK6LAxzWcaO@rqHmKgj#$V-Jh6UjF!Av{MPRfd~1|J-<=Xhjh%*^fRGM`Ui&Mf7^jh_d* zDLe$i1FqWW5hRCE=UZzg0zHg3JFD0ld5kf6q^P zStGN5%@2Ma(p6S;33oj4=%A=2302MfKuHte8%oIYt}GhT=OO8gspiN0VA-5ZM!)D) zUBIy#jwCBv&#o}J7ad9fJ_;Rp8PE>U{gc>RDgSz~d7)egqbM)CzFT@ztisecX4yAg z&_8Vnuw%dCRAd^@vC!6VMo6p3l5I|2Rpk8lfxr_bzAi2ymT(od0(D>tYwVYBa1&(c zBkM@prq+jRaiNRVDMo_$U<8*{X$lBeDC0<7cw%0ygQD>U+mOQ>OqDP#%d1m@L{FR& zYLO83O;T6i*bC{K3wk1OuaU^hPuPY=bs3 z+yivfXg75hbNF{acB_sp5cV+Z@G_%Buxy2?25J%C&54|~W6R5hybPZ!+cWQ^%Ss|>dT{u*>y;FA43 zKHdTIh2pDs)Zl*odd&~Ya@a0a{~A#%u4CP7GZfnc9R_%_OuwW%SR?$hGwbW?JvW=r z-BIys4+Z8+0*}jG+yqw%+DPlLu7bb9Y|@B*$8JDS7#KLm-|=g5bjhCIXZ#UIFh(% zU>59xI8i7Mzzz6eL@1_GEt;+1uQUq}P#Ts(k7Xa>pumS(!J9#^NDnxd{a7a$Scqq>6 zW_{@GbQSV7O|X}Zfr$wWFgplCvJU%hy^l}VD6aZQS`N^m4H$qb@(gN0X%HiIkU4L54cqhxocn@(z+`tuc>SblfwyVo{2a*_ooysI1GRL`0e(S}m&k8p_dXkh zn?#R`ad0;3eZTZo&s$`2)EWf-twC0v(d*aA$Zt^*Ir)b-`b#v?`20VNk=HKg<423> z{;1t?EIY)}AEFSgb0{rMja^O@Flaexs9RDPDRXay*T?axqB!R*H zW?Qb(%d4A6^8oghcb-FX?9E+U;55}1X#|3d-|fIq+TIC1pZ~eaP0$}el4v39 zEz&R<5HO_B?K4dRuJwu$mDFM*wBU;%Q}!!?bJ634iS691qlv^Ts>l zD2yoaT%WaONx>BP z@$qqBDvbd4hEr8Kugi%y>B%aRvY4Zdz3GyJZ4b0K1TPirDdyDGB}PR>MMl8U7W8eO z1Yj;muaK0L4X#oGPLy!2w4@{tfT7hby2})Ls(omzpK)#?N2y zlwv2=AAYvH|MweC@(aSZk%|0Oq+#zaE<6j)1!p5)m7M65;o{ly#E0a5KVH|NiF)Y_ z@D!T@{i<_uHO2>BXP*rxzQl8V-S5X_+e_@=P}+)?W6R9Ud<*pSwh$iq*yzB3X#RxC z^XG$cn)-JSN&hh|_{2_n;=Ws$`8hdd&gO1BRGE2i`@;H7kueZ)vfTEK9Z6`^yfB0d zX1V}&k*Qn;dQ*R%wLrW!LlyFY345woS@f&@&gW;RYo+4?rW-Wx6`jA)ENE-upNqfU z_?dg_MIV^5Kt&LZyA1<)>}s>8thOZbec1;NiOmn`@lZX>-8-SL1~5|2`6Y?f!?#^| zKG{Lal9eN}N!N5?!k%nyGD$O6lKgR&Ct&t;cz^#IgZcCbNL=F9rP0&Vy7j18aM5-f zw}ZAB68n?(^afF4rJ=d$ad!8@l{xju~B zq7|OkFD`gL$c>$j8J%2#K$>wK*N2IT33L#Co$qgtRyguQ4&{ulvqE1lj7v;F{@JH% z7snY_Lx5{h>$m3Q_a%CvAT^^=ROrgS4#5RrLr?%;2B0q? zk~R1$es=`r_=JS?K9v&uy+Oc)RINaJ3|k4fWTcR=H#Ob!{1tOvyS255)}FazF6x7P zUYlGh5$?^Is_30>@fBG>Qz-L)`_fQ3Sy=8%$V*ih$NKH*m z{64cTk8&eFvs&i?3@gzepJ4vVWXSnt$AJ&R;qSr185dT{AxY*_$!TuZYdD>nob0-2 z(iXrLX*MFuaJ%6C%4fe~64BD5Sz@<*^b%dAxmIIGMnEeNsC%eO6hS>M&5~RXyNu2l zEwfO{M~n}t`+w^d=n;TygF435l}DqH+Z2xWNfVhB`if~~oO5WF>0kEub^w~w3q!lW z-o=hoEzDO1F=TkETD2g7<7xwVWW6vcm8Au)_810U2K@Q++UxI*fz&QaBF0$r{6B1+ zKxS(LVw<2F!|-hbD{JoKeTyg-bn?`9cF^lrL{Yi@g)Q+Moqbj@_tPilb=DO_lsih| zA$KEsJE1>QE>KSu^3bT+8XdPh-G;^6wN`rc=R?5gl@eLPqs>EhO>Wg_BE8Bkr)0n0 z;8T5_zF;#u6R{kPM?tf^ES%`%g8~p0q>4$gZu{`2*NC_TVuRyReC5q`2#VZ*FYL2M zNOKuFpM%Hd_4VrVWU#`OBk0qK?lwFud~NV|K#dCK9T|`q%NKMS!p0hl8TM|up#cf! z*)9-|eJOYAh%fY#!bJn|jD@u|&4UNF@!P+2S05m;2{_vU85~Mxe8|mBcb=S7V0HiM z8_s=!`A+Zq`)ks@w531OR)5P`*V{=k4REj@9THy~ylbTUv7AbkCApdqMPA&BEtWB| zvY*y1ZE6zTrd(@!h}`h-Z!)1b5*5JL#wf{WVPRQ=LviHzl&2NjiI4SY$A3Ld zZ+w+9{I1;)#6cB6-h%!sp-ME+h0UH1&EpUH40*8sV@x*HDpDdOF}jfATz^ua2&-v> zY}Dl_-hcL}o%$L(dE!VNMF+h)rWTYe_q(t#2GxP2~%p!pP-h5c{2K?yl^H_J1rIk_biMpJ}Q0G75EKiQ1(H_NlL|l z+)iYSlNAP`#L?}mgWFBVY5ThWhgMnnqE9dOpw|cGU_m4y#5jyV%msiGlT%aOp6vt^ z*T*p0mh)oZOasc8z)@C$jguGy4ZQU*wd_530BT@z?+1*qnJVn3xZoBvF*Nk~`O}4s zb;Nk>c8b&Yytlc|EoQ6V2!mt z2TNYaDk(+CUK9}(-i@r~r%{L3`?FG-K)}*|#PgV-z*N09YbsR6@v*U|&dyxO*~L&P zZ#rk3r@AaEr3?0hm#6*3*N(0zcy`o1yRH*(Juq?zc|Ft9Jwjgy6+ty@2;=wy>v!BT zZ3ET)=XPD;z=HW!#`~Ry>)%Avfi?y21@PcV_E%AHB+Hv3qRQDJ8?tq|;h969%^ENK zRr8yPNpiTz&(phAf7~xNjs*_~+`mriwuLng&Z(voIX!H%qQ_GcAg;^;2p-B#Y79|z zc(=hhEd;J-08T-fgZ30j&`wTH);&rJ?0a_2qH=Cp98K9XBRn+p0H%GyJR9UNYsY$B z&pqc1vHS^eQBeOD(EBImE5lKf2_C%6}l3UPai4 zrQkZpH1~3GbhI1ts#LBqVop|nMx*v325Ep-YX%HZ1$$v9a#$&dZjqyg;k`rF4l;}| zMgWRL5G3d^ODYj!1+ga#B5kE0%!S1WWfWO$h(w(VAJ}Hw4Jqm9Bx;iTEYJXThe~Dw z76i=Ciaw6tqSoXs0H1j2OiXezGxrLHFNd|k^_i-;*x0g?yTbSw2d_(mGFl&Y9e?4u zMbGf+%HYP;mW(Fk&Uo#iaQ=Kqo*0;$R1UnAl&sjAddCd^u zhyR}zpjG59N+{w`s5OxTnkdsuHiyGwqC!NKPBFro8Qtb#QcmFJ`PUO+KrzqSZV0!* zc?7ErU@6+0$Us>bd@|@%4pQPu)i%&@L%<{xsryO*38dhxHekb(yoYn+y5C#yWdaWg zLdnS8)jO7d=4^N-=I4V)w@d&&had*=raZUQY$9fu%k0;V{1IRn!rQ+M+J%}1{FCa=!RJv?WHynfh;;6!9BH|JF;;@*`SYNvpjQX>|4qTiRh^7bhO1>avY3EnZPi@GfL0D6xv+f0ttGhk*)VC!jDP#FP-t zj>3|lf@m5LJwp$mtfSNLO}gIVX6^k^C@IIr+LxD?S(REXX8ilm4IS3Y%gU6A6xbAec{}Ghpa--hFMuHGDV0*ph8P1 zc4DWO!+7mSWL)J|u}k>ec826UaQcG2}%^Wj>IovTE%yI zf-$t$4;03biSIeyh3d>ekwB0+M1vt?@cYbOHt%6D)J_eb* z;R$z@H+22OC%nFO8X2t4ZNu!O7Gs$u|5&a($#EloJSEx?qSU&?1Ti@qTGD=BjwW26?_J{;_p9PY(jbA1& z&%b53j6YtI&}vzVgkT@3PU(>oUrQC%&J;DCkwNcBV4;4qy|@1|3AAy~G6g zd7=3!oRYMai0LD3gN}PmJBykp6m<8-50m&DmfMQf@GN73x{ax5XiOAl*&g_x&SPR? zy3I6xtsZ(FqxEm~u+GT9*tm(AnHxhGRP_pZopg2Akx8N)cM_A6vtaK9PjB5`Mzrg!w-czE*YpYkdkL;EsNG+x?UAfnter-B=qP*2S9~PVu9&zru`R;+8A~rui z-QQ|bhifMGUc>p6dYzj`tU3b^0qQoX`-}bcZ_VGFT*=@16PBB;duh+?zuZClXQ!w? zBQZZjUvA&>I33;Lm8z2DV|_0%sLMi{=x!gVfIX%)M*9%;&i;uFb#GgNI`gE}OLYZ$ zCWPtw$yx<}b%@3zAfF(&2Gl$F2`=`GqBq;>(|V%r$i4S-GcsUC z<%5RY+IpvmK0=%-Sgl&My(F33b!z>fdvM2+tom4V3r*y8CcC()J&z8Nu5wY1&Q{i= zVI|w(2+V4V44l|LG6}L;cej=|zUE%1fQ{NMdEbzJQ`S7B8#KYe{ zw@7nHmIwF#P;JYv2S`bK;}^l-vnEKu0kS~nevg6d%;@@!8}5I$^HGpP*y~o_k)5_) zowrI*ygDA{b>442k-ds|wR@sN4_j-IGzewjLKBD03Nlw=szTTt7CZ9bfxZjyZb0`& z{9QwaiG1Lkfwu;3?qXhPAk8=8U~u5E0d)&fk%&+IG#c64%GO|iArMl#IGV)mZO9`g z^tQ@HH89*vuoEFC!*sYsgo_xmh&4N^Vm`uVU>x`ynU+V#ASR`6M(Uo_kW^KwAt4!z z8K?TTm!fTrz~LO#{_?TUAemv`4`ajm?zY&Nb|#V-oc*HRx(5FD?D{Gczl60y;{>tz z@L6CSg&$}vfmqHV3L_t?HV+~RBE3dpnd7*32=jKakaNh-ww1gGGOjMnkx^^Hf47yg zH45uN73;SA_J;AiFq{Q2L1H+FfMK@TgBKxkc^EMVVe_Ew1o;p;1)1(iA}!CqSA>=t zom`NuI4jE+ym4}PHPRq$6#rocGqPye*&*c9m9~jMhtQ`zOYY}SIhXI+j~4Ch?GuXo zX-Iw1bB1@?@QC-J{0uMt7t@rfmB)O$ z#$7);$b3}teO2;N|5i~rG@Wtd9lP3t+{||8h{+*+t8MzI&YhILqf@!$@mRr4T z2v#xpzhi*h*q9L+ZmT!FAo)WLG?`PH2E&jOQc2$so8X6};HYuYkTEn+f&eZ?)tei@ zg)OcKsvlT5AP`akHN8Y#eCKt*W587cq)UXU2SHH20qf3ntRUEAVQ#xLp5I26c}M?# z1opAFxdNXc7w?U2Cgzek-JvCW_S_Z@7aAlh0taS;@7cCysYu)Bsu+fd7J0iD*VIN3 zt`QU|3-5<_JAXKsIKD2cH?uMH^g;=fefmiccY@rv7o&sO;>Aym14I zN&BmzYXl7MR)ufU;uTD|-6S0gmiz%wh1VN6Y%)e`KTzn?mR?(BM-V`2K_?oiS%Xja zF@&p`LFyN$2*Z&48pexh19kPTzP>)+h(^$FA$e;6I^aEV1gQ2HjO)&z=hLp)u@6Ju zr5^zAKXAeyiSCe=#alc3F`*PTkSnBnr`5FA0{DI$F&>p zWc`JB8y*#f`ypc@+v`R`O^2F(KP%@w?@`Qak9g<=OV3DpTTOh7h0?-e1h39P?l77= zJ&TbuWjLA-1ikjtm6rVjc zrF3Hol;!xA1bs6tzqyeq{_KBvOPh``aRsugCeODaEG7Q38RoMA|n zwiz`wC=s#;~OfEb~3^OXlM^XCZ?^7u62mOG7LgSmNO7Db!p!pC=oPup*^ z{o#dXrU{r97(Oy`{$+MH(S^Lz2*$pH^bV5x;HAbyzS!bjI(4_XNW$9NpTheCjy_|U zE@6~pKavKIt35^nj5?y>3RtfQb&CSG4`7&%H+8@rAV;f%jogpu+3@ih$UICt&K9LiCVqb;VTho;=7uwBDt6{FMzduSf4NE&y zTr7*)Cnyn}JSZ`!Cs@*kHK-O>_W`#LH3V3L3b^d51=8G$QYIm{!k8z;9TuiBn?yPUA=_W0z0eV!^&;XA>#*2W5uYx73s?&o4Ip_zn=%%>=luJrX1g06O zHuSH`irV2WK_g3)Musdp-T{H6kfz>CA_x@bx-D|S!C`Xkxa3u{oE3r}vUcq~78Yo+ zl0iz84tvqp!|+$2w}Z5bEg0W`Orrr^y1{STi|3TcE&&ak%dpCTA%oTxwmP!Io3eZK zxc?^-i3OY=2LF&WpGTMjpFL3u_GXAh8Ex=W@;~)YdVC4*u#CceI1C4Mc1{v%9QQ8Y zkg*x~s8>fT{U}_|_F$Ht`p75T(vVL8iua1jN;(x##uQDy000w4?&yDi2=GjiNd+-T z4ryoq&@wQ*hqoHKN>QD$p)Xfsup<*CJ;wygb%=<*VAu!b?kapfH|hi&2ZBrTGc@A2 zbCx`s^gopS(t90$^J}CsI?_*Id*9+btOv+R}L5dkRUZld_eIFZ-%}{-5ZfNx?}1(T!mW+4_*XXLOgTmj?X@q-fu{6&ex2 z&iG{Qo_lJ@)W%xL(zbhnIVt)OWz~#k`rH_l=?#odp{JZP-Dq{Niv=(oHYbw zKd5u%NwG$@Y5|Z@k;5GBY=fNkeEQb@9>Pe4+KS6vUZNwGG8|9nW1R@6 zAT<{BT+oQBGsl^xvhqgpyamKM8y@VLXN7!mVE6T&|8TT}0|#|HX;6qxS;h762UIGp zCjBmfd3l-2kkz`qv$N(Milca&QRZ2DEgQR;b(B@VbdQb_ic|=cX?8Ag@I$VYln*WI27F00!cQ5gY z+H2@Q*-p;qVAvm}U0$J%$unyxz`($e7rquSO@c;cg95M^WT14fI1C;A40#70ZifWN zoEX9Nk0^vEeiPfVS>eXw%(xiKzi)aTo?M4MZE%L@pj>W7u0yS;_+iTMah8TLrx)^+ zE#lUo329y;wsOElM=JiI2dYGqIk3?`yz{qs3enS`SjK@>ll3;qIT7%zxnM~)l#d#P zFUn(NDjv$DbH|ALQ(}M~7wI2^KL#n^O*uSP9;BoV`S{%g!67C7sD)9ti{QKJ7%FfS zpnZS>5|pM7_*dD;v+#UY9=ZEh+EwATfnEvJlEAIPeXalxAX6~2{v_z8ZwoJpE#-t{ zg%a+0d0->;tb+ozHOy{V?<_*bfnIw3x`9@axI#kPvZzbD4fw0dJbpAR{K`3b*tcC9 zVQwEy5HwMsPF7qLKb0*-3H8q%xrZ5GEW<*|srq;(m>#@$0!nD8ZxbVc!HY`g>Yt~D z<3_v-QrTZOr~gOW5xkDVCPE?H+DHfe?|-6tjo&}n4f3f3*o1#MD=l1&lQWOG#mj;KxV=IOW zEetzUJhZpRTce|&ishh+-}z&#&Kye#=Cl9NC4kIn(x>^iWUrq-T8;?fWZ zArlF+7eIhO^HMmN;T1%@L#Ys?&~ zx=LX+hl@`ldK&#DJShep{Td>P!scwutgJA7AkmRKhe)BW_X!~Y%F*exum8RIjD*22 z@Vq%

Ioyz#;UrfUV+<5`@RqtMtK&gW+rdM@8ow2(r=2y`;R6pH^5{2*&#ax-2{y zH04knuv9?lB@DW8 zdkcq0qU7U8#Me)@Tq(n<#)k8yz3H)Z9CD|T;zmjRAT&1(i`ZO^qsXm8N zCrqy`6IZ15P>Z2Xmzs+!21YcoHGHGqz5`PzwR;WizVl7iQ^QQVb0qT^s5R2~^w9SV zHa@uhV(`*-gk1Tde@NU!TQz$_LoK-~D)F-8E*;5ELkN{lBh1emUtAO%{4Kh;S>JKn zEPSgR7u6wi*C(k8M*a3~yMR(bR9`=x7Dr6tro>o*M&Z^}4SEX0tCueUUk9N?8n+Bh z6#cAIOkm%fC~UcY)R-M`H~;+kv#IbIq?_qm3W*=-Bi&{Y!CPVCMbLRC2wn$gAyf@8 z+LIO8zr%x7)K&Pf-+JYB38O6?H;3+mCIhGn{Uc8B^}!f|G^zXU{=g-`Ow3_$`-nko z>z#!Em3~ETzQyW>k~RU>A6R*1bMb2zhC!?Zn)dkd)Wep~!6aeJSaFb^L0=-iuwG6W zB7yKL*6Y&ggpx=*szF&rBr5&pjZQOOCT2Pk^)1$hd#gWVK?4b37mViF5j>W`Gxx(l0~w8eLT&4{@OeTm`}h zZ*P|YLF@$C7AX+VUNRKx*Qmb!XqG*ADqH*^8 zMc0q(9{`%HiVeu7UiE-d9XX4%U+lqTO~((65Jp625dJ6iPuee^fg6Y%a&>_xlDk(1 z_5j(;AYYEuM`nx~#v~3oUHECwEqr?x|0wv(=zAzw`D+cNaLmtu=%pUNw{5sO3yf)8 zNM|y#)J4XmF8!IV&2j#!+4|=h_$O-;YY&m`F2sABdzd@>;Dugp_q!Nuy}$ye`Kj4X1MVOb zPYvcctZDq~QW&uAQNZE~4_5U?(dbr81N&AUr=Rsr8t%QY8$q}}PNjI{e+wa-;lHE_ z!JhL>chu{ey#Rp9)J>;SxdLAmh)D%!7ra z(gC^wX%U!DkHq(zPFF)@Q0~wn*&gFne!pk9Xdsp6Bn?yLdTU{g7#qG8jt(vY$Qz{< zMSGf9Efge5$e6E~28q1E&@%c@ihzt7z&wt|O~;uMJ!I~TT%~06D)Xp}M6vqvZ*-7^ z$C@@1brbfUGR4Wd?#|Oa4`x_o%uGc6edY-rEt4#?2K~dsaaob=UlNQJ;@S-px!3q0 z*{WUid}PohD{* z=>Ttz$T{&r8jZmmHdroW?TZ9SjLYUOMLZ1d(`)PLzp*laNRQV``6vG$Q&$00<=U+` zr8G!NNSBCoN{Vy|N-5nbEg^_>Z&CyVK|&CamhJ{A5s{MIASw+?NO;%w+&gn;nDd{1 z4g>7(dtURWLwDG=84 zKv|>Bok~{{wkg2TY&o)iAYe5d9*(2Hk|dJq-OHa&Vbx!mm~gkr%K5qGzyjxRpP!_u!>R zdicY)?o^70e&xk{D7Lt2$&rQG-ew>MHc2g^Ms> z>_q;0kXBI&P0co_mhKceFyk@JnPkp7WT`W{)u|x8f52SUsf`iwS@t=r*5*4Qk!BdS zQtpPSuNwX!m%bU3#`g-K2rV4hqiB>#N6iT@aB*DILu55N>HJV*{5a3TaN-4-=cI$GMRZ-i)(g3V#hz1+2*cqb}< zT3+dpFL`Kei>S_j26QZ<#o;GfvjF%ooTK13yH^ji@3#V@rgYY&fK=<3h08~$1XLA* zl9EH8ar;p2=wN09NEJsm4UdkFQ0O!jNF=}p-nV(Xi2!|XeEe^iP{4-KA{efE%+4px zC|Bth@s{wI65b_^(J?ny<-K`>12E;-&yg4>9Pq7m!A^8=z=IwF)H`##SY_I*=7;Ci zF!ZtS_pnkf9#dujS)_n_W1ppftwDS&iFF=7=zIfyB|kYk4kJ4H>p=XhtqhMc=64Q> zux9smg5?wTD2IMJGuN}HPkmRMdhhL;&prKXN6T`D4R9ALG=#a zL5RS{4`+!cb)ksf_clNfT(iAN6-y&D0UAKqRQGrpXr@xMZA7mR*NGIS<1gaUInCnP95se9#n`ro;Q5?O3BlNGUD>7 z#7L^fM`8IY@psv{-%OZ?8|BkI!wvp!6I(1$DutBOpBSBW&k-Fs|mj&ELRaNKx#r~WGIl03^~XSR#+m<#fOsj2A%?VWul zv0GD?WOC&iOyMj+$_Whf!dU!|BlW=M00*(S$EwQh(I2mHvl07T;xG*o1Tj#_(%x6B zTe8I7JUD?-j4-)nD(-=h1Wc$N-8jaEvC{DP;V^@HAd_Q@+VZT$^gV%qBy_VYAY4}9 zzZy2T6#+V2=(6Dl0{u1G^o?uT##_)5pPaoHTkpCFye%^TM5c$uObnK z1|#J7K`+#BZj~BDGcX6d5>eCTX5Yxh(z(`c%6IsPk!N7PL7Pb~LpN^wp+J+-ETi4!uOTFV4?od=C}7!K$m?4;#E=yK<7_;;r&hV65(aLEJ_7StB(T6H$BO0Op~mLjpO14%p+Ij3TNs zj8kDto){~PP=^M?=INW^aD3$n;rsq@&4dr}HuK~s5vAcXDe7FMqZ<;R4d2wNZK=vv zKAePl55Q`e0C_1`u2PBpxeA*K8yU1uozPyxh`Hj5}6BAZs67%JM~MyIZcc@GOm){LP@XVFRwl);4Gx~ zwV5SyP4C*nqmIAZfH3^{`o+rb;gheG`54@dVa#SQ&Z2ed+wUjfc!5j7eYp!qn+-B>$kIFP3gB?ib3%5@pYT?%N6UEM|%&x@6+T?Eu1%C z<<6aSROF%vkb%B?a+KW7GYQ*y^<}yC0SSxl{?0uE-rj1y4`)YWp6DSOFwWC0zGO*?Wd03!NNMh!MV+T*`g$kQ$n0A@I zog#fq?kMGx%cOG2Ro4x`t_E5=uG9|&QYzkMuliBJkPh6vtRi^$E=@E2Mgv<=3#PAo zC})tAxFYP5aeLS(N`T$KI~6}qB#rHTfDD>hpIBVXshabZUR6WPIDUV^k)RR(iUd7+ z3`ASt2OQ?g&N6Bn=iL-|05%7%n6AAkP2Vrh-Co+5DMImbJQ7vyYS;TN;?*{HO8VO3 z)r9p1t{^$xcAnAgz8jBkepcK)^|ysFvQDhr^Yic9-b4_Zqhn^40C=*N(4ip4%obZ? z$PCZ7*f2~&kz#?*1$}0W_IJ3b`fDypUToc^8Wz8EoQA6LJ)TtjT33%Fw(T4wzhC%7 zU2Y%W;iZmoT+cbrq7>d~hX=UvH3W71{C<2oMY1w5VRgLYsUNWSOmjE<`q!ok z5!3;bWD#63K+&kritt>TE-|}HBGwpzAC1d6{Uzv#Xll8>$>wY$wp3v^$E>S4O#Pprj0{2KawoRM5p3_$fFLz^vJGvDNCgEXC(<-v zeFm@P7OHQ3aX!>`3h~~1Iu_-HZs*zZ?<FNy9kH(q|nap&UDij|f6hLI0_vF_R*+*fb`8<+# z-=|mFCuc}w%CggRaa-&vsiqz|b&cMnOjH-WU{X`Qg}dBv`#WYA=2-=M@cQr#Uk6JZ zv7NV_*U}!#m#GndgJJL>x{OlFpKg092{9=ie_B0o1SWZI-psA9uU~q^;mmTpRy6Fp ze@3<)C?DAzxiURD5e75!BCsn55vO~LZ$HJixJ%ol`ueX3J47Du>-w1m0mu58uo+Xz*r8KsRCj8^ zTvBC5eW;3?9Sy5?7p*#Y#_eUqVvOH!hqD~%G;71CuM}Vvr|Xb?W+Re&$iJB#I8LM_ z*$+Bhe|dlX%!*z|ZzmnYEVMOs97bo#M*Ksy8NK^cl#O03k@B)rdObgi4&6#YfSr_;|3F70(p1x8Gy z=5K3h`(^aQib= zIW;lS-MH)^1gorc5+8EG{FNQuR&8Xz8CyvG`ar+k)^sC(j;V)JkKNjg|6LIonc@u- zB?jg&=lna4Z%jC-k-Z-VdS>PwppgNEh{-!Epu_OA*`Fq)D^4(0Xf9x( zh(-DQu{m3*RWvr-75uxPf4%<=vV&L?+rQ4SCZ>9j5;~lzM~^yETPKgl4<^LDZ87-9 z!rg6kf;Q^vmOi=>{j{O@>f-Z>MS76!v%PSpsE9RI-k&7HT+5Qcj-Om#VB`;KkAF`z zjW=U&I-~d9#_qi&HGI4i)-iSu0m$HFroe}gqLg8FD8P~9!6ptMZU~oSLNpp@E8tE@ z%k(nY^$G`5>`SZhP~CZR?=}w=I>_#|!^@c}O_9TbzP|!X(tNop?I54uQ%mNh*lK?Wve3`F2Q|Z3U91h}dRaXDzd1ClU3H|pIuk4DopJ<|I{fI-C zT5#|BSC^wg*#mThN3qyd#NqaQtbwUt_`!jHQ7{xD3!+SW3bXV;FW@!Kw%-FcqqZ)`sl_Ml&w33Gb#xpE|q6{hvLDeq}gvDwRaV@RJN+)%k@}H%hoePdpT`oYkK%E9ovTMUH@7zk|pZ}gN#n@{p0M9>t{ffgU zrXFkfJ7Khfd@kv|VhSN4paBm$-bev)!30t(RV9XkKOb5h%L%I+Ao? zPl*0K?pW)feBav)yZx3^FQ2JdPqI%fw>Dx)&WG~C(g^F&M_)jV3PVwckeN+K7zURX zk80(Q%W+XyLXR688w>n9Z_J!DC7W&&jI4p*uliw4D8eRS`I!c8?}~ke{?cHAS+bs< z>8&1eVV6HMP9Kd@Zv1&U6jb`haqr@q(d$Rtk#3=<%p!piQerdabQ<@feWVy7VVasS zZMXtUFet=f7kke}-Af0rf6~SS=mSxvsBDB``?G0G4vY3g_dNH)@bGXLWU2 zY5}_fDg!(K>S~TQRQg_z*bbG(-=wb8WI$BHd(7JzfZrVvRQLXU0n!`HZ4a)z_YLW@ zvZHca^t_L&fFi}iZg^xyAmgWa*^*gStNjmm(BxwMbnELq->jJfAkP(yjOLnt8b4|z z%xP%3#wb~d)8Bv1_iUKO^rhFzUGh>hOvD|{HSv#n;p)vLm9;h^AH{3OL?D^E0Hhz7 z0RQN`1CkE%mmWMvSYuiWGe1b)S&h|L*`_s;~+L4O)Xt!{QOPxB50B$nR9y@ zDPe$nw^b(8?ydku=vy%!2>WWj^hma`Po8G$Eq|QNom-?I3@P@oA;9B- z1!sriOp;~yn#XPMESr4FC}{;S+Tdt%H~N87N>9Gke%C5H;AvlTmG;m{k}4LSHxokw=hpixYp;H7 zRU?U$RID}Myn^!Qs8)vuGu?I3t+0PtKKJM(K((ULGBa!17ahI{j#Ko+JZNr?Kc%&W zm6kW*SHMK=@s)mF{ax(Dek1%%jdrNB-)977eCb>x@J_j$8TReo<+K~qe2S< z@x#a01-B)>ebi;{2K!Np_qqK`TU;3N3G@8!KwEpSSc8_X$jM=VV$YWzm_9Lk)O#U(ZS|LTCOw*CAd&6$#1E!awG`t~bjk79hz`&+agnV) zPpccg3oV$q#AtVwEW)p0)^PdJ{XW1}Ea9!#}8)zys z+DV9r&;yJCQSAl=H4?&6WQu@f#YeXoc;VU|0LuV6W?3~Ikr7B&8|;SRouYgzMOpN* z+X<7%EI8u@TvR>IoCg;VzEK98`&bQUfAz0@d>rZ2l$%6gAxL|*+6dq0Mzns?&{|d| z5AA0U)rI^N0rUu8(^6 zBet6ag`IS{`1q6|Xe4No07o7-?Zpdo(M%|&QYgB-bm)qKXPD5xZ0b9YZDbe}^`0St z!N=0khIpur>)@$cG48W3X0c9@KPxNp$;htwl#n?jLaIJE7ava9vy;cV`P~)a-6`%% ztJn&68IMG_JAuwP@anRv8{9vv^wsBN$eBH*5#s(L>axI#rU{r1pzM?{*5%kDOFl5J z_@u>=k_#Hg>%ueO?9aC%(=XHI&ebo4Z_T||j9-FHbqh1%X;y#_p~K6FvGpO}H5A&L z4EYg-MD0bbtt^2sRIHu4HCmht|5$TS?FXQIj)ED^Mem_IStxV=6q12}iSE{!i zA>lF0cOE7%?k8K%CiS-)27h45MRboi5{i}XMGws_CusYc$|jIU&`ir?igR7(8RhTN z#k^F?c&SwO$9k_k1~&znE?1BCkU=~A+gnP@w`-HyH#V5ad#cD{m?~F7=IVpw#PNd_ zE+y#_!#{HkBO2tQa8u>@Y5U&^g+<%Qp6MKh_X-|}MXLl2HJ9$SEk7}yx!>y0Ys!nh z5`TCB;MV}Q4#5R?N_^~TI5NN3iflKPki*i*W;E*b{6^U#x)Cu$6*NQD(SGi5>Gzr- z{bp&OUaLq0n~iIt_#D7GG~HJ+6W8uLD1Vj}b+GII2+svR`17x0>mC>O&W({p| z(87X29~2cIA!x{JGC`Oc#FwdI0YH`T7;@x#jiAWW9t*MB!A;n5BM#3WL4JtfSUmOw za>p<$f|WF!m300LL8yS>LR^ToeY%5q{pMcqDD4`Zna%eSoxGxQLYwNY7L%0PSh!w5 zm9@I{YXMHKN2d?n=i2leLIqmHm(33^J!->ckgSR7A8lzA^VKsWI;<}i5OYAU$+tri zDQe9~U0Bs0NYl*uacdu<1GR0vG)g8{J^cQG*%HNJz4)1hTrnwAiHkdYD#S~hn>^68 zqUW3^^rI))AkZ4G8c%cpi|ez~IdQM=q!1w&PcIQd0-5jdsKLG8i4Il)4=7p=k(IM3 zNrXmr$oJXtp+p7wDAdNlEyK4CAB#3}m|OqiB8wJu+~?{cX3{Z(5*8iFqR^~O(aS{- zP!nXRWyUk8rnx98;)EnFvnFK0v%lkNLUZxsBh!e<3DW=?iqLk$v z;U+%JwU9wt;hd|ZmV9{0EW;ZG*Q@DO^#!`;xWP`h)*6nUHtXmmoqvDUYSN{lD=$A9 zg%QQI4VcJaKY)G1e-zP*6kZFC)KFJW9DcHBCbDRXFhtOH24JfSn ztXZ}w*7}MHz713jbf^G*XN>jK7AqsQ8mn4 zgj@)N&-!WDny9WUy7pTl@o9FLh7WHF+|JMqn>(r~i8Q{E{X#$dE%+2 zi_=^KRW{JC1J>2R)fXp>udJ|uZR(CMemH2xfh%~yT-DeCxgn*c1b~}xKA@KH#Yt4w z8d$?h37h#gmyx-Hn_lPu=uVfl;O+cz>-p8rRLmdV%^5aF>b#9?JNDFdd#|h8@RUs@ zV%OXdzqtk4Jp9<5Yf{pY3h=VDO-x3tOV)0E`9Rg9H{w$5d!sn87vcYP_UqR;kSRs* z0MrhAT|pGN{C8;v64*`|OxB{;pAW4)m`~VLO0ta1Ajdun#9l`pABYOb{B3I$uOaPv zI(E17{c3+gg3+#75%uoWHpf8ES5nhDHI+D1CoW`^KXQqdm+$OL9wM@I`ZVJT1eu|2)j%U30X87@T14g_t`R5v6oU(3?68|VAzOF{kbJQHLN64pd_bAT%hr$@)SFM@yokRyH6b6vEE z;psQad;qn}f*j^8y_maBeOVW_kmn z2a>;-XnJf-hw%gS%T3Nq)wrE^MY(2ftFNO&PW3ZKLO)ikDG893W%@?H@Nd5?w5WXu zdl|CVBf)SzDsu*$9SDjJrW@y7&}Zn~)x8uIHjO&-V#~m;WqyD~;h(S(K~?f8(2OU$ zBV|3!?XNq5bK7J?-Y6}*%u}1o0_f2FE;_IU!u@u!cUdmWVtz{^7xlMdVr-&N zMB93@`mn7fF*%U<^11vK<3hK2S(kq2ne5|vY2l=*EbUy(p`QsCoBY{GC1TB;?>(Ku z6Q~~Gg3pKc9mKeZ#P_kHVq*Pq8hFfX+!IRpw3)x2f3TPTc?ug#tStEk#QWJA<{dz0G>lgZu+fe$ZUN(`7`g!3`Wq1Z78fe*hmnu*T#(T zA}UP0r3UYrjBx^V&by3!BbIPY1IH9uS4Hn*G8vsT`r+MZ&MVjHiHQ`woCDibZ|4w| z#PUyC;0=KJ(1>L2Eh=hi8Y~0AJnPh?s9DW{x!d0#IiU4xIY-NZvO=tzZu0QFh92=6 zL=x|`S-->@5cLE5s;3g7nmNdN3VND1P=atI1Pq#m6z&1me%v}pTt#|p&lf&)Y1V`G zap9aID|gDoj~-ZefF*$QGg(aqm%;tTp8y&A+Sz0;?_>f}`9HB`e=tf5`t61PcM3co zJ_e*+01yRV&{}lB838vqWI;pTb$XNV{&AIFmwa3(SM);Q|7iiP$H0Kw>Upu}>Iw}{ z|5Bl$vYec(?mHF3w_SfP$N!Z|P@ffUIQ^;R>@;&`$xDH6;pIg^p=gup+Q1-M`kwWf zUAwYEzK+V9dxd22Eq(X8OJ;~T031|%Bj}LkRue%}?v3y}=$Aw*96Ir$R;&ceF5xG@ zmG0abD1^gr223h|h%{TW$xrW8SE%Hf;O^x++;UyH{O+P{(a#q1Znjy7HsWl=Tf#-A zA+_dI_SFXpDB`R3fU3VA$&sY8bZ`Pkw2APiZY7qaj@4CbRXy3d!H{&57@`#yWUu8k zOe{|RITCmVy%4YsAa%*r=1QJb-xs-jC58Nl;OOJ?JM84B2?Ot?JN_^CI@E>GxN*g3 z!(JLmTYM-~ryC{2zdpa=Jns1c0{}Y2Hwl7&>&nOE@k>e)fUFoc2i!}WCh~cIt|X>& z!rcd9Fi^)KTq7QmTdJ5^g#~U-c~Wl9kpS_9C-YGNQr5C zHb>eH8$mz&Fp|VfBK>H~)}NT|O93=mATA3w+#l{Wk4vZ2=Sx zK0G|kD-G6sVNsuVtBlbi9Tf)f1YRh3IUkR5$3)9I{pQ@MhXM zbr>3H>kIGP52s&jC}(7(ap-NAl117fy`@APyLWyKp$`qlSskK{=Xk()@~sIl!n23` zS8z*M0LZVpqj;h_oAh*EcLk28%IfNe2fiyk(XcXRjH7n(mn1L$yI7#;h4VDY-TzXW z3cI;;%4KGgUMZPf4vh4i98i){lW%ps84N|jl?@oYP1@ykdQ61y#bWtcXmJgOgD2ph zP7m3pGs2+RRK3ChFA`dt-P7~Lp*7WS$-44YG;lpYJ%){Wdr(()|BN+%)N*PD!?kH> zu+5@PKn627$f1uZcpdIS=CGlmA!t}HBfhy8a^VvY5J0`5RVw9y9DOPH)yEh2k0Rr? zzY7`P-A1>~Va#kP$pqK-W<1vBKQUTegNl1!ztTfPB-%xWXJh#}-FQYuh7R)nvE?f!q@IBoZTl_Pj2=GxxTr04Kq8O{ zKhoG^kX?c%VZZo&Y_BCy&cnX##l?6XKRv^AXJJw+$Gcl5Z(afIF}4n5mVcm+5yfYl@HcJ5>OvTXmf=D97%h$_)M1briK*yz#xHG zkpi(i%itD?HZY6h2>4lh1jx&&zE=l_#6e+CV9hA!1Rte>J3(YO$nKROJTdTiz%k>` z+8#>*plA_X89&$mL$#7r zKmfmxCl-TnwVefNG8jGEzvpWK01U(IVQ@L{rI0yCjkubc*n_B=TtccmEr%E?AeE=T z7$T3%nuv0IH4+F2K)pBaDgI;wj!(*qDrNz(aQ;l^0}6D~F0Tnc^dw;E^2qwj^OqQRh5ME zjxD$jR2@*s0e11WxVRI_uzT+b{8Zf)FW{F!q#Jrd+~-Kh?cnk2Zurxd@)|?x;K$Qp z%m8Mnu7Sz``BbaRTQBC0Q8usUd6;;F=-{FUY#`H?%mc4(e;Qnca2_QiY3%ImYDV9q z_UVCi<(HLB0NNkK@kP>?$y~dLOE?O$HE})hZpZhdU`zHxhx6z3l%{uxE`B*5n&O3@ zcOGX@kSx|=T@wi}7Kw%LqN9UlbZ^k{H&h2eJ41h#LHhl7SILCpbmE-2-#**Lalh;e z%npc{bc8gje%NoI1D6RntjQUIA~bpcNQSCRVh5poJzUe2thUA8dHt0<(nPFOoJ`;UH9k+kf+GV~zPZFScm~ zHGlxo3YN%~!%Q7Idqi`cF2(aR2=0dk#zuw*6JeU)o@TT0X%_2tt$F0Wdq-$g=L|e) z=+IghoEK!#Jxv*5#vxxp;+dx52Y}htYuA8b<$BO&@lyhp&YM^oGQR{B|mYR^uW77eS6e!zFfmUhuLtR1k#_nSJU@;x17SVLRHwf zXSVGieH$1VXz!ABm%O-{H78WRHNwpW8Nx6u5DVrJQ@i~=KSF_tryn9@_dp~_v5Gl_ z+H*FHJO;vnnJRYqrBk`ZMH%dSkS2(`uVCLrNT8|w)i}$f3NH?l(#e;P>6B%du)TmY ztgWrZjqHX;eADnfL31z5d_0}z1X^H$SmLXr^;9`5 zfM@~aKt{v}x+=(4N53fv3ari6p?lfgaQ^o7_g8^@76KpEyod!e2q6O|?qQ#I9GF;! zM@B%v#IL3AeT^Bx@eO>akLNuzy1$@Em@cbPmc)JSG%lMO2OaLF>`Qzd{Vk!;907auM?q>DCbt!^<0yK`GG`K zuce}aEFP5laL08myDj*qHL}3RJb4L;v%{rMqLiE0Zey-o<&<=U&JsWgxPJi9hw9MY z!C@xRS1(m{Z|NpHfID^;?qP1H-Rrv_A4z^ROgb%RQ`7;CG9dEcIl56qnU)daMZgD) zp0BSs>Pe>t_m*kEo+3!!yBuq6y)u>K={vzUGfC9G{_DpCI<(|q5y(j2G5-a0Iw#7C z@zhYM-y(lnB!XH9eT3Ul{8Supp)3ZM3S{NJ6kfro-oYBhfu0F2Kfp0n$9X{;GIL)1 z?p>Ht{<8I|N<1Wek55k4S_EzXH8yO;Y-_5xwC1sG+6QD_bQPCgN}p}XqHUtu8;I3a zFPUK>*xA&O_ojTQl&|_h&1#)4xpl~r?^Vs|^No!Sv8|6cDWV4Rnwn^#MH`Z|mEV*;`>Q_goa&R1%*5nnm0 z956*JEG)pxv!wjUprgVmov~DI2E)}`-I4+m+02o?tK?Be@msojToezkSc9$rYA1;L zBP1qvX}asxeRoW*{(HCVHQJuj$i@d!KF#xRQK2vG9b;#>YoLaMlL?xt5H6P@KWw)ShvYG4UhrLaq_-0>>t)4CQqyKOP;F#EjE)cHq0jj^q z45)44Euil+ei@ngeg=QSG-D4u&ic$4u&6+?Icc-^R#$$4Ow$h>N`YXhKi~7wu|kM< z3zwk;YQYO*MWf%NOUa08pG}p-O(nH%$nYMA8E<|JpPzv0vLXDlJ0Fci^{W!N)M$~H z;C_OHHp8i6u>NH%Ogq!EZ9kEZgi`nG0q|+o*6OD1Lhko@p@juV4Ju7`_f)bXPnlDj zMi#1Afm1tx-aASJ<)ajsSJc0BfB1k8yOEoln_pbq@Ry8@x!x8n9NN0vy#SW05)v2a zu`wjTKJuL)NcNQ?hBypTc>p?M@~*xStPymBK zSG~NvoE*0}!)gY@j(+rZz^o&;xkvRDAeMmaD6XRXr=rk_1I24#4hb>7$IFo^1B{JP zW`>3xP=z8R83WUqbJ26n0%82<()aQFw}!Kmas4Rvxf*p)LuY1&!fERGd3ukX zEhSNRCaE9j-E@+HhryGMBX1S+NX_X?)yljxG&msW(Do}g9Bp4#=W#-BM^Hq(Iyg2o zAe9x35@@6EC?m=4JC8gaH#W*RL=({HmfW$n$v%Haw^F$cqylSkQDD{qhkG(#>soB7z&%PH4#PQr;pk=$qd@zM`+$cxLNvgUpet61{W;GJ`) zE{G7(=pmd`fF5oHek_Woe=5URaL<8^c~~SR5nDa&o;yLfJk)=%Ct-VX<6SA@!UrD| z?4V(%q>VXQ*+<<)&!jPY0JTzEN(puC!%eE%>*~Vfp;908pbG9n1^`Nn#<0+4E?7$4 zYiVYqgIoK)r@;mYKZ-Kss*aRSEklLp9OB%L}?+(SAnI2PLO# z0)PS5>L!vw)k{D#vYOQokiizp z&FJ}ks$+I`*6uW0D@Ug=8;KODR0XnGkv`1o&~M(&Rqx+p*UV&5n80Z5L7w+ma-dfC zs3NuC4hGf7)?_C6_y45qJbZllfIrM1M#zV}vQyq4Qp^1VL8f5s1CTE3+N&Q*@f$W| z)q3*Ds#l>e?^Z9)^M>2ICKLz+34^bKx3Bk&h9cMA@}+fti#7kEY~^Dl1Tryn(HjTo zZUUHQU`}pO;SaXdN^J{NF|Qe~avOR=1bV^&L(b{f_1!moPm9^ua{7%3&V+)LbvpFt z{#OFr;_axwqN9ac2bl66)~iMq_Dz)(ByNHN%WdeRQf~AS_p^_)Tj##wQa2G%`<`}o z)#%Vw4UzVb5YYj@O7emslf?ZB-4aHcr*qDt|Ct(-gsQ7_IGBHka%6--Wd`IJ_;KLA z;ENs)^}u|)DwGsU0PfDg@CMVj_!5k&jO!xG`;cg zstmYORWC88s-;eOLfRJ|oGk}Qu28^2bY3_*Pw#lLA~4*kyfI>I9EB0I2>j4x` zwDZ4CqXw}c;`1h_Rt;FfVURkcl}e*>X1Hdu&|p+hZ*}RPH_#tdcR!cBbxZuD6$cXol*ihjrv$to5fq)>SG>-Xe$Xc%CU&{i zG{;(v>8BMhuT}Z3zVN#LI8cMG|t-y)ous4V?kYSgzBTbL~B6V`{tvg37r5tz);}IgeHt*?O9-8_8<)O z`qM`|#?D%H?`>d@$&LS-`Q)Cjl}DQpO_)Np0}lu?>)}i2iSm{_c!2*nIGM>ai2 zS}K{M^J3VYL^1SzER^B6F(fZvq!z>wc+g>kg_y>4_hoh0<@Ris1;sRAjPR9Tt;ljE zf;4$gwzze|UKm`v$eqJRL4S0$&2C9|x<|4TAe{R5IbJU2YK*y70Ieg_)E{00=r^=hV45h1JIAS{eX$7s$q~M8R33Ia)&MIX zyr2l}2mje;p=Sgm8qPI|ckkYHnDf6ItW*QP=++iEe@p=ZTr1B3(gH$({rn_gc2P`| zk@tEPfw;`JoPR2&+3XEP;qToY9UV>n3Y+e%&6r3p+8UFRnU%7SIRpskdq9c_eGi}; z)>c}A-rGNZSa1(FV8Z5Ik0)#?}AGZ1sc6+IJ13L`g+svV{4Fx3P(!{wPT zw%dt(AKG;nRcv;=N~@dtkWcCo2T20D2lX>LcL1iTr2ldS`d(O5XkBN( zJ`IY5ltFBGplxk!A;qugI+qic_%606=sU&Bzp`}8KC{28-;*ir;?-yj2n&Ehs`$*3 zfwt0#zBwvvA)TF_$pHg3p>NcS1^rr0p`3@posfu#(q>abhhjqJ&H%RG!qn%_uiw0} znb7Ex?*Pqi(S$&)_nYZEtbPfQMg;x4D*pHGis5C+F|8$YSCK%Ei7z49muD~@IXXV{ zS2@F;f{t?iY&d!7ok^sKU>(SL@_~GUPbZ$m`$kS0QzG0OmjE0mj--)?oB;wD>LFw|x^%pULXW5%;%koscru0au6*gF zN8p7Nuj#*o>uq_t5@@=|(;B1k!me!z3JNBR&W5W`@?9nDvKM9{#We?FcH;Sk#_hvm zz%0;ykGX|r%JCS#v*QLFv(qw~?vb_c?*=s=TRuZ1SyiFz0MUXbKgDPL;6zSGch+sg za;Z$voA^>Fy6)?>WM_Zfq<&E1P$+_UG#}zj zb?_QvZqDF>c@OUjFFOW%vyJt81AI(1%+%1%rKY}2-TM%!aTlMi-jK(ss}C-gYC*g! z^-kA!0BFH~r4dEBN%_8GLs9{nDPASCauw7QprZ zv2b&%WGmMfMQ;vyAD<&yOvfWdw{xtbW!xS&5X;m1nK7=fcXF5jBMx_%##!9Znk5B? zw=*6ynQ;=vAujClY=J)%bO3t84}>3!24c4#GD&DR!g;)Q=*Qt$Uj&{ z-=l1c~!E21OrO;5{iWiyHK8MqdKtq9WY$Lh+^EBk$AkjcJkAv{b ztV0gGANx;QK_EW7ZCM437NjG<5w2$GVm>vSG{9coDT;UHKBnXlh}eS)9iZHR^eH&3 zQrXhf0_^E{w?3HN)_Q#6k%5bTV_&`J9s#9E!Vs|A znQR=j-_XzjoU>oYY4=Z)1k<#tyv=-)&pwm89ssBFyn28Kki+VVN{!|0(N$ zX+>UKTqGy)LemIkH^f|}^_&p3IJf`>1)o|mmO+jE2mk_5QKM}34XK1epv<*`*9M&r zy6o$}NW@J~8dW>{^=`fu+EL0lB8B#X)LpyUUTI z%fBT^F45^7SIqg`<{rK9G2f{d5f*m4Zg@BEMc)m;v>VIr zg$@rw!ZAi&GB1;$wYvoHP=Fvgn6puChOo%Inwpg2SF9-0f5c@v2?Jb~@QdS$RDM4w zECAg}IE@WRwVoG<#z}jRat@9D#6$hLtkq4P=Vo(Pr0pa)P?uYXvGH5!9~%PiGr}2m z03|>ofO{#F%MpVDS^y@5s={qucn12VH)pH_;Yb4_2D0j(zf%{dz`tEI!3@S3U*86{ z65&LRCLsR4w6uM)A>$y67Lm+7;c5u;y2;CHX>QI`Dq{ilK$7z4^7CG#EMs}U4++t? z0ETe-6F_yT&^e<|{QdX7=O?^yu1N0hCHVJY!9)wNx7{6*`3lca``z8aPRAN@gk>DntrGSeS zEcfX$iJ}g=`v(am`k)Z}0_+a_|1J<`u48mvDkV9(m<=jUWkM`9-a{{bEJDFAz%9zdU_06Om7+6z;aSXRq{lb$QkoUf8_V>5hFb+QnLpbKYnh=CN&1s1vFlHE9NU*QmDE`s4<<0qc89uL|EjBNxY|&gclqUv=*%vA6Rl^lUjImU24cPlM78Sum6Xb$-9|5Iy4vJ( zQ4j%2Nf{@sTzx02Z&O;{`>lWutKdlx+o)~nWX$&Vc5doV` zzws7b3<$*6>Ux}<)+Qk<8#aSVta5dSN~UsscsMU#(z3yI5W}aFiZ6lBk*^Pl?z}VC zFHLXHqH%nMZA9q?0xg;>)`dcWMy#(As~_)rfctEMnMN#W0V2aNsJX(vH}bI7u3!mX z+$oyPAC=tcF}qkZTR{(2P(76N+eLdTT9WWEhDB!C4Ju#NhI7zB25dAb2;&N6Yg<~f z1YSM&$l3T?@3Zc%^JR{)L$lgbcvwm|ak~YFWyW?$o@Co%r`kkZUTZPqGy91U zBH5rHJ<0Y25Cy_Rz!qSxyQ1spb$pz*as0>fI)dqKJFBP+v%L1RgXau0LOD?4c4L)m zMvutPO;0oN@WgF6wu+!`qzt~+kIB0ubJ~?d4Ne zT%KQ9NqTZ}a#he8H{j`0$@A7vZ$Gb`r! zw>j3*mKs!`!xj<~6NBus{UX%b!o(;L$-Ds30RQ!rbUt(X!pFu|Fv7&TH>=s=Crlu=Mmufz z(0s-Wliw*!Y_m)=g-Cdl!$v(TU|3X${{8%dP^7c|pxghn_od-juW!2#%9OE`dCJm6 zAw_0UWN0q)EJK-9$Sgvu3?))hissCjLS_<@21AcA6qzeirhVT1-*+GHaqMG%-yin+ zuvW*a@ce%FeO>2uUgvdQA6N7DdOzZ&kskM9V`aT`^Cq|3!&Y1d)KgQhOpPBN6P1-+ zhhhvT#r#iAuje(EOu>2$*6OHt(5(-UZ_B8LPY*-|p{@3SP&-Q9DlYjiH;Q7DFf z63V;%MeYg?N73v^Zua4gJ_(_0Nx~kK`?%(u8#c5KTe7rIfy|0^(Q=&7)u_7VYi(^^ zBEUddmdyL9U{61n+gIJx#Jcg?laGU5d52=QJ1}SUJInA;LjSZ|FR^fj&vz;$!g?sS z{uG&+f|7+&URxX9z}D@$$=XkbCDbx-el-kX>(CGU7i%0QTRSx)MVTo&H@o;KZwEc0 z!*qQa3GCD2$bI(I64L9n`1sY}@c@3h@18)HwH|DqDhmE=RbXZIsr>XCDi5 z2i2>ZIE93S7@3%c-zz+G3zDjv$=$!@>2X?$yu33uJJ@3&G-R?$9Y2;C)JQrfSm^#)~){TbZ%_uu<(XLl-4?X_q zaqsZV!b_E&F88zhIWiT9izX|?|R?4(h?Gq0RmcjN3}tg8l% z$nef|gEh&Ayz5$aIQKFn%8fWAx~ySi8~+e@z!0{IuzXF$*%uWR<&m~hFU|}VG_>L2 z;LA|4ZKxfqf1f2j3y|w&x8$n**unjq3F~XcAUf_Vr#i>3b?-=2@#PR=9%FE7pfX4~ z&p_sIt!@4yhOOZ+nIvLgOd>Nb=@a>_p%79mK>i*Ei!*~j_I)4(k?SIGii^n`-O=SQ~zCLmqeQs076MhLv#L?)Ef(J z1A|tv2UtP}(hT!`wlzMcu&i}A&J-(C%O8g0CV7QG3s9_QM%mDE7vuBi)Ep(l|X}lNALJ} zH1in@&hY%yZn@B|WNN0R+A0QyBuQ$kg)_yY zzm^peJR0@q>%Ghs3IjKvNf%PneU|lIgDFSib|H1YYq_oHp*iZDdt8*x@D{msP48m+ zpJ(sg6o;rC$M1KpASS}v`&nPP#|#f;;c@B&JJsC=x`AEekBaLDJ<}dPu0sEnlan*~ zRdkN8c(g@&`L*B7Ty5JZFGuq%)Xc+DmdBEe+wWd?bHFLIZh5f(z~RGnnA7bW8;imw z$2+JT;nq(yR`hHFjyZKB&xlTIX||{Cq25g^2M28$E2(|t$|oOUh|;pdtUFDrlm4R>R8fDzWfJ$V}c@^61kO z>3n*xt{1&XPba1Zp`mnUrF2tcpU?RJ@>3W%BScfxs;Ua<=;*W;xCw7M zbf;ZRyX56Vu#TitT7cbG>|ig9iaf>fh<#q|{E)nb@(*^3*T>$ubG5p9Us_sP@7s8Z z$=|=L6J=~ksK8Un*DT(3UQtk9SW-l!{GWPN zW^gcN)v8sUGF#3VjJPX4=hMze=-24G*B>t6EGe_UQ~2XXdXcwndB;QIfoTC$i3PT@ zib{6S%CgY;{LI`Zt2!5sP!8z-tI`;V3D$o$ZQ7`yz{@RV5e7LP8g7ys3g+pG5TEgP z@lXMBOU^fCD>7Tgz!nGJ8lo;?-AZn&lZ(`Y!U7U`c z_Q*aG{%O(VPD7?av z@Y-Y-Qu#xSa4C@NDZ$`dQrGS&9;|ea5>yHdZ~B9-!L0!&UD$SFCIRmiSI7PAS*Ksq zzj0gk@AUW1qkWw7tY~c}Mg3eb|08X-p(B?IJ})I@4>SNXj8P+QTKF>QB}i7lPPC3H zb?K27jn#xEjfjjyxHW1DVte?HxVWf{3@7~Sg7H4NIih)G>Cp)Cf!UA@EHoSMD6CUe zRV@+xDz$B!2G-YPICBv080ot>WZPL?*|vwo_nlFCo$G?hj6#oo`b^f%;YkMV znV*a8-nt2@iN)1pDx}a}RIYtu==)1RFgzc34-c@guDPTB5SdSWl$c!l%-k8vo8U7h zBj!mF8rgSw)?hUFbg4zpHlFcq@Qs2U&}yQ=M0q29{>LZ3M+15XOmPLe4m~{9_j9n} zfUfRW-8yi8AYqAfxy*&1r}pjNUr}58$TX-N2LX+AGy(Vivv$hMr`x?)$IiUGeKTH6I1uQY8rKhSvVu63+=3mm;^DBhPTx!_G>Rd*$r4lD&!C37b?cV> z)_H(!(H%S3upESRENE$I^}Tw+CNI&dS0?@YFLQNWT|b~vP1*9a)E-5}jpE{~v0MU? zyY|V^)I!NV{QIh$W#aX|AvQ}(OBS?YXJ6dAa%IiFjht?0M{(?TqKtv5#fB?XW!pM? zZ&tcW1k6-f2UMhjx0#;2A98 z*5>?q0B2}aw<4GGHYx718@{!4C+A)>ww9-E-!&=>7)$WCy}d@pk;>D(99+A1Zl|%l zAO@(B^65v^xGBYooOpJZty5o2lfL;iN{dOXy{vEXb7^^DBHL%mrWzZ6`fH;~0uHaW zIm1O++fDSQ@C&Vbd@u0Kt|&_wGQrVAM&??>+U}AI>wvG!>V19>wYYnEjsJ`b($u5_ z???$$is~3!kESXLo)A)x+U@rWG;`4HJ(Krki-HVbk#3eP6%Q!YGQm0k&%u=~*_j3f}Ps&t38= z-$5(0MWp0BLk6}@Opml-ci>e~&G7QJHg52HEL9?zujXTZh2|BW_$O2&)$NzZYKpSC zb5U^CU<}>jf?_$fiA`>7R1wEI9LNHLHjqTPJ#@f#Ij@prTWe){!bH$hVldWV*YdRv zCk@w=aYrxTwR5O1@A=Fux-`bA`h;DJZKk?+`CZmaY0L8J1plUND(VKFv)>v+H13z* z*Nos;yVfRWVN=@U6iRI8+4gxi=bIj$o;xvd#<5|8{U@UjWkD-)bd@+@C>LPeqAt0? zyCLqhDQ=*rrze@;_G^t9dtujn_}25>Yc7)3xkQu2Qsqu_KRH4+2~HfD~<*~idgfQC0&Y_xHX_iTcHp2_y^pp^i4 z6|X02FrY#u*TD(bVFBL_Dg-&cL&*sV^!UJ#8(ZP>C(60-N!eLey|8q5mqzXI#s+lM&}C$1 zrhPx{A};u_#gRNO&;mHm<@Z=OBj^xB=aLEMf;Vs9Uew=&=^l-;z@;xs`QheIooz1@ z;KPy=B|f&q!+$rs*qr0p=MhoE;gnB9_dhL96@)JZHI$t+QQ^9+RHpxMzTYifh&f7V z+xY8cpCnVQ$5|m}!VD6HUyOU!ep=#vGhH;;!zP!Iyv!%7yzXuE{+-^B?pX7nnn=A6 z2B3#02V%SPf;niFxPS==k(|3rcnM*J-eCkP)t_GfgmI_>u8upgR0UfnDlJ=;oj29N zPUCm?)S6u+td*iL0ik5JX>2QAlgN$c@O3GJwvYwuZwTvlExu&^IQz;Kz2}+eZ#P7B zgLzEe;dc9?b%95DtksYelamCEzzZeeH|N8j((5M?@ffISD>pZG5TiTZ%QteMBjCNw z@j2$ael@GH!`B*Bv=tJgy$?I%a0lR18Y_gHh8bpkw3R5k@d5T}Yo8VG=M>ZG^fG1Q zcRy)huyM~G9)QS_)n@W;-C;Oas3kE!=(cwY)!5X+A{yLYuAK%F5ZoxEa>5joqfNbQ zqYH+&3dcd4b0Z;PJ|jDO9Z;SRtEXF@YnP&csy`359A*_1XoK}>&9pph>lkT_M|waBd`sHHhaNQ6 zh>O57T2Xv8c8nb^;=qY}PoLfjzd%GLeP6Fg?B6M_KN)|Y?1GgOj^^0 z*M(cZhqDJrVQ|y;!N`Mj%+Jq9DOED^O7<)WCJpRgL}QafNC=c%glmBRhAL^S_pC(n zl<}Fgnp;@-HFN&KiEY>J-tErVVHs{Mac3^Hmkgcox4DhrBLK5b3?Xj*j*#R_**CZ#J`z=DB+fv^Ip zQd^ln{K6=k7NeXc%D3L0+DQub*47O+*@*?cmF|A4dn#5JcOQbpc*~YZB|RfULx#T< zu6AlpcG1NXRkB_L{%6D=fBqR-M@M|HeVwtT|E&eU;FH@zCe6ol^EpnV?P^6uMMlM5 z+c$39NS+Rudoq`TP46X}wMvK*69w4;ef?@E#pYiNc-})z&asZ{ipRoZ25xR{7C}WW zh-&-u<9WRJyib|xIPkc(fHQ{IfMpD0At@L63!C0uZmh2lDJbwx8X5a^Chur{Au1z0 zd@Up=+qPLYr(Jw_B5+UWZf^hF+DcY&DP5^=;}My0%1=wH9x#86;_nn2nGjx*y)h={Vmywn#u9uwsUZ>{q*Fh5jLW3*lw~4 z)2FVcOr|^S3*r%+UH444XWP0Jr>Dubm)6pT^k$guYP@XC(^=?2ht)n+DSJaXcz7HW z#@3Hnx4U#+3)Xx5k=O3}shzx-XNaP$jFuD0qPdxyD@(45V0*Z6M{lo{MGQT9_E?mT z^=r5Nvt+BJ3E`7vQ^PE2n$I#(@{0|G6n1jGOOIO|h&!x5hG@`atPBhy5toa+lL%K{l$S$7Xh zF*P+ck?N6nfcoRVi979dW8VGFv#Y-@PRKdz43l1}9S6^-_SjTq-^%(gQCQ9vQdCaf4`a`o6~>`>1YZ`{ChQq(%Gs?)%+* ztk^K?@$1_`3;8RQ+T=W(l&)5V`ghlNu`nhjCDBl#qoV<^H1Rhs@XBK$na9|d-3V^C z7q6OY^CtL1>2%OnKg62HA6h1(oKsBS-X&jQ<(Hc@u%D0&x8}gw_KDXEY*=&D7v8qc zh7DEB2fA$DN63YJjkjd6Z?vN33U}Y==vDB2___~JCzmb%k;vp_n=wm5D$mUA=Lz4d zqr-$iMz5w;=GtN5p)?vij7b|GKfh3Go{a43ckkXg+FnLK-B>T~fAA0%+Lz(qB_b>P zQIhvSyzV9WZXY^K1y+Gnn8GpZJ}YiXF)HzqAtVD%U$^7TJ&+=9{b_HOt{@JL(~LfQ z@%g!7KXOP!S(zFTANFlTy}F9MVPCdCG_2Yw(IZO_D<2eDIBdAtg?pR*ts=Hksnh~K z>AwIcD;KeM8?O>L5J`7C`P2B3NsKB8ZtoQD!T!(2yDI!36|s43B?1>Et~<#4RoCo$ zd{$}i@y_v59?zasN^GoOHDuaT{V{4puTRFm&>`@c|2e4SDslPWe=ovKMkh4)e-oKG zrVTG#o<4ha5lJf|f+6!eo06=)G1Qm$?^ig+%KYvBo!@o*(Z}IJmNX;D@%MM_j>|eL zUXF~EzED3qP#tLn6$X)-(4I9 zijGt*n5aoV(tqXH(=e1(8u!(KXdm-?;H4iw9@nCv0h;^ej~9pUsd}?`qKk3u+BF)q zYQrKTe&v5Z$r4H7E4i7X_TK|FAPCWUu++7}lSeT8tU!Lj4VLs3^`IKv#14xQj))eA7`5kt=w zuZa-EUi`m`48{$d9n$e=H8eFRUPa`Kwda&st3NrirSPP~sIdlsw(E?ItLu)5ilQRD z*I2fYsN}r^$H#r>y(oV0q~FUE+^A9{3^S^c@!f|%h$Bwp4l&Uk?=R12vTG!xh!4I7({2tP;AU5t!m%xKsnC$|A=jn23*i%f0QCHUAfSfYb3 zIMwRXD9ysm%#2;uLDX)S}6cEk1&J}vlD?A;kBfw%EYQTG!jU3Drq)xKRe z)_=^Hepxct#Mrpv%NIUE&tm`ik6m7#o<9;c2cj;*iBUy);*270q%E`VYt5CXcP{W3 z*w2H{Aby#@du%D`@eRJ*BkW#KY7h+jOF z*Tl3Z5qrJ@(y(ukG*bUg!$c`R$6#9+mpa{UFDIf98!Mt@S%8TS69)9tzmn1h1JocoWP82MwJ;^ zBcmPrtFBLgO4ENG^aqccBUke5KEs9AKcp;jpG9OC(SQ#DxpV6L;?k0gQ>!G*FB*p!qS5ETPqB=xWVX$B zRW=pftrHFs!2B16ST80e@eBD5iN3Hb>mD$^ys*AX+GixaAj`33nSjJ10Y%!(QJoL!& zTHhHF!SA4%@V0jBf^f(6OkC?Qjr%&XxwvvnOiVrtV+Gm1(`QPs+UiF3nF!=sGUG!& z84yoVA1PZS;C68L!Z%(-5$qK5bJb&(Q6ZOO%Z?pdAmL_LkOYXw;T+B&hoGQKj@@_C z&DGauDJdyAVq)^|dIUpp_5n~2H@l*) zE}}s4lC``)ghP{yJU~r#-fjao)AfJJ1j~fG0bSaI5&0U>6r+4 z%qTC6=JLqevvm88Y^RiedU~AD=mILfT+r>ksjsgorf^+Y^t&6IT)Ly9pwFOjz%8c!{JhcOUHcHQmictFSBKDS8 zUeUPUJM;batK9tU;2j-pb!=_LP>S?^`$iAV2AZ-&db@Myhk>^1ckf1jzw5^aMtcrT zu?t-i0hwDRC9BhP;$m;yAVG_~0K&MVY6?wEMN1ME+Si})k13@wkxRz=b#;LkNcP^9<)tFon;1a?q=YW{O>^`2 zcefEa{-Yq;5%3O}7SVkYNcSt85Gqjx9aKC%6^LQLFN+t@A!0@vBc^;)Q&W-A(Y0{C zY(cXcPol5-^14LD^V=RBO8)o3g9irM$_FMidDT68jL`;DU0FN}KNVY4vwN@*@DE%L zX6-l^i_*i)EpOuSJUqsP{L`2y6RK492}PlKX+E?7xOS7THJaSt!OfsLn1;>=+bZ>5 z)6ui2+uEic?J&x0JFlU$*(W0nAU*apt zJs>p0VPtJvdzF`z6jP(Q%^8)RBU#Y-4^Iy_*aK^ZX{JmbYzkT?4qk9HE> zbhx0PmVf2H{nDjNK7iL#NCr3$v>-F8&o-+>0MCZL`k|PDTUHg$T7}K`&;KI6wl%I4Ih`=U{TC&i*=v7ZNSMme!HWRhwI0eYaS*snnC8Ls0T&ln zFCcbZ++kIwl<5@54}LA8Yd-W}@bNk4yJq#mLKKE@>+_&K!3;6kGD5_G2=CeMeH!rg zaPBN@`6s4@AAM-`jn`f>ZzF^S7gtzHiU=s>l+XFs355TW=Y-@aw*^A~(aAWzq^@Jz z;?utkpO>%td3N8vRhg!SJmGzc*_s*}(wI(Q3vlkpO+lzIi^wubC)T8abA1f|{PRyQ zE<`wT72YKs2AD2hq=mL*_6?lx>;53+@pdgkeEWIygkCCy)P5yE@4BUi?znh`hDJ$IvuvU0gJ3s3lc55=&*SH93^Y9k5X z7@Huc2HHrYQs3PK+g>}+XwXW{vZhcLvD6+-XCHbw`YiQ@y(thR{TggjtULAf~(*Fd7)F;x{7#p`2t)1AWkFE8D|kmofja z4u(e+Xi;W^%^K(K#78FZ458CydE?u6X5!Txb_mf`-Q7aObm#VNC$L9-gM*jgLe!6< z*Iwo0NlVMa{LHT|tBaK5e-Ok#h$CP+a4lwxE^^! zX_nCI*VmdAd$H2c&@eJG_U@*uWxF*FnF27(nGY$6);u`S>^{r$-^oy??XPc5;M%@h zWE^{X+kg8MifWQK5pwf zl6Q1c`-=vzy2h(sz!4r*|2+h(;uHo?Gz?F(6vSsa5Sk8%7eWnC&sDJbfHid^^*pS} z(9j{`31wvgqKY-wGt_(So!a6!30*LuhUXT4HFI)vw~6VM7GSY?UvF=)W(1ox4r1pR z1ooOHCVT*b2WO1Jm3(Kh`2@NUH~?GW_emy!QjYXkfTDzu0~xS~4Gaxh|C5;Ju<6LL zi;Rhp@0gBV;9ngameQYGtFCu?2%EsM#X~t5+;rl?TAj^!pm@M9O!Am<2Z;q$o>N;d zAXc)dfV`_&M1K6a^~K-bb#&aC{p{&nZ-_&+abxAcUJtOdgcCp?<0}!XUa0{Z9*4V4 z6v`|#IO2;B!ddal%hTqrw5Ojd7oQtGMjY1~Zc1sRfzv=vU|_3nwlD7t)&=n4+!D- z<3lx(oMMyX#S@+dZfNQN-0eejN1KA+3_NBLkaRe=KGUBKW$<;eFy0E4{^ay@&9jrw z3)6+pb;rEqwxci&r$Cmae`gOTdcC0kGK~Y<5nvVwC-vdO2P<1!x?B48KkJ6J1585i z=Sw|%+xg~Zzi7C-tp~jec>eVrM>LNNEx5*Hw)PX-){{>7ku4mYoHUTiW|ZieS4y+| zr=@x-;dP4yWS#!F>8h~WYrWZ` zz$M?3r5P%*Ga#s)qK;#UVl@jdRbY=hW#vy>nKa5Y^t$@K-wUdRO}z{RoRE+aryCr? zh=Kxn3iI-TIzm)3WZB_rTN3_FOU?OU$B``iCO#Aw3?w<>uadGD4;O`knR?>&>lbl4 zuL>xvB^>B~4kQVwP3s|qYm2r$d(;2<^XJ)C6|{(IB)ou(NYlY21s6AW-TA!Slk^y< z1BpQhnJXK%Yk*k`{4>0n+;KEs_F$h7Gzf(YDII}nvZQ}N=LIKFta#6x-+9(wVhrUN zPV8e!tTw6!bRYMe{l!%n#y0R{|Rn+xqtQ_Fk~F$hU%{V`Ids%?jv@vSm&z z*?Z#qAk(lGXthE5XWf4yI`1KfL*I~ns(vkb^bpp8`X+gt4EGRZ3n?>?l9wcDqypA| zMa9aa;=7AX9u5sT#@#elx;Xappq`#4M&0m|_4yV-llY@C?=zZRm*w2C6QMToVi_B> z2DMlV?eVRV7vbgZg7MehUJ6Y4Z`|O58rKFiOQQ0Hox~1dbr5Cjr+Bh90;I3>Tgr?- z*xUS5i&gAuuqx$)Xp%%sf+~lBTH&mDVp}|fAYl6!F>h84oRk%`5_pwg{$1K^keZCZ z)Q1E(f|QW}?lCuMP2N@56@^4ay2AehY}>i${{vkeB7N0(S8?C>?{!^) zBl*|i0gO&imzSxIVYc~ez(gYPz-B`ox`Q~*UB%x0d2JR|C@&{r)o}FtX(co;Z2KxN z`TThLcoGI?rA=)|w{VeyNI*cq9Z}VQ*Yw9v@kfMfS4rGTSN?y)zFZJ{Zz?qTaF=c# z)Hlq{AiwEU?Z+KR1&Q7%SqZ#?{@2SYn)_hDAQm!u?XM8HJnukaJBW)1J5MQXg*dUs z=I_gu$Sm=sTJ4aKSc8!s4DHvcEl;rvW!(6^;V)mjo|~H}kFg%PCY+TY%0Ctg0fDI6 z70=(ppc^?jKtY96C^8-$zSlkILZ9{v^n}@ty7^zpl=n&FDpKh(U zQ@6C_{O!~Rd3;Z3P>FmIG%$m(uy9=W%F;mV&UCHR5cE`4{|t%26jT^+XYf@36)no~ zj`Xbq-FwLsdXItXZ2XQv!OBF?N>v!sIz|7WuA=4}=UA(xJO92O==8p#eV>I@7ql8x zF5(En_?@z_VzAcU%nKvNx z1}VD}70zzL)Fe(|1XSnj2$c_b1H%kI>Fzrq$sR#2PiXTl90?jIXM{&a1LB^)io2`5oSYlge8#KV^n5% z?TBHNcB;8uXOUM)G@=TgW&awvMpgj+oj~h}rdf4iROQ;kw?I%(Lg8nF0+a(SRU1SG%u%+34#{k@x3vug z${}6w`uchwsIu<2IJzUs>nAJ5w>gQG@+aeGziCxhj>UQ_d%^n^M&Oe@*Lt&f*`hlHljTZB@b8tpTT%TB4!4ZB?`rW4)YxADtZ)J zA~1%U(381-<3r9JX28Chbn+{YEcYf9w{91g9_&g;b8^v z%!`YQ(tb0}B$*>T!E}OODHmUue8vxHI_TD(c&yDj{SW9dcW>|S3p@9WUZ76(*WqdQ z<4SWaa#|dvkL9qDyFpVT4~G2mzKf!o;V+9Gjar;t`g0z0C<4Mki!$JE%V!7O+p~9o zN8XESgd|VZfIpJ~#%VVnSgJ7&+p|=id#V)2aU41q2%<+kDtgd z-Z9&=1M!TtnvB9GL*Aja7KJzcdhw^8^7N7{GyCaS%cz zKcaI07G3+m0mh!2TKd&hRdkq&8uRV?9}p8($}z0Vlp~YczF~J41U23SKzzhTf|2U{ zz`AicC{(c^f%-ZM%?Ew`t1WCjccH>z0wz14r$iJJVt`eP5FgmMBdF5KPV=cpWk=a%Nmybl~s_+D&OmOA9?KwF%ozi7uwH zQiI3=u#IpHn8zObnIEXPP+1%}bf^}WoaXl^s}MSl72wfKyHqhL2fCc<9Aio>9hMdd z@!ww;BTwy*z3YILVwP_TnDay%j3hsC6kqF{tG-I z{rFQq40hx?kgg@pg?mAE#XXBmx3j6lXxFD95T7H&>72}>?YmxfN8SaR6z!;GkuK6x zaS3cfjreAa#BTWyj=PYq_o6bNtl_q_8hU@9(49ESUetnlux}je*ViQTN=itO;tmQw z#6l}P*x!F(u}caCPGMoU*4c-fL1dF~RvRtoR~I8L8W#IEX+~iJBHO8L2T~+6g#d80 z9($%-P!|x$J$_^Xn34<;l72K$%^sgg5i*N51|Bm4BL@(btfs&?0g=VtKXa}CUjWlB zwje$IEty5bI@ghin*>Er$#ko!QLa6!LAuE$j4fE9!Rqaw1fCke+7?7vrwhV8DF^~r zmb}TaBv&@WqV#S2rA&)bW;CCm7^ehdYAj|va`f5UoDZR)SMav~tHaKrG0rqQkxMJ8 zasX;Ap};w77_rKeL(+ZWyCB+W{dh-^BNYS`1NVUgZWJ9g!8)bnn-%7r{IeR?_~0qz2>g4gyqIon2g}zFv{~ic4ODEp@CEGQEbb z&{UIr4@Eu6?hA-O64P05{6oMR9gx}>;TX*AH{20U*kKHUg6xq6-(P`ZoS*D~ z(Eqb1l%BN`1YIN$T0g+XT!ZEE#nT@jVdf$6w+EwWn$q7R37ArSl~pHlu z{TPq|D52|ZF3m*qXy2(*{|H?e;1p2ydB0_y@yU~ypl2oR5&Oj_=MEx_!Us~ux^flD zB#$6iXgPkUyMUTw@{L%G2(l4aRv#Ev_YDku+V)(RQFM0gmsJ{!G2I{+sjpw8rL98w zMOqud2@2lOPmrqe-aTPTFYK#`!L`h}p(7M^2%CUX3O_@h1`Vy|7Ot-!9_mqo5f=I} zY!wj`!%p%gA{LpxgdvR2&-%?p3rC-S22(FbUFbgPe{~M?W~4+z>z}Z+OH)ju)h7_F z53mnnS{L6<7!iyTT(&qUg*Ij_4q!AhG>M1B`u$Cm_tD!UlSOA&*cloB@~_Zl{B^m4Oo$8?_w~^j8XAH}MG=nOfNbCe zC?>kikTbq%YN`PR{3hKFV@?0i3j$Ihz{J{|Gby6NE-_CK4e$3+&YBGEU>rWyR5!7-XhdCX(^LP zf|j3#udgBqN+`WADS#_H7bx$S79r%z2rSW_?~>FZ zL=d!5-kJ5t6yX>iN*crAE?o!AP^699)CW?wZ&?bmfC>g-tX}aBw zSv$aA*~+pK(LVqiHzq4_tLybgK<*79Bjxj%;j2_ZK!SK(yZnW+bHi&1>e;t%U*eby zz7gTiIyWOeU{>P zaeP?+X%$kmE@*iSOM+pfqhO@~SOY6e4FesQV|Gj$?GlpBNRtb5wNSKNyM0^t`w9!J zhDe(O0Fk^=(hr1X)(7XoDRI4o-WF})8clYUQy`hcID&Tuhy7MWdydc|2u_ovFOiZ$ zVOO4{l$4ep1&YpfXtn_qrMw@>4WeX$m2mZYK_(f|w&~ZJ;n0XIA`FkzZYUL$u@6G4 zKtV}~09hhBIBfXYFCGLKcuAtG!zrJ8Er5>|jq+VIjbV@H0Hb|lqV>y!!rv?MkG>;aC}k@{IZk^L;Er`tS> &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 062/110] 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 a4a8a28..8df417f 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 da86704..ed4e347 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 063/110] 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 8df417f..60e5f65 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 ed4e347..55597a7 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 064/110] 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 55597a7..31838a5 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 065/110] 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 31838a5..0112344 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) { From e5ffc6ce8309dca4e15f14698a1bdb8a25836241 Mon Sep 17 00:00:00 2001 From: acxz <17132214+acxz@users.noreply.github.com> Date: Sat, 11 Apr 2020 20:10:29 -0400 Subject: [PATCH 066/110] ensure interpreter is initialized for suptitle & subplot --- matplotlibcpp.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 0112344..9649010 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1611,6 +1611,9 @@ inline void tick_params(const std::map& keywords, cons inline void subplot(long nrows, long ncols, long plot_number) { + // Make sure interpreter is initialized + detail::_interpreter::get(); + // construct positional args PyObject* args = PyTuple_New(3); PyTuple_SetItem(args, 0, PyFloat_FromDouble(nrows)); @@ -1670,6 +1673,9 @@ inline void title(const std::string &titlestr, const std::map &keywords = {}) { + // Make sure interpreter is initialized + detail::_interpreter::get(); + PyObject* pysuptitlestr = PyString_FromString(suptitlestr.c_str()); PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, pysuptitlestr); From d7839cc47547f70dba104848e3318aa64025f837 Mon Sep 17 00:00:00 2001 From: Guillaume Jacquenot Date: Sun, 8 Mar 2020 22:34:46 +0100 Subject: [PATCH 067/110] :sparkle: Added a first version of Dockerfile --- Dockerfile | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..850466f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM debian:10 AS builder +RUN apt-get update \ + && apt-get install --yes --no-install-recommends \ + g++ \ + libpython3-dev \ + make \ + python3 \ + python3-dev \ + python3-numpy + +ADD Makefile matplotlibcpp.h numpy_flags.py /opt/ +ADD examples/*.cpp /opt/examples/ +RUN cd /opt \ + && make PYTHON_BIN=python3 \ + && ls examples/build + +FROM debian:10 +RUN apt-get update \ + && apt-get install --yes --no-install-recommends \ + libpython3-dev \ + python3-matplotlib \ + python3-numpy + +COPY --from=builder /opt/examples/build /opt/ +RUN cd /opt \ + && ls \ + && ./basic From 7ef2559239fbc1dc47f9092d650616979a85484e Mon Sep 17 00:00:00 2001 From: Guillaume Jacquenot Date: Sun, 8 Mar 2020 22:35:12 +0100 Subject: [PATCH 068/110] :sparkle: Added a target to build docker image --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 60e5f65..a1e63bf 100644 --- a/Makefile +++ b/Makefile @@ -36,3 +36,6 @@ $(EXAMPLE_TARGETS): examples/build/%: examples/%.cpp matplotlibcpp.h clean: rm -f ${EXAMPLE_TARGETS} + +docker_build: + docker build . -t matplotlibcpp From fa673c670602e3f6ccb34d9bc3f5b10897cef8f8 Mon Sep 17 00:00:00 2001 From: Guillaume Jacquenot Date: Sun, 8 Mar 2020 22:36:08 +0100 Subject: [PATCH 069/110] :sparkle: Added a first CI file --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..bacca68 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: minimal +dist: trusty +services: + - docker +script: + - make docker_build From a7c38ad725c745fd8089fd96c2d17f5410ff74ed Mon Sep 17 00:00:00 2001 From: Guillaume Jacquenot Date: Sun, 8 Mar 2020 23:15:27 +0100 Subject: [PATCH 070/110] :pencil: Typo update in Readme.md --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 72bab39..a1ad7bd 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ A more comprehensive example: namespace plt = matplotlibcpp; -int main() +int main() { // Prepare data. int n = 5000; @@ -73,26 +73,26 @@ Alternatively, matplotlib-cpp also supports some C++11-powered syntactic sugar: using namespace std; namespace plt = matplotlibcpp; -int main() -{ +int main() +{ // Prepare data. int n = 5000; // number of data points - vector x(n),y(n); + vector x(n),y(n); for(int i=0; i Date: Sat, 28 Mar 2020 09:15:28 +0100 Subject: [PATCH 071/110] :wrench: Moved all elements related to docker build in contrib As requested by maintainer --- .travis.yml | 2 +- Makefile | 3 --- Dockerfile => contrib/Dockerfile | 0 contrib/Makefile | 6 ++++++ 4 files changed, 7 insertions(+), 4 deletions(-) rename Dockerfile => contrib/Dockerfile (100%) create mode 100644 contrib/Makefile diff --git a/.travis.yml b/.travis.yml index bacca68..d6175a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,4 +3,4 @@ dist: trusty services: - docker script: - - make docker_build + - make -C contrib docker_build diff --git a/Makefile b/Makefile index a1e63bf..60e5f65 100644 --- a/Makefile +++ b/Makefile @@ -36,6 +36,3 @@ $(EXAMPLE_TARGETS): examples/build/%: examples/%.cpp matplotlibcpp.h clean: rm -f ${EXAMPLE_TARGETS} - -docker_build: - docker build . -t matplotlibcpp diff --git a/Dockerfile b/contrib/Dockerfile similarity index 100% rename from Dockerfile rename to contrib/Dockerfile diff --git a/contrib/Makefile b/contrib/Makefile new file mode 100644 index 0000000..f659cd9 --- /dev/null +++ b/contrib/Makefile @@ -0,0 +1,6 @@ +all: docker_build + +docker_build: + cd .. && \ + docker build . -f contrib/Dockerfile -t matplotlibcpp && \ + cd contrib From 9595628a80f972c7f3ee5166fe8d91ad50e667b0 Mon Sep 17 00:00:00 2001 From: Mateo Gianolio Date: Fri, 3 Apr 2020 10:33:24 +0200 Subject: [PATCH 072/110] Replace occurrences of std::unordered_map<> with std::map<> --- matplotlibcpp.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 9649010..55a04b1 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -9,7 +9,6 @@ #include #include // requires c++11 support #include -#include #include @@ -793,7 +792,7 @@ template bool scatter(const std::vector& x, const std::vector& y, const double s=1.0, // The marker size in points**2 - const std::unordered_map & keywords = {}) + const std::map & keywords = {}) { assert(x.size() == y.size()); @@ -823,7 +822,7 @@ bool scatter(const std::vector& x, template bool boxplot(const std::vector>& data, const std::vector& labels = {}, - const std::unordered_map & keywords = {}) + const std::map & keywords = {}) { PyObject* listlist = detail::get_listlist(data); PyObject* args = PyTuple_New(1); @@ -854,7 +853,7 @@ bool boxplot(const std::vector>& data, template bool boxplot(const std::vector& data, - const std::unordered_map & keywords = {}) + const std::map & keywords = {}) { PyObject* vector = detail::get_array(data); PyObject* args = PyTuple_New(1); From 1243814dabc8a34fa0b6b9ffc86af5b42e85feed Mon Sep 17 00:00:00 2001 From: NancyLi1013 Date: Wed, 26 Feb 2020 05:29:35 -0800 Subject: [PATCH 073/110] Add vcpkg installation instructions --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index a1ad7bd..ce6480b 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,17 @@ anywhere. Since a python interpreter is opened internally, it is necessary to link against `libpython2.7` in order to use matplotlib-cpp. +You can download and install matplotlib-cpp using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + vcpkg install matplotlib-cpp + +The matplotlib-cpp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. + + # CMake If you prefer to use CMake as build system, you will want to add something like this to your From edb40746123e2fdd194083f4e4a75cd985e5c593 Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Sat, 18 Apr 2020 12:19:26 +0200 Subject: [PATCH 074/110] Reword libpython dependency; move vcpkg section --- README.md | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ce6480b..61ffef4 100644 --- a/README.md +++ b/README.md @@ -199,23 +199,15 @@ On Ubuntu: sudo apt-get install python-matplotlib python-numpy python2.7-dev If, for some reason, you're unable to get a working installation of numpy on your system, -you can add the define `WITHOUT_NUMPY` to erase this dependency. +you can define the macro `WITHOUT_NUMPY` before including the header file to erase this +dependency. The C++-part of the library consists of the single header file `matplotlibcpp.h` which can be placed anywhere. -Since a python interpreter is opened internally, it is necessary to link against `libpython2.7` in order to use -matplotlib-cpp. - -You can download and install matplotlib-cpp using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager: - - git clone https://github.com/Microsoft/vcpkg.git - cd vcpkg - ./bootstrap-vcpkg.sh - ./vcpkg integrate install - vcpkg install matplotlib-cpp - -The matplotlib-cpp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. +Since a python interpreter is opened internally, it is necessary to link against `libpython` in order +to user matplotlib-cpp. Most versions should work, although `libpython2.7` and `libpython3.6` are +probably the most regularly testedr. # CMake @@ -243,6 +235,20 @@ target_include_directories(myproject PRIVATE ${PYTHON_INCLUDE_DIRS}) target_link_libraries(myproject ${PYTHON_LIBRARIES}) ``` + +# Vcpkg + +You can download and install matplotlib-cpp using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + vcpkg install matplotlib-cpp + +The matplotlib-cpp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. + + # C++11 Currently, c++11 is required to build matplotlib-cpp. The last working commit that did From d74105036a82e6ccede3fa1a7d872ac1677b00ab Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Wed, 22 Apr 2020 00:28:08 +0200 Subject: [PATCH 075/110] Fix use-after-free bug when plotting unknown numeric types --- matplotlibcpp.h | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 55a04b1..c89a743 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -299,22 +299,24 @@ template <> struct select_npy_type { const static NPY_TYPES type = NPY template PyObject* get_array(const std::vector& v) { - detail::_interpreter::get(); //interpreter needs to be initialized for the numpy commands to work + detail::_interpreter::get(); //interpreter needs to be initialized for the numpy commands to work + npy_intp vsize = v.size(); NPY_TYPES type = select_npy_type::type; - if (type == NPY_NOTYPE) - { - std::vector vd(v.size()); - npy_intp vsize = v.size(); - std::copy(v.begin(),v.end(),vd.begin()); - PyObject* varray = PyArray_SimpleNewFromData(1, &vsize, NPY_DOUBLE, (void*)(vd.data())); + if (type == NPY_NOTYPE) { + size_t memsize = v.size()*sizeof(double); + double* dp = static_cast(::malloc(memsize)); + for (size_t i=0; i(varray), NPY_ARRAY_OWNDATA); return varray; } - - npy_intp vsize = v.size(); + PyObject* varray = PyArray_SimpleNewFromData(1, &vsize, type, (void*)(v.data())); return varray; } + template PyObject* get_2darray(const std::vector<::std::vector>& v) { From ecf3a8dd0bf2766c7988de8c6fbbb0098564f44d Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Wed, 22 Apr 2020 00:31:41 +0200 Subject: [PATCH 076/110] Add native numpy handling when plotting vectors of (unsigned) long long --- matplotlibcpp.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index c89a743..b83d8f0 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -296,6 +296,14 @@ template <> struct select_npy_type { const static NPY_TYPES type = NPY template <> struct select_npy_type { const static NPY_TYPES type = NPY_ULONG; }; template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; +// Sanity checks; comment them out or change the numpy type below if you're compiling on +// a platform where they don't apply +static_assert(sizeof(long long) == 8); +template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT64; }; +static_assert(sizeof(unsigned long long) == 8); +template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; +// TODO: add int, long, etc. + template PyObject* get_array(const std::vector& v) { From bf2be71082081ff3db96ab3ff1a00df7f99da92e Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Sat, 2 May 2020 16:55:08 +0200 Subject: [PATCH 077/110] Update python-config invocation in Makefile Since the new Ubuntu 20.04 LTS version is shipping python3.8 as the default python, update the Makefile to use that as default and more importantly fix the python-config invocation to work with all python versions. --- Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 60e5f65..1a5a339 100644 --- a/Makefile +++ b/Makefile @@ -2,11 +2,14 @@ CXXFLAGS += -std=c++11 -Wno-conversion # Default to using system's default version of python -PYTHON_BIN ?= python +PYTHON_BIN ?= python3 PYTHON_CONFIG := $(PYTHON_BIN)-config PYTHON_INCLUDE ?= $(shell $(PYTHON_CONFIG) --includes) EXTRA_FLAGS := $(PYTHON_INCLUDE) -LDFLAGS += $(shell $(PYTHON_CONFIG) --libs) +# NOTE: Since python3.8, the correct invocation is `python3-config --libs --embed`. +# So of course the proper way to get python libs for embedding now is to +# invoke that, check if it crashes, and fall back to just `--libs` if it does. +LDFLAGS += $(shell if $(PYTHON_CONFIG) --libs --embed >/dev/null; then $(PYTHON_CONFIG) --libs --embed; else $(PYTHON_CONFIG) --libs; fi) # Either finds numpy or set -DWITHOUT_NUMPY EXTRA_FLAGS += $(shell $(PYTHON_BIN) $(CURDIR)/numpy_flags.py) From f5f8ce3d6aa0c588e7f45346a0f89ad4f80991cf Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Sat, 2 May 2020 16:56:53 +0200 Subject: [PATCH 078/110] Move Python.h include into first position --- matplotlibcpp.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index b83d8f0..0b7484b 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1,5 +1,9 @@ #pragma once +// Python headers must be included before any system headers, since +// they define _POSIX_C_SOURCE +#include + #include #include #include @@ -10,8 +14,6 @@ #include // requires c++11 support #include -#include - #ifndef WITHOUT_NUMPY # define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION # include From d612b524e10ebdd43d3a8889a95e84c017ad65af Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Sat, 2 May 2020 16:57:26 +0200 Subject: [PATCH 079/110] Fix python3.8 segfault for the named_* family of functions The fix might be more widely required, but in particular python3.8 would segfault when PyUnicode_FromString() was called before the interpreter was initialized. Which is expected tbh, but a change in behaviour from earlier versions. --- matplotlibcpp.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 0b7484b..a03f4b9 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1174,6 +1174,9 @@ bool errorbar(const std::vector &x, const std::vector &y, co template bool named_plot(const std::string& name, const std::vector& y, const std::string& format = "") { + // Make sure python is initialized. + detail::_interpreter::get(); + PyObject* kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); @@ -1198,6 +1201,9 @@ bool named_plot(const std::string& name, const std::vector& y, const st template bool named_plot(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { + // Make sure python is initialized. + detail::_interpreter::get(); + PyObject* kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); @@ -1223,6 +1229,9 @@ bool named_plot(const std::string& name, const std::vector& x, const st template bool named_semilogx(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { + // Make sure python is initialized. + detail::_interpreter::get(); + PyObject* kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); @@ -1248,6 +1257,9 @@ bool named_semilogx(const std::string& name, const std::vector& x, cons template bool named_semilogy(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { + // Make sure python is initialized. + detail::_interpreter::get(); + PyObject* kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); @@ -1273,6 +1285,9 @@ bool named_semilogy(const std::string& name, const std::vector& x, cons template bool named_loglog(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { + // Make sure python is initialized. + detail::_interpreter::get(); + PyObject* kwargs = PyDict_New(); PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); From 60dcd64c8fd4766e5426f57decfb765422a1d3fe Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Tue, 12 May 2020 01:20:35 +0200 Subject: [PATCH 080/110] Sprinkle calls to interpreter::get() all over the codebase --- matplotlibcpp.h | 128 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 110 insertions(+), 18 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index a03f4b9..ea2e4fb 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -259,6 +259,8 @@ inline void backend(const std::string& name) inline bool annotate(std::string annotation, double x, double y) { + detail::_interpreter::get(); + PyObject * xy = PyTuple_New(2); PyObject * str = PyString_FromString(annotation.c_str()); @@ -309,7 +311,6 @@ template <> struct select_npy_type { const static NPY_TYPES template PyObject* get_array(const std::vector& v) { - detail::_interpreter::get(); //interpreter needs to be initialized for the numpy commands to work npy_intp vsize = v.size(); NPY_TYPES type = select_npy_type::type; if (type == NPY_NOTYPE) { @@ -330,7 +331,6 @@ PyObject* get_array(const std::vector& v) template PyObject* get_2darray(const std::vector<::std::vector>& v) { - detail::_interpreter::get(); //interpreter needs to be initialized for the numpy commands to work if (v.size() < 1) throw std::runtime_error("get_2d_array v too small"); npy_intp vsize[2] = {static_cast(v.size()), @@ -396,6 +396,8 @@ bool plot(const std::vector &x, const std::vector &y, const st { assert(x.size() == y.size()); + detail::_interpreter::get(); + // using numpy arrays PyObject* xarray = detail::get_array(x); PyObject* yarray = detail::get_array(y); @@ -431,6 +433,8 @@ void plot_surface(const std::vector<::std::vector> &x, const std::map &keywords = std::map()) { + detail::_interpreter::get(); + // We lazily load the modules here the first time this function is called // because I'm not sure that we can assume "matplotlib installed" implies // "mpl_toolkits installed" on all platforms, and we don't want to require @@ -524,6 +528,8 @@ void plot3(const std::vector &x, const std::map &keywords = std::map()) { + detail::_interpreter::get(); + // Same as with plot_surface: We lazily load the modules here the first time // this function is called because I'm not sure that we can assume "matplotlib // installed" implies "mpl_toolkits installed" on all platforms, and we don't @@ -605,6 +611,8 @@ bool stem(const std::vector &x, const std::vector &y, const st { assert(x.size() == y.size()); + detail::_interpreter::get(); + // using numpy arrays PyObject* xarray = detail::get_array(x); PyObject* yarray = detail::get_array(y); @@ -638,6 +646,8 @@ bool fill(const std::vector& x, const std::vector& y, const st { assert(x.size() == y.size()); + detail::_interpreter::get(); + // using numpy arrays PyObject* xarray = detail::get_array(x); PyObject* yarray = detail::get_array(y); @@ -669,6 +679,8 @@ bool fill_between(const std::vector& x, const std::vector& y1, assert(x.size() == y1.size()); assert(x.size() == y2.size()); + detail::_interpreter::get(); + // using numpy arrays PyObject* xarray = detail::get_array(x); PyObject* y1array = detail::get_array(y1); @@ -699,6 +711,7 @@ template< typename Numeric> bool hist(const std::vector& y, long bins=10,std::string color="b", double alpha=1.0, bool cumulative=false) { + detail::_interpreter::get(); PyObject* yarray = detail::get_array(y); @@ -731,7 +744,7 @@ inline void imshow(void *ptr, const NPY_TYPES type, const int rows, const int co 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 + detail::_interpreter::get(); // construct args npy_intp dims[3] = { rows, columns, colors }; @@ -806,6 +819,8 @@ bool scatter(const std::vector& x, const double s=1.0, // The marker size in points**2 const std::map & keywords = {}) { + detail::_interpreter::get(); + assert(x.size() == y.size()); PyObject* xarray = detail::get_array(x); @@ -836,6 +851,8 @@ bool boxplot(const std::vector>& data, const std::vector& labels = {}, const std::map & keywords = {}) { + detail::_interpreter::get(); + PyObject* listlist = detail::get_listlist(data); PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, listlist); @@ -867,6 +884,8 @@ template bool boxplot(const std::vector& data, const std::map & keywords = {}) { + detail::_interpreter::get(); + PyObject* vector = detail::get_array(data); PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, vector); @@ -893,7 +912,10 @@ bool bar(const std::vector & x, std::string ec = "black", std::string ls = "-", double lw = 1.0, - const std::map & keywords = {}) { + const std::map & keywords = {}) +{ + detail::_interpreter::get(); + PyObject * xarray = detail::get_array(x); PyObject * yarray = detail::get_array(y); @@ -930,9 +952,12 @@ bool bar(const std::vector & y, std::string ec = "black", std::string ls = "-", double lw = 1.0, - const std::map & keywords = {}) { + const std::map & keywords = {}) +{ using T = typename std::remove_reference::type::value_type; + detail::_interpreter::get(); + std::vector x; for (std::size_t i = 0; i < y.size(); i++) { x.push_back(i); } @@ -941,6 +966,7 @@ bool bar(const std::vector & y, inline bool subplots_adjust(const std::map& keywords = {}) { + detail::_interpreter::get(); PyObject* kwargs = PyDict_New(); for (std::map::const_iterator it = @@ -964,6 +990,8 @@ 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) { + detail::_interpreter::get(); + PyObject* yarray = detail::get_array(y); PyObject* kwargs = PyDict_New(); @@ -990,6 +1018,8 @@ bool plot(const std::vector& x, const std::vector& y, const { assert(x.size() == y.size()); + detail::_interpreter::get(); + PyObject* xarray = detail::get_array(x); PyObject* yarray = detail::get_array(y); @@ -1013,6 +1043,8 @@ bool quiver(const std::vector& x, const std::vector& y, cons { assert(x.size() == y.size() && x.size() == u.size() && u.size() == w.size()); + detail::_interpreter::get(); + PyObject* xarray = detail::get_array(x); PyObject* yarray = detail::get_array(y); PyObject* uarray = detail::get_array(u); @@ -1047,6 +1079,8 @@ bool stem(const std::vector& x, const std::vector& y, const { assert(x.size() == y.size()); + detail::_interpreter::get(); + PyObject* xarray = detail::get_array(x); PyObject* yarray = detail::get_array(y); @@ -1072,6 +1106,8 @@ bool semilogx(const std::vector& x, const std::vector& y, co { assert(x.size() == y.size()); + detail::_interpreter::get(); + PyObject* xarray = detail::get_array(x); PyObject* yarray = detail::get_array(y); @@ -1095,6 +1131,8 @@ bool semilogy(const std::vector& x, const std::vector& y, co { assert(x.size() == y.size()); + detail::_interpreter::get(); + PyObject* xarray = detail::get_array(x); PyObject* yarray = detail::get_array(y); @@ -1118,6 +1156,8 @@ bool loglog(const std::vector& x, const std::vector& y, cons { assert(x.size() == y.size()); + detail::_interpreter::get(); + PyObject* xarray = detail::get_array(x); PyObject* yarray = detail::get_array(y); @@ -1141,6 +1181,8 @@ bool errorbar(const std::vector &x, const std::vector &y, co { assert(x.size() == y.size()); + detail::_interpreter::get(); + PyObject* xarray = detail::get_array(x); PyObject* yarray = detail::get_array(y); PyObject* yerrarray = detail::get_array(yerr); @@ -1174,7 +1216,6 @@ bool errorbar(const std::vector &x, const std::vector &y, co template bool named_plot(const std::string& name, const std::vector& y, const std::string& format = "") { - // Make sure python is initialized. detail::_interpreter::get(); PyObject* kwargs = PyDict_New(); @@ -1201,7 +1242,6 @@ bool named_plot(const std::string& name, const std::vector& y, const st template bool named_plot(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { - // Make sure python is initialized. detail::_interpreter::get(); PyObject* kwargs = PyDict_New(); @@ -1229,7 +1269,6 @@ bool named_plot(const std::string& name, const std::vector& x, const st template bool named_semilogx(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { - // Make sure python is initialized. detail::_interpreter::get(); PyObject* kwargs = PyDict_New(); @@ -1257,7 +1296,6 @@ bool named_semilogx(const std::string& name, const std::vector& x, cons template bool named_semilogy(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { - // Make sure python is initialized. detail::_interpreter::get(); PyObject* kwargs = PyDict_New(); @@ -1285,7 +1323,6 @@ bool named_semilogy(const std::string& name, const std::vector& x, cons template bool named_loglog(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { - // Make sure python is initialized. detail::_interpreter::get(); PyObject* kwargs = PyDict_New(); @@ -1336,6 +1373,8 @@ bool stem(const std::vector& y, const std::string& format = "") template void text(Numeric x, Numeric y, const std::string& s = "") { + detail::_interpreter::get(); + PyObject* args = PyTuple_New(3); PyTuple_SetItem(args, 0, PyFloat_FromDouble(x)); PyTuple_SetItem(args, 1, PyFloat_FromDouble(y)); @@ -1352,6 +1391,9 @@ inline void colorbar(PyObject* mappable = NULL, const std::map void ylim(Numeric left, Numeric right) { + detail::_interpreter::get(); + PyObject* list = PyList_New(2); PyList_SetItem(list, 0, PyFloat_FromDouble(left)); PyList_SetItem(list, 1, PyFloat_FromDouble(right)); @@ -1467,6 +1513,8 @@ void ylim(Numeric left, Numeric right) template void xlim(Numeric left, Numeric right) { + detail::_interpreter::get(); + PyObject* list = PyList_New(2); PyList_SetItem(list, 0, PyFloat_FromDouble(left)); PyList_SetItem(list, 1, PyFloat_FromDouble(right)); @@ -1484,6 +1532,8 @@ void xlim(Numeric left, Numeric right) inline double* xlim() { + detail::_interpreter::get(); + PyObject* args = PyTuple_New(0); PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_xlim, args); PyObject* left = PyTuple_GetItem(res,0); @@ -1502,6 +1552,8 @@ inline double* xlim() inline double* ylim() { + detail::_interpreter::get(); + PyObject* args = PyTuple_New(0); PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_ylim, args); PyObject* left = PyTuple_GetItem(res,0); @@ -1522,6 +1574,8 @@ inline void xticks(const std::vector &ticks, const std::vector &ticks, const std::vector &ticks, const std::map& keywords, const std::string axis = "both") { + detail::_interpreter::get(); + // construct positional args PyObject* args; args = PyTuple_New(1); @@ -1637,7 +1695,6 @@ inline void tick_params(const std::map& keywords, cons inline void subplot(long nrows, long ncols, long plot_number) { - // Make sure interpreter is initialized detail::_interpreter::get(); // construct positional args @@ -1655,6 +1712,8 @@ inline void subplot(long nrows, long ncols, long plot_number) inline void subplot2grid(long nrows, long ncols, long rowid=0, long colid=0, long rowspan=1, long colspan=1) { + detail::_interpreter::get(); + PyObject* shape = PyTuple_New(2); PyTuple_SetItem(shape, 0, PyLong_FromLong(nrows)); PyTuple_SetItem(shape, 1, PyLong_FromLong(ncols)); @@ -1680,6 +1739,8 @@ inline void subplot2grid(long nrows, long ncols, long rowid=0, long colid=0, lon inline void title(const std::string &titlestr, const std::map &keywords = {}) { + detail::_interpreter::get(); + PyObject* pytitlestr = PyString_FromString(titlestr.c_str()); PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, pytitlestr); @@ -1699,7 +1760,6 @@ inline void title(const std::string &titlestr, const std::map &keywords = {}) { - // Make sure interpreter is initialized detail::_interpreter::get(); PyObject* pysuptitlestr = PyString_FromString(suptitlestr.c_str()); @@ -1721,6 +1781,8 @@ inline void suptitle(const std::string &suptitlestr, const std::map& keywords = std::map()) { + detail::_interpreter::get(); + // construct positional args PyObject* args = PyTuple_New(3); PyTuple_SetItem(args, 0, PyFloat_FromDouble(x)); @@ -1757,6 +1821,8 @@ inline void axvline(double x, double ymin = 0., double ymax = 1., const std::map inline void xlabel(const std::string &str, const std::map &keywords = {}) { + detail::_interpreter::get(); + PyObject* pystr = PyString_FromString(str.c_str()); PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, pystr); @@ -1776,6 +1842,8 @@ inline void xlabel(const std::string &str, const std::map& keywords = {}) { + detail::_interpreter::get(); + PyObject* pystr = PyString_FromString(str.c_str()); PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, pystr); @@ -1793,15 +1861,16 @@ inline void ylabel(const std::string &str, const std::map& keywords = {}) { +inline void set_zlabel(const std::string &str, const std::map& keywords = {}) +{ + detail::_interpreter::get(); + // Same as with plot_surface: We lazily load the modules here the first time // this function is called because I'm not sure that we can assume "matplotlib // installed" implies "mpl_toolkits installed" on all platforms, and we don't // want to require it for people who don't need 3d plots. static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; if (!mpl_toolkitsmod) { - detail::_interpreter::get(); - PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); if (!mpl_toolkits || !axis3d) { throw std::runtime_error("couldnt create string"); } @@ -1846,6 +1915,8 @@ inline void set_zlabel(const std::string &str, const std::map inline void pause(Numeric interval) { + detail::_interpreter::get(); + PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, PyFloat_FromDouble(interval)); @@ -1934,6 +2015,8 @@ inline void pause(Numeric interval) inline void save(const std::string& filename) { + detail::_interpreter::get(); + PyObject* pyfilename = PyString_FromString(filename.c_str()); PyObject* args = PyTuple_New(1); @@ -1947,6 +2030,8 @@ inline void save(const std::string& filename) } inline void clf() { + detail::_interpreter::get(); + PyObject *res = PyObject_CallObject( detail::_interpreter::get().s_python_function_clf, detail::_interpreter::get().s_python_empty_tuple); @@ -1956,7 +2041,9 @@ inline void clf() { Py_DECREF(res); } - inline void ion() { +inline void ion() { + detail::_interpreter::get(); + PyObject *res = PyObject_CallObject( detail::_interpreter::get().s_python_function_ion, detail::_interpreter::get().s_python_empty_tuple); @@ -1968,6 +2055,8 @@ inline void clf() { inline std::vector> ginput(const int numClicks = 1, const std::map& keywords = {}) { + detail::_interpreter::get(); + PyObject *args = PyTuple_New(1); PyTuple_SetItem(args, 0, PyLong_FromLong(numClicks)); @@ -2002,6 +2091,8 @@ inline std::vector> ginput(const int numClicks = 1, const // Actually, is there any reason not to call this automatically for every plot? inline void tight_layout() { + detail::_interpreter::get(); + PyObject *res = PyObject_CallObject( detail::_interpreter::get().s_python_function_tight_layout, detail::_interpreter::get().s_python_empty_tuple); @@ -2149,6 +2240,7 @@ class Plot // default initialization with plot label, some data and format template Plot(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { + detail::_interpreter::get(); assert(x.size() == y.size()); From 480af0f4f9d13dd62254981855e7bc1a11378dbc Mon Sep 17 00:00:00 2001 From: pf Date: Mon, 20 Apr 2020 10:29:54 +0100 Subject: [PATCH 081/110] Added support for axvspan --- matplotlibcpp.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index ea2e4fb..6cbe74f 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -73,6 +73,7 @@ struct _interpreter { PyObject *s_python_function_title; PyObject *s_python_function_axis; PyObject *s_python_function_axvline; + PyObject *s_python_function_axvspan; PyObject *s_python_function_xlabel; PyObject *s_python_function_ylabel; PyObject *s_python_function_gca; @@ -208,6 +209,7 @@ struct _interpreter { 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_axvspan = safe_import(pymod, "axvspan"); s_python_function_xlabel = safe_import(pymod, "xlabel"); s_python_function_ylabel = safe_import(pymod, "ylabel"); s_python_function_gca = safe_import(pymod, "gca"); @@ -1819,6 +1821,29 @@ inline void axvline(double x, double ymin = 0., double ymax = 1., const std::map if(res) Py_DECREF(res); } +inline void axvspan(double xmin, double xmax, double ymin = 0., double ymax = 1., const std::map& keywords = std::map()) +{ + // construct positional args + PyObject* args = PyTuple_New(4); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(xmin)); + PyTuple_SetItem(args, 1, PyFloat_FromDouble(xmax)); + PyTuple_SetItem(args, 2, PyFloat_FromDouble(ymin)); + PyTuple_SetItem(args, 3, PyFloat_FromDouble(ymax)); + + // 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_axvspan, args, kwargs); + Py_DECREF(args); + Py_DECREF(kwargs); + + if(res) Py_DECREF(res); +} + inline void xlabel(const std::string &str, const std::map &keywords = {}) { detail::_interpreter::get(); From 107912124d9d6a0a6942b2b2153c781905e83854 Mon Sep 17 00:00:00 2001 From: pf Date: Mon, 20 Apr 2020 10:49:19 +0100 Subject: [PATCH 082/110] a few of the parameters are floats. This is a quick hack to get a couple of them to work. In the future matplotlibcpp should use map instead of map for kwargs. That should be a separate PR --- matplotlibcpp.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 6cbe74f..b4bf18a 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1834,7 +1834,10 @@ inline void axvspan(double xmin, double xmax, double ymin = 0., double ymax = 1. PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + if (it->first == "linewidth" || it->first == "alpha") + PyDict_SetItemString(kwargs, it->first.c_str(), PyFloat_FromDouble(std::stod(it->second))); + else + PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); } PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_axvspan, args, kwargs); From 374dcd032ca8e609d25ba7ced2dd3f25a173fabb Mon Sep 17 00:00:00 2001 From: JBPennington Date: Fri, 24 Apr 2020 08:58:15 -0400 Subject: [PATCH 083/110] Added arrow, cla, margins, contour --- matplotlibcpp.h | 116 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index b4bf18a..7460eb7 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -45,6 +45,7 @@ namespace detail { static std::string s_backend; struct _interpreter { + PyObject* s_python_function_arrow; PyObject *s_python_function_show; PyObject *s_python_function_close; PyObject *s_python_function_draw; @@ -54,6 +55,7 @@ struct _interpreter { PyObject *s_python_function_fignum_exists; PyObject *s_python_function_plot; PyObject *s_python_function_quiver; + PyObject* s_python_function_contour; PyObject *s_python_function_semilogx; PyObject *s_python_function_semilogy; PyObject *s_python_function_loglog; @@ -79,8 +81,10 @@ struct _interpreter { PyObject *s_python_function_gca; PyObject *s_python_function_xticks; PyObject *s_python_function_yticks; + PyObject* s_python_function_margins; PyObject *s_python_function_tick_params; PyObject *s_python_function_grid; + PyObject* s_python_function_cla; PyObject *s_python_function_clf; PyObject *s_python_function_errorbar; PyObject *s_python_function_annotate; @@ -186,6 +190,7 @@ struct _interpreter { Py_DECREF(pylabname); if (!pylabmod) { throw std::runtime_error("Error loading module pylab!"); } + s_python_function_arrow = safe_import(pymod, "arrow"); s_python_function_show = safe_import(pymod, "show"); s_python_function_close = safe_import(pymod, "close"); s_python_function_draw = safe_import(pymod, "draw"); @@ -194,6 +199,7 @@ struct _interpreter { s_python_function_fignum_exists = safe_import(pymod, "fignum_exists"); s_python_function_plot = safe_import(pymod, "plot"); s_python_function_quiver = safe_import(pymod, "quiver"); + s_python_function_contour = safe_import(pymod, "contour"); s_python_function_semilogx = safe_import(pymod, "semilogx"); s_python_function_semilogy = safe_import(pymod, "semilogy"); s_python_function_loglog = safe_import(pymod, "loglog"); @@ -215,6 +221,7 @@ struct _interpreter { s_python_function_gca = safe_import(pymod, "gca"); s_python_function_xticks = safe_import(pymod, "xticks"); s_python_function_yticks = safe_import(pymod, "yticks"); + s_python_function_margins = safe_import(pymod, "margins"); s_python_function_tick_params = safe_import(pymod, "tick_params"); s_python_function_grid = safe_import(pymod, "grid"); s_python_function_xlim = safe_import(pymod, "xlim"); @@ -222,6 +229,7 @@ struct _interpreter { s_python_function_ginput = safe_import(pymod, "ginput"); s_python_function_save = safe_import(pylabmod, "savefig"); s_python_function_annotate = safe_import(pymod,"annotate"); + s_python_function_cla = safe_import(pymod, "cla"); s_python_function_clf = safe_import(pymod, "clf"); s_python_function_errorbar = safe_import(pymod, "errorbar"); s_python_function_tight_layout = safe_import(pymod, "tight_layout"); @@ -709,6 +717,37 @@ bool fill_between(const std::vector& x, const std::vector& y1, return res; } +template +bool arrow(Numeric x, Numeric y, Numeric end_x, Numeric end_y, const std::string& fc = "r", + const std::string ec = "k", Numeric head_length = 0.25, Numeric head_width = 0.1625) { + PyObject* obj_x = PyFloat_FromDouble(x); + PyObject* obj_y = PyFloat_FromDouble(y); + PyObject* obj_end_x = PyFloat_FromDouble(end_x); + PyObject* obj_end_y = PyFloat_FromDouble(end_y); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "fc", PyString_FromString(fc.c_str())); + PyDict_SetItemString(kwargs, "ec", PyString_FromString(ec.c_str())); + PyDict_SetItemString(kwargs, "head_width", PyFloat_FromDouble(head_width)); + PyDict_SetItemString(kwargs, "head_length", PyFloat_FromDouble(head_length)); + + PyObject* plot_args = PyTuple_New(4); + PyTuple_SetItem(plot_args, 0, obj_x); + PyTuple_SetItem(plot_args, 1, obj_y); + PyTuple_SetItem(plot_args, 2, obj_end_x); + PyTuple_SetItem(plot_args, 3, obj_end_y); + + PyObject* res = + PyObject_Call(detail::_interpreter::get().s_python_function_arrow, plot_args, kwargs); + + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if (res) + Py_DECREF(res); + + return res; +} + template< typename Numeric> bool hist(const std::vector& y, long bins=10,std::string color="b", double alpha=1.0, bool cumulative=false) @@ -1040,6 +1079,39 @@ bool plot(const std::vector& x, const std::vector& y, const return res; } +template +bool contour(const std::vector& x, const std::vector& y, + const std::vector& z, + const std::map& keywords = {}) { + assert(x.size() == y.size() && x.size() == z.size()); + + PyObject* xarray = get_array(x); + PyObject* yarray = get_array(y); + PyObject* zarray = get_array(z); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, zarray); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for (std::map::const_iterator it = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = + PyObject_Call(detail::_interpreter::get().s_python_function_contour, plot_args, kwargs); + + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if (res) + Py_DECREF(res); + + return res; +} + template bool quiver(const std::vector& x, const std::vector& y, const std::vector& u, const std::vector& w, const std::map& keywords = {}) { @@ -1669,6 +1741,38 @@ inline void yticks(const std::vector &ticks, const std::map inline void margins(Numeric margin) +{ + // construct positional args + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(margin)); + + PyObject* res = + PyObject_CallObject(detail::_interpreter::get().s_python_function_margins, args); + if (!res) + throw std::runtime_error("Call to margins() failed."); + + Py_DECREF(args); + Py_DECREF(res); +} + +template inline void margins(Numeric margin_x, Numeric margin_y) +{ + // construct positional args + PyObject* args = PyTuple_New(2); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(margin_x)); + PyTuple_SetItem(args, 1, PyFloat_FromDouble(margin_y)); + + PyObject* res = + PyObject_CallObject(detail::_interpreter::get().s_python_function_margins, args); + if (!res) + throw std::runtime_error("Call to margins() failed."); + + Py_DECREF(args); + Py_DECREF(res); +} + + inline void tick_params(const std::map& keywords, const std::string axis = "both") { detail::_interpreter::get(); @@ -2069,6 +2173,18 @@ inline void clf() { Py_DECREF(res); } +inline void cla() { + detail::_interpreter::get(); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_cla, + detail::_interpreter::get().s_python_empty_tuple); + + if (!res) + throw std::runtime_error("Call to cla() failed."); + + Py_DECREF(res); +} + inline void ion() { detail::_interpreter::get(); From b79ca6dc655f01eac206b84d3c7ffc7cdb268107 Mon Sep 17 00:00:00 2001 From: Tako Hisada Date: Sun, 14 Jun 2020 21:38:22 +0000 Subject: [PATCH 084/110] feat: Add keyword argument support to legend() --- matplotlibcpp.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 7460eb7..6770074 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1565,6 +1565,24 @@ inline void legend() Py_DECREF(res); } +inline void legend(const std::map& keywords) +{ + detail::_interpreter::get(); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + { + PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_legend, detail::_interpreter::get().s_python_empty_tuple, kwargs); + if(!res) throw std::runtime_error("Call to legend() failed."); + + Py_DECREF(kwargs); + Py_DECREF(res); +} + template void ylim(Numeric left, Numeric right) { From 7d0e695409e7029fd52a4653907a167b4cc725b9 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sat, 23 May 2020 19:04:01 +0200 Subject: [PATCH 085/110] include ldflags instead of libs --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1a5a339..67b5ac3 100644 --- a/Makefile +++ b/Makefile @@ -6,10 +6,10 @@ PYTHON_BIN ?= python3 PYTHON_CONFIG := $(PYTHON_BIN)-config PYTHON_INCLUDE ?= $(shell $(PYTHON_CONFIG) --includes) EXTRA_FLAGS := $(PYTHON_INCLUDE) -# NOTE: Since python3.8, the correct invocation is `python3-config --libs --embed`. +# NOTE: Since python3.8, the correct invocation is `python3-config --libs --embed`. # So of course the proper way to get python libs for embedding now is to # invoke that, check if it crashes, and fall back to just `--libs` if it does. -LDFLAGS += $(shell if $(PYTHON_CONFIG) --libs --embed >/dev/null; then $(PYTHON_CONFIG) --libs --embed; else $(PYTHON_CONFIG) --libs; fi) +LDFLAGS += $(shell if $(PYTHON_CONFIG) --ldflags --embed >/dev/null; then $(PYTHON_CONFIG) --ldflags --embed; else $(PYTHON_CONFIG) --ldflags; fi) # Either finds numpy or set -DWITHOUT_NUMPY EXTRA_FLAGS += $(shell $(PYTHON_BIN) $(CURDIR)/numpy_flags.py) From f4b49a35d0bc59bb2fd606b1f7c9bedf9c271e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A9=20BALP?= Date: Sat, 15 Aug 2020 20:02:15 +0000 Subject: [PATCH 086/110] Fix issue lava/matplotlib-cpp#124 --- matplotlibcpp.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 6770074..98f356c 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -153,6 +153,11 @@ struct _interpreter { Py_SetProgramName(name); Py_Initialize(); + wchar_t const *dummy_args[] = {L"Python", NULL}; // const is needed because literals must not be modified + wchar_t const **argv = dummy_args; + int argc = sizeof(dummy_args)/sizeof(dummy_args[0])-1; + PySys_SetArgv(argc, const_cast(argv)); + #ifndef WITHOUT_NUMPY import_numpy(); // initialize numpy C-API #endif From f2bf7a45112d1d9e41cf97ceb645f106e80f6442 Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Thu, 27 Aug 2020 20:50:30 +0200 Subject: [PATCH 087/110] Add missing call to initialize interpreter in 'modern' example --- matplotlibcpp.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 98f356c..111b904 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -2321,6 +2321,8 @@ struct plot_impl template bool operator()(const IterableX& x, const IterableY& y, const std::string& format) { + detail::_interpreter::get(); + // 2-phase lookup for distance, begin, end using std::distance; using std::begin; From 6f841d442aebbd22f80eb70c9e0d1fbc590c7638 Mon Sep 17 00:00:00 2001 From: Andre Furlan Date: Tue, 26 May 2020 15:53:17 -0300 Subject: [PATCH 088/110] Added support to barh plot method --- matplotlibcpp.h | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 111b904..c6762db 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -96,6 +96,7 @@ struct _interpreter { PyObject *s_python_function_text; PyObject *s_python_function_suptitle; PyObject *s_python_function_bar; + PyObject *s_python_function_barh; PyObject *s_python_function_colorbar; PyObject *s_python_function_subplots_adjust; @@ -243,6 +244,7 @@ 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_barh = safe_import(pymod, "barh"); s_python_function_colorbar = PyObject_GetAttrString(pymod, "colorbar"); s_python_function_subplots_adjust = safe_import(pymod,"subplots_adjust"); #ifndef WITHOUT_NUMPY @@ -1010,6 +1012,36 @@ bool bar(const std::vector & y, return bar(x, y, ec, ls, lw, keywords); } + +template +bool barh(const std::vector &x, const std::vector &y, std::string ec = "black", std::string ls = "-", double lw = 1.0, const std::map &keywords = { }) { + PyObject *xarray = detail::get_array(x); + PyObject *yarray = detail::get_array(y); + + PyObject *kwargs = PyDict_New(); + + PyDict_SetItemString(kwargs, "ec", PyString_FromString(ec.c_str())); + PyDict_SetItemString(kwargs, "ls", PyString_FromString(ls.c_str())); + PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); + + for (std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject *plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + + PyObject *res = PyObject_Call(detail::_interpreter::get().s_python_function_barh, plot_args, kwargs); + + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if (res) Py_DECREF(res); + + return res; +} + + inline bool subplots_adjust(const std::map& keywords = {}) { detail::_interpreter::get(); From dfe5a69a950251887f8b8754023e7fc23d8da6fc Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Thu, 27 Aug 2020 21:18:02 +0200 Subject: [PATCH 089/110] Fix whitespace error --- matplotlibcpp.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index c6762db..9b414a1 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1993,10 +1993,10 @@ inline void axvspan(double xmin, double xmax, double ymin = 0., double ymax = 1. PyObject* kwargs = PyDict_New(); for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) { - if (it->first == "linewidth" || it->first == "alpha") - PyDict_SetItemString(kwargs, it->first.c_str(), PyFloat_FromDouble(std::stod(it->second))); - else - PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + if (it->first == "linewidth" || it->first == "alpha") + PyDict_SetItemString(kwargs, it->first.c_str(), PyFloat_FromDouble(std::stod(it->second))); + else + PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); } PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_axvspan, args, kwargs); From b6f58a5c4c0a1430c7ec92e2583b817309650d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=20creepeur=20petit=C3=A8?= Date: Thu, 18 Jun 2020 07:20:46 +0300 Subject: [PATCH 090/110] Suggestion: Give more multithreading flexibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Python interpreter can be constructed by any thread while the destructor is always the main thread. This can lead in some errors with the Python side of things, where some objects want the thread which constructed them to also destroy them. By adding this 'kill' function, a developer can use your library (btw gj! 👍 ) with `std::thread` in order to create a plot async -aka without blocking the main thread- and then close the plot and 'kill' Python interpreter afterwards, without waiting the program to end or having destructor errors. Doing this in order to showcase this more. Small change but not that polished. Let me know about your opinion first and we can fix it 😃. Thank you for your time and this great library ❤️ --- matplotlibcpp.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 9b414a1..714bfd3 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -108,7 +108,17 @@ struct _interpreter { */ static _interpreter& get() { + return interkeeper(false); + } + + static _interpreter& kill() { + return interkeeper(true); + } + + static _interpreter& interkeeper(bool should_kill) { static _interpreter ctx; + if (should_kill) + ctx.~_interpreter(); return ctx; } From 70d508fcb7febc66535ba923eac1b1a4e571e4d1 Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Thu, 27 Aug 2020 21:29:25 +0200 Subject: [PATCH 091/110] Expand comment for new interkeeper() function --- matplotlibcpp.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 714bfd3..93a72be 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -103,8 +103,14 @@ struct _interpreter { /* For now, _interpreter is implemented as a singleton since its currently not possible to have multiple independent embedded python interpreters without patching the python source code - or starting a separate process for each. - http://bytes.com/topic/python/answers/793370-multiple-independent-python-interpreters-c-c-program + or starting a separate process for each. [1] + Furthermore, many python objects expect that they are destructed in the same thread as they + were constructed. [2] So for advanced usage, a `kill()` function is provided so that library + users can manually ensure that the interpreter is constructed and destroyed within the + same thread. + + 1: http://bytes.com/topic/python/answers/793370-multiple-independent-python-interpreters-c-c-program + 2: https://github.com/lava/matplotlib-cpp/pull/202#issue-436220256 */ static _interpreter& get() { @@ -115,6 +121,7 @@ struct _interpreter { return interkeeper(true); } + // Stores the actual singleton object referenced by `get()` and `kill()`. static _interpreter& interkeeper(bool should_kill) { static _interpreter ctx; if (should_kill) From 80bc9cd84da8d40f9b52b838291eb91f00c1c73f Mon Sep 17 00:00:00 2001 From: Joshua Brinsfield Date: Sat, 31 Oct 2020 18:51:05 -0400 Subject: [PATCH 092/110] Add 3D quiver support --- matplotlibcpp.h | 86 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 93a72be..52b2780 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1202,6 +1202,92 @@ bool quiver(const std::vector& x, const std::vector& y, cons return res; } +template +bool quiver(const std::vector& x, const std::vector& y, const std::vector& z, const std::vector& u, const std::vector& w, const std::vector& v, const std::map& keywords = {}) +{ + //set up 3d axes stuff + static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; + if (!mpl_toolkitsmod) { + detail::_interpreter::get(); + + PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); + PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); + if (!mpl_toolkits || !axis3d) { throw std::runtime_error("couldnt create string"); } + + mpl_toolkitsmod = PyImport_Import(mpl_toolkits); + Py_DECREF(mpl_toolkits); + if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } + + axis3dmod = PyImport_Import(axis3d); + Py_DECREF(axis3d); + if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } + } + + //assert sizes match up + assert(x.size() == y.size() && x.size() == u.size() && u.size() == w.size() && x.size() == z.size() && x.size() == v.size() && u.size() == v.size()); + + //set up parameters + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + PyObject* zarray = detail::get_array(z); + PyObject* uarray = detail::get_array(u); + PyObject* warray = detail::get_array(w); + PyObject* varray = detail::get_array(v); + + PyObject* plot_args = PyTuple_New(6); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, zarray); + PyTuple_SetItem(plot_args, 3, uarray); + PyTuple_SetItem(plot_args, 4, warray); + PyTuple_SetItem(plot_args, 5, varray); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + //get figure gca to enable 3d projection + PyObject *fig = + PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, + detail::_interpreter::get().s_python_empty_tuple); + if (!fig) throw std::runtime_error("Call to figure() failed."); + + PyObject *gca_kwargs = PyDict_New(); + PyDict_SetItemString(gca_kwargs, "projection", PyString_FromString("3d")); + + PyObject *gca = PyObject_GetAttrString(fig, "gca"); + if (!gca) throw std::runtime_error("No gca"); + Py_INCREF(gca); + PyObject *axis = PyObject_Call( + gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); + + if (!axis) throw std::runtime_error("No axis"); + Py_INCREF(axis); + Py_DECREF(gca); + Py_DECREF(gca_kwargs); + + //plot our boys bravely, plot them strongly, plot them with a wink and clap + PyObject *plot3 = PyObject_GetAttrString(axis, "quiver"); + if (!plot3) throw std::runtime_error("No 3D line plot"); + Py_INCREF(plot3); + PyObject* res = PyObject_Call( + plot3, plot_args, kwargs); + if (!res) throw std::runtime_error("Failed 3D plot"); + Py_DECREF(plot3); + Py_DECREF(axis); + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if (res) + Py_DECREF(res); + + return res; +} + template bool stem(const std::vector& x, const std::vector& y, const std::string& s = "") { From 9d19657a36c3950de2848113a546406cad8f8f29 Mon Sep 17 00:00:00 2001 From: Ruan Luies Date: Sun, 17 Jan 2021 13:30:12 +0200 Subject: [PATCH 093/110] Add 3D scatter plots, allow more than one 3d plot on the same figure and make rcparams changeable. --- matplotlibcpp.h | 185 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 167 insertions(+), 18 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 52b2780..226a16a 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -99,6 +99,7 @@ struct _interpreter { PyObject *s_python_function_barh; PyObject *s_python_function_colorbar; PyObject *s_python_function_subplots_adjust; + PyObject *s_python_function_rcparams; /* For now, _interpreter is implemented as a singleton since its currently not possible to have @@ -189,6 +190,7 @@ struct _interpreter { } PyObject* matplotlib = PyImport_Import(matplotlibname); + Py_DECREF(matplotlibname); if (!matplotlib) { PyErr_Print(); @@ -201,6 +203,8 @@ struct _interpreter { PyObject_CallMethod(matplotlib, const_cast("use"), const_cast("s"), s_backend.c_str()); } + + PyObject* pymod = PyImport_Import(pyplotname); Py_DECREF(pyplotname); if (!pymod) { throw std::runtime_error("Error loading module matplotlib.pyplot!"); } @@ -264,6 +268,7 @@ struct _interpreter { s_python_function_barh = safe_import(pymod, "barh"); s_python_function_colorbar = PyObject_GetAttrString(pymod, "colorbar"); s_python_function_subplots_adjust = safe_import(pymod,"subplots_adjust"); + s_python_function_rcparams = PyObject_GetAttrString(pymod, "rcParams"); #ifndef WITHOUT_NUMPY s_python_function_imshow = safe_import(pymod, "imshow"); #endif @@ -464,6 +469,7 @@ template void plot_surface(const std::vector<::std::vector> &x, const std::vector<::std::vector> &y, const std::vector<::std::vector> &z, + const long fig_number=0, const std::map &keywords = std::map()) { @@ -516,14 +522,29 @@ void plot_surface(const std::vector<::std::vector> &x, for (std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), - PyString_FromString(it->second.c_str())); + if (it->first == "linewidth" || it->first == "alpha") { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyFloat_FromDouble(std::stod(it->second))); + } else { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } } - - PyObject *fig = - PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, - detail::_interpreter::get().s_python_empty_tuple); + PyObject *fig_args = PyTuple_New(1); + PyObject* fig = nullptr; + PyTuple_SetItem(fig_args, 0, PyLong_FromLong(fig_number)); + PyObject *fig_exists = + PyObject_CallObject( + detail::_interpreter::get().s_python_function_fignum_exists, fig_args); + if (!PyObject_IsTrue(fig_exists)) { + fig = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, + detail::_interpreter::get().s_python_empty_tuple); + } else { + fig = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, + fig_args); + } + Py_DECREF(fig_exists); if (!fig) throw std::runtime_error("Call to figure() failed."); PyObject *gca_kwargs = PyDict_New(); @@ -559,6 +580,7 @@ template void plot3(const std::vector &x, const std::vector &y, const std::vector &z, + const long fig_number=0, const std::map &keywords = std::map()) { @@ -607,9 +629,18 @@ void plot3(const std::vector &x, PyString_FromString(it->second.c_str())); } - PyObject *fig = - PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, - detail::_interpreter::get().s_python_empty_tuple); + PyObject *fig_args = PyTuple_New(1); + PyObject* fig = nullptr; + PyTuple_SetItem(fig_args, 0, PyLong_FromLong(fig_number)); + PyObject *fig_exists = + PyObject_CallObject(detail::_interpreter::get().s_python_function_fignum_exists, fig_args); + if (!PyObject_IsTrue(fig_exists)) { + fig = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, + detail::_interpreter::get().s_python_empty_tuple); + } else { + fig = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, + fig_args); + } if (!fig) throw std::runtime_error("Call to figure() failed."); PyObject *gca_kwargs = PyDict_New(); @@ -911,6 +942,103 @@ bool scatter(const std::vector& x, return res; } +template +bool scatter(const std::vector& x, + const std::vector& y, + const std::vector& z, + const double s=1.0, // The marker size in points**2 + const long fig_number=0, + const std::map & keywords = {}) { + detail::_interpreter::get(); + + // Same as with plot_surface: We lazily load the modules here the first time + // this function is called because I'm not sure that we can assume "matplotlib + // installed" implies "mpl_toolkits installed" on all platforms, and we don't + // want to require it for people who don't need 3d plots. + static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; + if (!mpl_toolkitsmod) { + detail::_interpreter::get(); + + PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); + PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); + if (!mpl_toolkits || !axis3d) { throw std::runtime_error("couldnt create string"); } + + mpl_toolkitsmod = PyImport_Import(mpl_toolkits); + Py_DECREF(mpl_toolkits); + if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } + + axis3dmod = PyImport_Import(axis3d); + Py_DECREF(axis3d); + if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } + } + + assert(x.size() == y.size()); + assert(y.size() == z.size()); + + PyObject *xarray = detail::get_array(x); + PyObject *yarray = detail::get_array(y); + PyObject *zarray = detail::get_array(z); + + // construct positional args + PyObject *args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + PyTuple_SetItem(args, 2, zarray); + + // Build up the kw args. + PyObject *kwargs = PyDict_New(); + + for (std::map::const_iterator it = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } + PyObject *fig_args = PyTuple_New(1); + PyObject* fig = nullptr; + PyTuple_SetItem(fig_args, 0, PyLong_FromLong(fig_number)); + PyObject *fig_exists = + PyObject_CallObject(detail::_interpreter::get().s_python_function_fignum_exists, fig_args); + if (!PyObject_IsTrue(fig_exists)) { + fig = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, + detail::_interpreter::get().s_python_empty_tuple); + } else { + fig = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, + fig_args); + } + Py_DECREF(fig_exists); + if (!fig) throw std::runtime_error("Call to figure() failed."); + + PyObject *gca_kwargs = PyDict_New(); + PyDict_SetItemString(gca_kwargs, "projection", PyString_FromString("3d")); + + PyObject *gca = PyObject_GetAttrString(fig, "gca"); + if (!gca) throw std::runtime_error("No gca"); + Py_INCREF(gca); + PyObject *axis = PyObject_Call( + gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); + + if (!axis) throw std::runtime_error("No axis"); + Py_INCREF(axis); + + Py_DECREF(gca); + Py_DECREF(gca_kwargs); + + PyObject *plot3 = PyObject_GetAttrString(axis, "scatter"); + if (!plot3) throw std::runtime_error("No 3D line plot"); + Py_INCREF(plot3); + PyObject *res = PyObject_Call(plot3, args, kwargs); + if (!res) throw std::runtime_error("Failed 3D line plot"); + Py_DECREF(plot3); + + Py_DECREF(axis); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(fig); + if (res) Py_DECREF(res); + return res; + +} + template bool boxplot(const std::vector>& data, const std::vector& labels = {}, @@ -1139,9 +1267,9 @@ bool contour(const std::vector& x, const std::vector& y, const std::map& keywords = {}) { assert(x.size() == y.size() && x.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); PyObject* plot_args = PyTuple_New(3); PyTuple_SetItem(plot_args, 0, xarray); @@ -2094,12 +2222,14 @@ inline void axvspan(double xmin, double xmax, double ymin = 0., double ymax = 1. // construct keyword args PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) - { - if (it->first == "linewidth" || it->first == "alpha") - PyDict_SetItemString(kwargs, it->first.c_str(), PyFloat_FromDouble(std::stod(it->second))); - else - PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + for (auto it = keywords.begin(); it != keywords.end(); ++it) { + if (it->first == "linewidth" || it->first == "alpha") { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyFloat_FromDouble(std::stod(it->second))); + } else { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } } PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_axvspan, args, kwargs); @@ -2319,6 +2449,25 @@ inline void save(const std::string& filename) Py_DECREF(res); } +inline void rcparams(const std::map& keywords = {}) { + detail::_interpreter::get(); + PyObject* args = PyTuple_New(0); + PyObject* kwargs = PyDict_New(); + for (auto it = keywords.begin(); it != keywords.end(); ++it) { + if ("text.usetex" == it->first) + PyDict_SetItemString(kwargs, it->first.c_str(), PyLong_FromLong(std::stoi(it->second.c_str()))); + else PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + } + + PyObject * update = PyObject_GetAttrString(detail::_interpreter::get().s_python_function_rcparams, "update"); + PyObject * res = PyObject_Call(update, args, kwargs); + if(!res) throw std::runtime_error("Call to rcParams.update() failed."); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(update); + Py_DECREF(res); +} + inline void clf() { detail::_interpreter::get(); From d1b7c72be8b9c3cb28bfc565a0975117414be3e5 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 28 Aug 2020 19:43:50 +0300 Subject: [PATCH 094/110] Add #include for std::stod See https://en.cppreference.com/w/cpp/string/basic_string/stof for details. --- matplotlibcpp.h | 1 + 1 file changed, 1 insertion(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 226a16a..b3d57f7 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -13,6 +13,7 @@ #include #include // requires c++11 support #include +#include // std::stod #ifndef WITHOUT_NUMPY # define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION From 2bf4f26d727cc164e309122fb29f3fb733c087f2 Mon Sep 17 00:00:00 2001 From: William Leong Date: Fri, 2 Oct 2020 15:48:08 +0800 Subject: [PATCH 095/110] Fix #221 and #225 --- matplotlibcpp.h | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index b3d57f7..18d83b8 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -176,7 +176,12 @@ struct _interpreter { wchar_t const *dummy_args[] = {L"Python", NULL}; // const is needed because literals must not be modified wchar_t const **argv = dummy_args; int argc = sizeof(dummy_args)/sizeof(dummy_args[0])-1; + +#if PY_MAJOR_VERSION >= 3 PySys_SetArgv(argc, const_cast(argv)); +#else + PySys_SetArgv(argc, (char **)(argv)); +#endif #ifndef WITHOUT_NUMPY import_numpy(); // initialize numpy C-API @@ -362,7 +367,7 @@ PyObject* get_array(const std::vector& v) PyArray_UpdateFlags(reinterpret_cast(varray), NPY_ARRAY_OWNDATA); return varray; } - + PyObject* varray = PyArray_SimpleNewFromData(1, &vsize, type, (void*)(v.data())); return varray; } @@ -429,7 +434,7 @@ PyObject* get_listlist(const std::vector>& ll) } // 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) @@ -587,9 +592,9 @@ void plot3(const std::vector &x, { detail::_interpreter::get(); - // Same as with plot_surface: We lazily load the modules here the first time - // this function is called because I'm not sure that we can assume "matplotlib - // installed" implies "mpl_toolkits installed" on all platforms, and we don't + // Same as with plot_surface: We lazily load the modules here the first time + // this function is called because I'm not sure that we can assume "matplotlib + // installed" implies "mpl_toolkits installed" on all platforms, and we don't // want to require it for people who don't need 3d plots. static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; if (!mpl_toolkitsmod) { @@ -1849,7 +1854,7 @@ inline void legend(const std::map& keywords) if(!res) throw std::runtime_error("Call to legend() failed."); Py_DECREF(kwargs); - Py_DECREF(res); + Py_DECREF(res); } template @@ -2089,7 +2094,7 @@ inline void tick_params(const std::map& keywords, cons inline void subplot(long nrows, long ncols, long plot_number) { detail::_interpreter::get(); - + // construct positional args PyObject* args = PyTuple_New(3); PyTuple_SetItem(args, 0, PyFloat_FromDouble(nrows)); @@ -2154,7 +2159,7 @@ inline void title(const std::string &titlestr, const std::map &keywords = {}) { detail::_interpreter::get(); - + PyObject* pysuptitlestr = PyString_FromString(suptitlestr.c_str()); PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, pysuptitlestr); @@ -2286,9 +2291,9 @@ inline void set_zlabel(const std::string &str, const std::map Date: Fri, 27 Nov 2020 17:23:34 +0100 Subject: [PATCH 096/110] Adding possibility to choose dpi when saving --- matplotlibcpp.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 18d83b8..a7318f5 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -2439,7 +2439,7 @@ inline void pause(Numeric interval) Py_DECREF(res); } -inline void save(const std::string& filename) +inline void save(const std::string& filename, const int dpi=0) { detail::_interpreter::get(); @@ -2448,10 +2448,18 @@ inline void save(const std::string& filename) PyObject* args = PyTuple_New(1); PyTuple_SetItem(args, 0, pyfilename); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_save, args); + PyObject* kwargs = PyDict_New(); + + if(dpi > 0) + { + PyDict_SetItemString(kwargs, "dpi", PyLong_FromLong(dpi)); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_save, args, kwargs); if (!res) throw std::runtime_error("Call to save() failed."); Py_DECREF(args); + Py_DECREF(kwargs); Py_DECREF(res); } From 490fa9cda0b8ce39dbb900836c4fb1c0296ac7c0 Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Fri, 19 Feb 2021 14:12:14 +0000 Subject: [PATCH 097/110] Fix memory leaks in xlim() and ylim() Memory is allocated with new and delete is never called. Use a std::array instead, so no memory will be allocated. --- matplotlibcpp.h | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index a7318f5..643a120 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1896,43 +1896,33 @@ void xlim(Numeric left, Numeric right) } -inline double* xlim() +inline std::array xlim() { - detail::_interpreter::get(); - PyObject* args = PyTuple_New(0); PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_xlim, args); - PyObject* left = PyTuple_GetItem(res,0); - PyObject* right = PyTuple_GetItem(res,1); - - double* arr = new double[2]; - arr[0] = PyFloat_AsDouble(left); - arr[1] = PyFloat_AsDouble(right); if(!res) throw std::runtime_error("Call to xlim() failed."); Py_DECREF(res); - return arr; + + PyObject* left = PyTuple_GetItem(res,0); + PyObject* right = PyTuple_GetItem(res,1); + return { PyFloat_AsDouble(left), PyFloat_AsDouble(right) }; } -inline double* ylim() +inline std::array ylim() { - detail::_interpreter::get(); - PyObject* args = PyTuple_New(0); PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_ylim, args); - PyObject* left = PyTuple_GetItem(res,0); - PyObject* right = PyTuple_GetItem(res,1); - - double* arr = new double[2]; - arr[0] = PyFloat_AsDouble(left); - arr[1] = PyFloat_AsDouble(right); if(!res) throw std::runtime_error("Call to ylim() failed."); Py_DECREF(res); - return arr; + + PyObject* left = PyTuple_GetItem(res,0); + PyObject* right = PyTuple_GetItem(res,1); + return { PyFloat_AsDouble(left), PyFloat_AsDouble(right) }; } template From 08ff087d5ec3bfb5066335e7300e40e56b09aefb Mon Sep 17 00:00:00 2001 From: Florian Fervers Date: Wed, 10 Feb 2021 17:37:13 +0100 Subject: [PATCH 098/110] Add modern cmake support --- CMakeLists.txt | 125 ++++++++++++++++++++++++++++ cmake/matplotlib_cppConfig.cmake.in | 7 ++ 2 files changed, 132 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/matplotlib_cppConfig.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4e1ef89 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,125 @@ +cmake_minimum_required(VERSION 3.8 FATAL_ERROR) +project(matplotlib_cpp LANGUAGES CXX) + +include(GNUInstallDirs) +set(PACKAGE_NAME matplotlib_cpp) +set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/${PACKAGE_NAME}/cmake) + + +# Library target +add_library(matplotlib_cpp INTERFACE) +target_include_directories(matplotlib_cpp + INTERFACE + $ + $ +) +target_compile_features(matplotlib_cpp INTERFACE + cxx_std_11 +) +find_package(Python3 COMPONENTS Interpreter Development REQUIRED) +target_link_libraries(matplotlib_cpp INTERFACE + Python3::Python + Python3::Module +) +find_package(Python3 COMPONENTS NumPy) +if(Python3_NumPy_FOUND) + target_link_libraries(matplotlib_cpp INTERFACE + Python3::NumPy + ) +else() + target_compile_definitions(matplotlib_cpp INTERFACE WITHOUT_NUMPY) +endif() +install( + TARGETS matplotlib_cpp + EXPORT install_targets +) + + +# Examples +add_executable(minimal examples/minimal.cpp) +target_link_libraries(minimal PRIVATE matplotlib_cpp) +set_target_properties(minimal PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(basic examples/basic.cpp) +target_link_libraries(basic PRIVATE matplotlib_cpp) +set_target_properties(basic PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(modern examples/modern.cpp) +target_link_libraries(modern PRIVATE matplotlib_cpp) +set_target_properties(modern PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(animation examples/animation.cpp) +target_link_libraries(animation PRIVATE matplotlib_cpp) +set_target_properties(animation PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(nonblock examples/nonblock.cpp) +target_link_libraries(nonblock PRIVATE matplotlib_cpp) +set_target_properties(nonblock PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(xkcd examples/xkcd.cpp) +target_link_libraries(xkcd PRIVATE matplotlib_cpp) +set_target_properties(xkcd PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(bar examples/bar.cpp) +target_link_libraries(bar PRIVATE matplotlib_cpp) +set_target_properties(bar PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(fill_inbetween examples/fill_inbetween.cpp) +target_link_libraries(fill_inbetween PRIVATE matplotlib_cpp) +set_target_properties(fill_inbetween PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(fill examples/fill.cpp) +target_link_libraries(fill PRIVATE matplotlib_cpp) +set_target_properties(fill PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(update examples/update.cpp) +target_link_libraries(update PRIVATE matplotlib_cpp) +set_target_properties(update PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(subplot2grid examples/subplot2grid.cpp) +target_link_libraries(subplot2grid PRIVATE matplotlib_cpp) +set_target_properties(subplot2grid PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(lines3d examples/lines3d.cpp) +target_link_libraries(lines3d PRIVATE matplotlib_cpp) +set_target_properties(lines3d PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +if(Python3_NumPy_FOUND) + add_executable(surface examples/surface.cpp) + target_link_libraries(surface PRIVATE matplotlib_cpp) + set_target_properties(surface PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + + add_executable(colorbar examples/colorbar.cpp) + target_link_libraries(colorbar PRIVATE matplotlib_cpp) + set_target_properties(colorbar PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +endif() + + +# Install headers +install(FILES + "${PROJECT_SOURCE_DIR}/matplotlibcpp.h" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + +# Install targets file +install(EXPORT install_targets + FILE + ${PACKAGE_NAME}Targets.cmake + NAMESPACE + ${PACKAGE_NAME}:: + DESTINATION + ${INSTALL_CONFIGDIR} +) + + +# Install matplotlib_cppConfig.cmake +include(CMakePackageConfigHelpers) +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PACKAGE_NAME}Config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}Config.cmake + INSTALL_DESTINATION ${INSTALL_CONFIGDIR} +) +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}Config.cmake + DESTINATION ${INSTALL_CONFIGDIR} +) diff --git a/cmake/matplotlib_cppConfig.cmake.in b/cmake/matplotlib_cppConfig.cmake.in new file mode 100644 index 0000000..1793f29 --- /dev/null +++ b/cmake/matplotlib_cppConfig.cmake.in @@ -0,0 +1,7 @@ +get_filename_component(matplotlib_cpp_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) + +if(NOT TARGET matplotlib_cpp::matplotlib_cpp) + find_package(Python3 COMPONENTS Interpreter Development REQUIRED) + find_package(Python3 COMPONENTS NumPy) + include("${matplotlib_cpp_CMAKE_DIR}/matplotlib_cppTargets.cmake") +endif() From 3d3f9da65108e17c19799805786bb81e0776ea0f Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Fri, 26 Mar 2021 10:30:33 +0100 Subject: [PATCH 099/110] Unbreak examples by moving 'fig_number' paramter to the end of the list --- matplotlibcpp.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 643a120..9363b71 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -475,9 +475,9 @@ template void plot_surface(const std::vector<::std::vector> &x, const std::vector<::std::vector> &y, const std::vector<::std::vector> &z, - const long fig_number=0, const std::map &keywords = - std::map()) + std::map(), + const long fig_number=0) { detail::_interpreter::get(); @@ -586,9 +586,9 @@ template void plot3(const std::vector &x, const std::vector &y, const std::vector &z, - const long fig_number=0, const std::map &keywords = - std::map()) + std::map(), + const long fig_number=0) { detail::_interpreter::get(); @@ -953,8 +953,8 @@ bool scatter(const std::vector& x, const std::vector& y, const std::vector& z, const double s=1.0, // The marker size in points**2 - const long fig_number=0, - const std::map & keywords = {}) { + const std::map & keywords = {}, + const long fig_number=0) { detail::_interpreter::get(); // Same as with plot_surface: We lazily load the modules here the first time From cab80f33cd137dca50b98fe4ccb830d7e657721d Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Fri, 26 Mar 2021 10:40:39 +0100 Subject: [PATCH 100/110] Remove obsoleted build systems After the switch to cmake, we don't need the hand-written Makefile nor the contrib/ version of the cmake file. --- Makefile | 41 --------------------------------- README.md | 52 +++++++++++++++--------------------------- contrib/CMakeLists.txt | 26 --------------------- numpy_flags.py | 12 ---------- 4 files changed, 18 insertions(+), 113 deletions(-) delete mode 100644 Makefile delete mode 100644 contrib/CMakeLists.txt delete mode 100644 numpy_flags.py diff --git a/Makefile b/Makefile deleted file mode 100644 index 67b5ac3..0000000 --- a/Makefile +++ /dev/null @@ -1,41 +0,0 @@ -# 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 ?= python3 -PYTHON_CONFIG := $(PYTHON_BIN)-config -PYTHON_INCLUDE ?= $(shell $(PYTHON_CONFIG) --includes) -EXTRA_FLAGS := $(PYTHON_INCLUDE) -# NOTE: Since python3.8, the correct invocation is `python3-config --libs --embed`. -# So of course the proper way to get python libs for embedding now is to -# invoke that, check if it crashes, and fall back to just `--libs` if it does. -LDFLAGS += $(shell if $(PYTHON_CONFIG) --ldflags --embed >/dev/null; then $(PYTHON_CONFIG) --ldflags --embed; else $(PYTHON_CONFIG) --ldflags; fi) - -# Either finds numpy or set -DWITHOUT_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 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)) - -.PHONY: 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 matplotlibcpp.h - mkdir -p examples/build - $(CXX) -o $@ $< $(EXTRA_FLAGS) $(CXXFLAGS) $(LDFLAGS) - -clean: - rm -f ${EXAMPLE_TARGETS} diff --git a/README.md b/README.md index 61ffef4..0f8479f 100644 --- a/README.md +++ b/README.md @@ -202,39 +202,34 @@ If, for some reason, you're unable to get a working installation of numpy on you you can define the macro `WITHOUT_NUMPY` before including the header file to erase this dependency. -The C++-part of the library consists of the single header file `matplotlibcpp.h` which can be placed -anywhere. +The C++-part of the library consists of the single header file `matplotlibcpp.h` which +can be placed anywhere. -Since a python interpreter is opened internally, it is necessary to link against `libpython` in order -to user matplotlib-cpp. Most versions should work, although `libpython2.7` and `libpython3.6` are -probably the most regularly testedr. +Since a python interpreter is opened internally, it is necessary to link +against `libpython` in order to user matplotlib-cpp. Most versions should +work, although python likes to randomly break compatibility from time to time +so some caution is advised when using the bleeding edge. # CMake -If you prefer to use CMake as build system, you will want to add something like this to your -CMakeLists.txt: +The C++ code is compatible to both python2 and python3. However, the `CMakeLists.txt` +file is currently set up to use python3 by default, so if python2 is required this +has to be changed manually. (a PR that adds a cmake option for this would be highly +welcomed) -**Recommended way (since CMake 3.12):** +**NOTE**: By design (of python), only a single python interpreter can be created per +process. When using this library, *no other* library that is spawning a python +interpreter internally can be used. -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). +To compile the code without using cmake, the compiler invocation should look like +this: -NumPy is optional here, delete it from cmake script, if you don't need it. + g++ example.cpp -I/usr/include/python2.7 -lpython2.7 -```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}) -target_link_libraries(myproject ${PYTHON_LIBRARIES}) -``` +This can also be used for linking against a custom build of python + g++ example.cpp -I/usr/local/include/fancy-python4 -L/usr/local/lib -lfancy-python4 # Vcpkg @@ -258,17 +253,6 @@ Note that support for c++98 was dropped more or less accidentally, so if you hav with an ancient compiler and still want to enjoy the latest additional features, I'd probably merge a PR that restores support. -# Python 3 - -This library supports both python2 and python3 (although the python3 support is probably far less tested, -so it is recommended to prefer python2.7). To switch the used python version, simply change -the compiler flags accordingly. - - g++ example.cpp -I/usr/include/python3.6 -lpython3.6 - -The same technique can be used for linking against a custom build of python - - g++ example.cpp -I/usr/local/include/fancy-python4 -L/usr/local/lib -lfancy-python4 Why? diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt deleted file mode 100644 index edb40b1..0000000 --- a/contrib/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -cmake_minimum_required(VERSION 3.7) -project (MatplotlibCPP_Test) - -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -include_directories(${PYTHONHOME}/include) -include_directories(${PYTHONHOME}/Lib/site-packages/numpy/core/include) -link_directories(${PYTHONHOME}/libs) - -add_definitions(-DMATPLOTLIBCPP_PYTHON_HEADER=Python.h) - -# message(STATUS "*** dump start cmake variables ***") -# get_cmake_property(_variableNames VARIABLES) -# foreach(_variableName ${_variableNames}) -# message(STATUS "${_variableName}=${${_variableName}}") -# endforeach() -# message(STATUS "*** dump end ***") - -add_executable(minimal ${CMAKE_CURRENT_SOURCE_DIR}/../examples/minimal.cpp) -add_executable(basic ${CMAKE_CURRENT_SOURCE_DIR}/../examples/basic.cpp) -add_executable(modern ${CMAKE_CURRENT_SOURCE_DIR}/../examples/modern.cpp) -add_executable(animation ${CMAKE_CURRENT_SOURCE_DIR}/../examples/animation.cpp) -add_executable(nonblock ${CMAKE_CURRENT_SOURCE_DIR}/../examples/nonblock.cpp) -add_executable(xkcd ${CMAKE_CURRENT_SOURCE_DIR}/../examples/xkcd.cpp) -add_executable(bar ${CMAKE_CURRENT_SOURCE_DIR}/../examples/bar.cpp) diff --git a/numpy_flags.py b/numpy_flags.py deleted file mode 100644 index 56fd95c..0000000 --- a/numpy_flags.py +++ /dev/null @@ -1,12 +0,0 @@ -from os import path - -try: - from numpy import __file__ as numpyloc - - # Get numpy directory - numpy_dir = path.dirname(numpyloc) - - # Print the result of joining this to core and include - print("-I" + path.join(numpy_dir, "core", "include")) -except: - print("-DWITHOUT_NUMPY") From 1875696c55c1ae0ae93546f738fbf0047ffa6190 Mon Sep 17 00:00:00 2001 From: Pierre Narvor Date: Wed, 31 Mar 2021 15:16:49 +0200 Subject: [PATCH 101/110] Added 'set_aspect' and set_aspect_equal function --- examples/modern.cpp | 3 +++ matplotlibcpp.h | 56 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/examples/modern.cpp b/examples/modern.cpp index a8aa0c7..871ef2b 100644 --- a/examples/modern.cpp +++ b/examples/modern.cpp @@ -24,6 +24,9 @@ int main() // y must either be callable (providing operator() const) or iterable. plt::plot(x, y, "r-", x, [](double d) { return 12.5+abs(sin(d)); }, "k-"); + //plt::set_aspect(0.5); + plt::set_aspect_equal(); + // show plots plt::show(); diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 9363b71..a151d6f 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1857,6 +1857,62 @@ inline void legend(const std::map& keywords) Py_DECREF(res); } +template +inline void set_aspect(Numeric ratio) +{ + detail::_interpreter::get(); + + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(ratio)); + PyObject* kwargs = PyDict_New(); + + 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 *set_aspect = PyObject_GetAttrString(ax, "set_aspect"); + if (!set_aspect) throw std::runtime_error("Attribute set_aspect not found."); + Py_INCREF(set_aspect); + + PyObject *res = PyObject_Call(set_aspect, args, kwargs); + if (!res) throw std::runtime_error("Call to set_aspect() failed."); + Py_DECREF(set_aspect); + + Py_DECREF(ax); + Py_DECREF(args); + Py_DECREF(kwargs); +} + +inline void set_aspect_equal() +{ + // expect ratio == "equal". Leaving error handling to matplotlib. + detail::_interpreter::get(); + + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyString_FromString("equal")); + PyObject* kwargs = PyDict_New(); + + 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 *set_aspect = PyObject_GetAttrString(ax, "set_aspect"); + if (!set_aspect) throw std::runtime_error("Attribute set_aspect not found."); + Py_INCREF(set_aspect); + + PyObject *res = PyObject_Call(set_aspect, args, kwargs); + if (!res) throw std::runtime_error("Call to set_aspect() failed."); + Py_DECREF(set_aspect); + + Py_DECREF(ax); + Py_DECREF(args); + Py_DECREF(kwargs); +} + template void ylim(Numeric left, Numeric right) { From ec3745302a8295e3d7b28f910fb1a39936886a40 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sat, 23 May 2020 17:10:42 +0200 Subject: [PATCH 102/110] add axhline --- matplotlibcpp.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index a151d6f..f6365cb 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -75,6 +75,7 @@ struct _interpreter { PyObject *s_python_function_ylim; PyObject *s_python_function_title; PyObject *s_python_function_axis; + PyObject *s_python_function_axhline; PyObject *s_python_function_axvline; PyObject *s_python_function_axvspan; PyObject *s_python_function_xlabel; @@ -247,6 +248,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_axhline = safe_import(pymod, "axhline"); s_python_function_axvline = safe_import(pymod, "axvline"); s_python_function_axvspan = safe_import(pymod, "axvspan"); s_python_function_xlabel = safe_import(pymod, "xlabel"); @@ -2238,6 +2240,31 @@ inline void axis(const std::string &axisstr) Py_DECREF(res); } +inline void axhline(double y, double xmin = 0., double xmax = 1., const std::map& keywords = std::map()) +{ + detail::_interpreter::get(); + + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(y)); + PyTuple_SetItem(args, 1, PyFloat_FromDouble(xmin)); + PyTuple_SetItem(args, 2, PyFloat_FromDouble(xmax)); + + // 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_axhline, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + + if(res) Py_DECREF(res); +} + inline void axvline(double x, double ymin = 0., double ymax = 1., const std::map& keywords = std::map()) { detail::_interpreter::get(); From bbfb240ba80e9cb890a0c4dc259552ee8b235854 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sat, 23 May 2020 17:34:06 +0200 Subject: [PATCH 103/110] add contour plot --- examples/contour.cpp | 24 ++++++++++++++++++++ matplotlibcpp.h | 54 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 examples/contour.cpp diff --git a/examples/contour.cpp b/examples/contour.cpp new file mode 100644 index 0000000..9289d0a --- /dev/null +++ b/examples/contour.cpp @@ -0,0 +1,24 @@ +#include "../matplotlibcpp.h" + +#include + +namespace plt = matplotlibcpp; + +int main() +{ + std::vector> x, y, z; + for (double i = -5; i <= 5; i += 0.25) { + std::vector x_row, y_row, z_row; + for (double j = -5; j <= 5; j += 0.25) { + x_row.push_back(i); + y_row.push_back(j); + z_row.push_back(::std::sin(::std::hypot(i, j))); + } + x.push_back(x_row); + y.push_back(y_row); + z.push_back(z_row); + } + + plt::contour(x, y, z); + plt::show(); +} diff --git a/matplotlibcpp.h b/matplotlibcpp.h index f6365cb..abf0284 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -103,7 +103,6 @@ struct _interpreter { PyObject *s_python_function_subplots_adjust; PyObject *s_python_function_rcparams; - /* For now, _interpreter is implemented as a singleton since its currently not possible to have multiple independent embedded python interpreters without patching the python source code or starting a separate process for each. [1] @@ -245,6 +244,7 @@ struct _interpreter { s_python_function_subplot = safe_import(pymod, "subplot"); s_python_function_subplot2grid = safe_import(pymod, "subplot2grid"); s_python_function_legend = safe_import(pymod, "legend"); + s_python_function_xlim = safe_import(pymod, "xlim"); s_python_function_ylim = safe_import(pymod, "ylim"); s_python_function_title = safe_import(pymod, "title"); s_python_function_axis = safe_import(pymod, "axis"); @@ -259,7 +259,6 @@ struct _interpreter { s_python_function_margins = safe_import(pymod, "margins"); s_python_function_tick_params = safe_import(pymod, "tick_params"); s_python_function_grid = safe_import(pymod, "grid"); - s_python_function_xlim = safe_import(pymod, "xlim"); s_python_function_ion = safe_import(pymod, "ion"); s_python_function_ginput = safe_import(pymod, "ginput"); s_python_function_save = safe_import(pylabmod, "savefig"); @@ -349,10 +348,10 @@ template <> struct select_npy_type { const static NPY_TYPES type = NPY // Sanity checks; comment them out or change the numpy type below if you're compiling on // a platform where they don't apply -static_assert(sizeof(long long) == 8); -template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT64; }; -static_assert(sizeof(unsigned long long) == 8); -template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; +// static_assert(sizeof(long long) == 8); +// template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT64; }; +// static_assert(sizeof(unsigned long long) == 8); +// template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; // TODO: add int, long, etc. template @@ -582,6 +581,49 @@ void plot_surface(const std::vector<::std::vector> &x, Py_DECREF(kwargs); if (res) Py_DECREF(res); } + +template +void contour(const std::vector<::std::vector> &x, + const std::vector<::std::vector> &y, + const std::vector<::std::vector> &z, + const std::map &keywords = {}) +{ + detail::_interpreter::get(); + + // using numpy arrays + PyObject *xarray = detail::get_2darray(x); + PyObject *yarray = detail::get_2darray(y); + PyObject *zarray = detail::get_2darray(z); + + // construct positional args + PyObject *args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + PyTuple_SetItem(args, 2, zarray); + + // Build up the kw args. + PyObject *kwargs = PyDict_New(); + + PyObject *python_colormap_coolwarm = PyObject_GetAttrString( + detail::_interpreter::get().s_python_colormap, "coolwarm"); + + PyDict_SetItemString(kwargs, "cmap", python_colormap_coolwarm); + + for (std::map::const_iterator it = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } + + PyObject *res = PyObject_Call(detail::_interpreter::get().s_python_function_contour, args, kwargs); + if (!res) + throw std::runtime_error("failed contour"); + + Py_DECREF(args); + Py_DECREF(kwargs); + if (res) + Py_DECREF(res); +} #endif // WITHOUT_NUMPY template From 9ff7a4b29db0ef27da0136a9e6930d385b93fe50 Mon Sep 17 00:00:00 2001 From: Cryoris Date: Sat, 23 May 2020 18:40:22 +0200 Subject: [PATCH 104/110] add spy --- examples/spy.cpp | 29 +++++++++++++++++++++++++++++ matplotlibcpp.h | 45 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 examples/spy.cpp diff --git a/examples/spy.cpp b/examples/spy.cpp new file mode 100644 index 0000000..80bd544 --- /dev/null +++ b/examples/spy.cpp @@ -0,0 +1,29 @@ +#import +#import +#import "../matplotlibcpp.h" + +namespace plt = matplotlibcpp; + +int main() +{ + const int n = 20; + std::vector> matrix; + + for (int i = 0; i < n; ++i) { + std::vector row; + for (int j = 0; j < n; ++j) { + if (i == j) + row.push_back(-2); + else if (j == i - 1 || j == i + 1) + row.push_back(1); + else + row.push_back(0); + } + matrix.push_back(row); + } + + plt::spy(matrix, 5, {{"marker", "o"}}); + plt::show(); + + return 0; +} \ No newline at end of file diff --git a/matplotlibcpp.h b/matplotlibcpp.h index abf0284..9ad7faa 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -102,6 +102,7 @@ struct _interpreter { PyObject *s_python_function_colorbar; PyObject *s_python_function_subplots_adjust; PyObject *s_python_function_rcparams; + PyObject *s_python_function_spy; /* For now, _interpreter is implemented as a singleton since its currently not possible to have multiple independent embedded python interpreters without patching the python source code @@ -276,6 +277,7 @@ struct _interpreter { s_python_function_colorbar = PyObject_GetAttrString(pymod, "colorbar"); s_python_function_subplots_adjust = safe_import(pymod,"subplots_adjust"); s_python_function_rcparams = PyObject_GetAttrString(pymod, "rcParams"); + s_python_function_spy = PyObject_GetAttrString(pymod, "spy"); #ifndef WITHOUT_NUMPY s_python_function_imshow = safe_import(pymod, "imshow"); #endif @@ -348,11 +350,11 @@ template <> struct select_npy_type { const static NPY_TYPES type = NPY // Sanity checks; comment them out or change the numpy type below if you're compiling on // a platform where they don't apply -// static_assert(sizeof(long long) == 8); -// template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT64; }; -// static_assert(sizeof(unsigned long long) == 8); -// template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; -// TODO: add int, long, etc. +static_assert(sizeof(long long) == 8); +template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT64; }; +static_assert(sizeof(unsigned long long) == 8); +template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; +TODO: add int, long, etc. template PyObject* get_array(const std::vector& v) @@ -621,8 +623,37 @@ void contour(const std::vector<::std::vector> &x, Py_DECREF(args); Py_DECREF(kwargs); - if (res) - Py_DECREF(res); + if (res) Py_DECREF(res); +} + +template +void spy(const std::vector<::std::vector> &x, + const double markersize = -1, // -1 for default matplotlib size + const std::map &keywords = {}) +{ + detail::_interpreter::get(); + + PyObject *xarray = detail::get_2darray(x); + + PyObject *kwargs = PyDict_New(); + if (markersize != -1) { + PyDict_SetItemString(kwargs, "markersize", PyFloat_FromDouble(markersize)); + } + for (std::map::const_iterator it = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } + + PyObject *plot_args = PyTuple_New(1); + PyTuple_SetItem(plot_args, 0, xarray); + + PyObject *res = PyObject_Call( + detail::_interpreter::get().s_python_function_spy, plot_args, kwargs); + + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if (res) Py_DECREF(res); } #endif // WITHOUT_NUMPY From 630ba843d5c92309279d9c10798be169a20b75a5 Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Fri, 2 Apr 2021 21:57:56 +0200 Subject: [PATCH 105/110] Some bugfixes and adjustments for contour/spy --- CMakeLists.txt | 8 ++++++++ examples/spy.cpp | 9 +++++---- matplotlibcpp.h | 1 - 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e1ef89..9353473 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ target_include_directories(matplotlib_cpp target_compile_features(matplotlib_cpp INTERFACE cxx_std_11 ) +# TODO: Use `Development.Embed` component when requiring cmake >= 3.18 find_package(Python3 COMPONENTS Interpreter Development REQUIRED) target_link_libraries(matplotlib_cpp INTERFACE Python3::Python @@ -92,6 +93,13 @@ if(Python3_NumPy_FOUND) add_executable(colorbar examples/colorbar.cpp) target_link_libraries(colorbar PRIVATE matplotlib_cpp) set_target_properties(colorbar PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + add_executable(contour examples/contour.cpp) + target_link_libraries(contour PRIVATE matplotlib_cpp) + set_target_properties(contour PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + + add_executable(spy examples/spy.cpp) + target_link_libraries(spy PRIVATE matplotlib_cpp) + set_target_properties(spy PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") endif() diff --git a/examples/spy.cpp b/examples/spy.cpp index 80bd544..6027a48 100644 --- a/examples/spy.cpp +++ b/examples/spy.cpp @@ -1,6 +1,7 @@ -#import -#import -#import "../matplotlibcpp.h" +#include "../matplotlibcpp.h" + +#include +#include namespace plt = matplotlibcpp; @@ -26,4 +27,4 @@ int main() plt::show(); return 0; -} \ No newline at end of file +} diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 9ad7faa..e6f64e7 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -354,7 +354,6 @@ static_assert(sizeof(long long) == 8); template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT64; }; static_assert(sizeof(unsigned long long) == 8); template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; -TODO: add int, long, etc. template PyObject* get_array(const std::vector& v) From 5b88e8b98f630f305e4ea09bb975cb9a9c0cd513 Mon Sep 17 00:00:00 2001 From: Benno Evers Date: Fri, 2 Apr 2021 22:07:08 +0200 Subject: [PATCH 106/110] Change line endings in CMakeLists.txt --- CMakeLists.txt | 266 ++++++++++++++++++++++++------------------------- 1 file changed, 133 insertions(+), 133 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9353473..bb2decd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,133 +1,133 @@ -cmake_minimum_required(VERSION 3.8 FATAL_ERROR) -project(matplotlib_cpp LANGUAGES CXX) - -include(GNUInstallDirs) -set(PACKAGE_NAME matplotlib_cpp) -set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/${PACKAGE_NAME}/cmake) - - -# Library target -add_library(matplotlib_cpp INTERFACE) -target_include_directories(matplotlib_cpp - INTERFACE - $ - $ -) -target_compile_features(matplotlib_cpp INTERFACE - cxx_std_11 -) -# TODO: Use `Development.Embed` component when requiring cmake >= 3.18 -find_package(Python3 COMPONENTS Interpreter Development REQUIRED) -target_link_libraries(matplotlib_cpp INTERFACE - Python3::Python - Python3::Module -) -find_package(Python3 COMPONENTS NumPy) -if(Python3_NumPy_FOUND) - target_link_libraries(matplotlib_cpp INTERFACE - Python3::NumPy - ) -else() - target_compile_definitions(matplotlib_cpp INTERFACE WITHOUT_NUMPY) -endif() -install( - TARGETS matplotlib_cpp - EXPORT install_targets -) - - -# Examples -add_executable(minimal examples/minimal.cpp) -target_link_libraries(minimal PRIVATE matplotlib_cpp) -set_target_properties(minimal PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - -add_executable(basic examples/basic.cpp) -target_link_libraries(basic PRIVATE matplotlib_cpp) -set_target_properties(basic PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - -add_executable(modern examples/modern.cpp) -target_link_libraries(modern PRIVATE matplotlib_cpp) -set_target_properties(modern PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - -add_executable(animation examples/animation.cpp) -target_link_libraries(animation PRIVATE matplotlib_cpp) -set_target_properties(animation PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - -add_executable(nonblock examples/nonblock.cpp) -target_link_libraries(nonblock PRIVATE matplotlib_cpp) -set_target_properties(nonblock PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - -add_executable(xkcd examples/xkcd.cpp) -target_link_libraries(xkcd PRIVATE matplotlib_cpp) -set_target_properties(xkcd PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - -add_executable(bar examples/bar.cpp) -target_link_libraries(bar PRIVATE matplotlib_cpp) -set_target_properties(bar PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - -add_executable(fill_inbetween examples/fill_inbetween.cpp) -target_link_libraries(fill_inbetween PRIVATE matplotlib_cpp) -set_target_properties(fill_inbetween PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - -add_executable(fill examples/fill.cpp) -target_link_libraries(fill PRIVATE matplotlib_cpp) -set_target_properties(fill PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - -add_executable(update examples/update.cpp) -target_link_libraries(update PRIVATE matplotlib_cpp) -set_target_properties(update PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - -add_executable(subplot2grid examples/subplot2grid.cpp) -target_link_libraries(subplot2grid PRIVATE matplotlib_cpp) -set_target_properties(subplot2grid PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - -add_executable(lines3d examples/lines3d.cpp) -target_link_libraries(lines3d PRIVATE matplotlib_cpp) -set_target_properties(lines3d PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - -if(Python3_NumPy_FOUND) - add_executable(surface examples/surface.cpp) - target_link_libraries(surface PRIVATE matplotlib_cpp) - set_target_properties(surface PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - - add_executable(colorbar examples/colorbar.cpp) - target_link_libraries(colorbar PRIVATE matplotlib_cpp) - set_target_properties(colorbar PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - add_executable(contour examples/contour.cpp) - target_link_libraries(contour PRIVATE matplotlib_cpp) - set_target_properties(contour PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - - add_executable(spy examples/spy.cpp) - target_link_libraries(spy PRIVATE matplotlib_cpp) - set_target_properties(spy PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") -endif() - - -# Install headers -install(FILES - "${PROJECT_SOURCE_DIR}/matplotlibcpp.h" - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) - - -# Install targets file -install(EXPORT install_targets - FILE - ${PACKAGE_NAME}Targets.cmake - NAMESPACE - ${PACKAGE_NAME}:: - DESTINATION - ${INSTALL_CONFIGDIR} -) - - -# Install matplotlib_cppConfig.cmake -include(CMakePackageConfigHelpers) -configure_package_config_file( - ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PACKAGE_NAME}Config.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}Config.cmake - INSTALL_DESTINATION ${INSTALL_CONFIGDIR} -) -install(FILES - ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}Config.cmake - DESTINATION ${INSTALL_CONFIGDIR} -) +cmake_minimum_required(VERSION 3.8 FATAL_ERROR) +project(matplotlib_cpp LANGUAGES CXX) + +include(GNUInstallDirs) +set(PACKAGE_NAME matplotlib_cpp) +set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/${PACKAGE_NAME}/cmake) + + +# Library target +add_library(matplotlib_cpp INTERFACE) +target_include_directories(matplotlib_cpp + INTERFACE + $ + $ +) +target_compile_features(matplotlib_cpp INTERFACE + cxx_std_11 +) +# TODO: Use `Development.Embed` component when requiring cmake >= 3.18 +find_package(Python3 COMPONENTS Interpreter Development REQUIRED) +target_link_libraries(matplotlib_cpp INTERFACE + Python3::Python + Python3::Module +) +find_package(Python3 COMPONENTS NumPy) +if(Python3_NumPy_FOUND) + target_link_libraries(matplotlib_cpp INTERFACE + Python3::NumPy + ) +else() + target_compile_definitions(matplotlib_cpp INTERFACE WITHOUT_NUMPY) +endif() +install( + TARGETS matplotlib_cpp + EXPORT install_targets +) + + +# Examples +add_executable(minimal examples/minimal.cpp) +target_link_libraries(minimal PRIVATE matplotlib_cpp) +set_target_properties(minimal PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(basic examples/basic.cpp) +target_link_libraries(basic PRIVATE matplotlib_cpp) +set_target_properties(basic PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(modern examples/modern.cpp) +target_link_libraries(modern PRIVATE matplotlib_cpp) +set_target_properties(modern PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(animation examples/animation.cpp) +target_link_libraries(animation PRIVATE matplotlib_cpp) +set_target_properties(animation PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(nonblock examples/nonblock.cpp) +target_link_libraries(nonblock PRIVATE matplotlib_cpp) +set_target_properties(nonblock PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(xkcd examples/xkcd.cpp) +target_link_libraries(xkcd PRIVATE matplotlib_cpp) +set_target_properties(xkcd PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(bar examples/bar.cpp) +target_link_libraries(bar PRIVATE matplotlib_cpp) +set_target_properties(bar PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(fill_inbetween examples/fill_inbetween.cpp) +target_link_libraries(fill_inbetween PRIVATE matplotlib_cpp) +set_target_properties(fill_inbetween PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(fill examples/fill.cpp) +target_link_libraries(fill PRIVATE matplotlib_cpp) +set_target_properties(fill PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(update examples/update.cpp) +target_link_libraries(update PRIVATE matplotlib_cpp) +set_target_properties(update PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(subplot2grid examples/subplot2grid.cpp) +target_link_libraries(subplot2grid PRIVATE matplotlib_cpp) +set_target_properties(subplot2grid PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_executable(lines3d examples/lines3d.cpp) +target_link_libraries(lines3d PRIVATE matplotlib_cpp) +set_target_properties(lines3d PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +if(Python3_NumPy_FOUND) + add_executable(surface examples/surface.cpp) + target_link_libraries(surface PRIVATE matplotlib_cpp) + set_target_properties(surface PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + + add_executable(colorbar examples/colorbar.cpp) + target_link_libraries(colorbar PRIVATE matplotlib_cpp) + set_target_properties(colorbar PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + add_executable(contour examples/contour.cpp) + target_link_libraries(contour PRIVATE matplotlib_cpp) + set_target_properties(contour PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + + add_executable(spy examples/spy.cpp) + target_link_libraries(spy PRIVATE matplotlib_cpp) + set_target_properties(spy PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +endif() + + +# Install headers +install(FILES + "${PROJECT_SOURCE_DIR}/matplotlibcpp.h" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + +# Install targets file +install(EXPORT install_targets + FILE + ${PACKAGE_NAME}Targets.cmake + NAMESPACE + ${PACKAGE_NAME}:: + DESTINATION + ${INSTALL_CONFIGDIR} +) + + +# Install matplotlib_cppConfig.cmake +include(CMakePackageConfigHelpers) +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PACKAGE_NAME}Config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}Config.cmake + INSTALL_DESTINATION ${INSTALL_CONFIGDIR} +) +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}Config.cmake + DESTINATION ${INSTALL_CONFIGDIR} +) From 14807809928ffec468b41a2ebe720d5187e3deb7 Mon Sep 17 00:00:00 2001 From: SpiritSeeker Date: Fri, 18 Sep 2020 17:22:46 +0530 Subject: [PATCH 107/110] Updated named plots to infer different datatypes in case of x and y inputs. --- matplotlibcpp.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index e6f64e7..fcd7c8a 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1661,8 +1661,8 @@ bool named_plot(const std::string& name, const std::vector& y, const st return res; } -template -bool named_plot(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") +template +bool named_plot(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { detail::_interpreter::get(); @@ -1688,8 +1688,8 @@ bool named_plot(const std::string& name, const std::vector& x, const st return res; } -template -bool named_semilogx(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") +template +bool named_semilogx(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { detail::_interpreter::get(); @@ -1715,8 +1715,8 @@ bool named_semilogx(const std::string& name, const std::vector& x, cons return res; } -template -bool named_semilogy(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") +template +bool named_semilogy(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { detail::_interpreter::get(); @@ -1742,8 +1742,8 @@ bool named_semilogy(const std::string& name, const std::vector& x, cons return res; } -template -bool named_loglog(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") +template +bool named_loglog(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { detail::_interpreter::get(); From 61501081ea32549df1a02dca26cb4edbe0b6a890 Mon Sep 17 00:00:00 2001 From: kesha787898 <45235408+kesha787898@users.noreply.github.com> Date: Sun, 28 Feb 2021 00:00:30 +0600 Subject: [PATCH 108/110] added scatter with colors --- matplotlibcpp.h | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index fcd7c8a..d95d46a 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -1022,6 +1022,44 @@ bool scatter(const std::vector& x, return res; } +template + bool scatter_colored(const std::vector& x, + const std::vector& y, + const std::vector& colors, + const double s=1.0, // The marker size in points**2 + const std::map & keywords = {}) + { + detail::_interpreter::get(); + + assert(x.size() == y.size()); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + PyObject* colors_array = detail::get_array(colors); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "s", PyLong_FromLong(s)); + PyDict_SetItemString(kwargs, "c", colors_array); + + for (const auto& it : keywords) + { + PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); + } + + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_scatter, plot_args, kwargs); + + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + + return res; + } + + template bool scatter(const std::vector& x, const std::vector& y, From 3dda5267e5f76f86a7888221df6151f2f09c93c6 Mon Sep 17 00:00:00 2001 From: Kenta Yonekura Date: Tue, 13 Apr 2021 11:20:27 +0900 Subject: [PATCH 109/110] Fix a missing M_PI in windows environment --- examples/lines3d.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/lines3d.cpp b/examples/lines3d.cpp index f3c201c..fd4610d 100644 --- a/examples/lines3d.cpp +++ b/examples/lines3d.cpp @@ -1,5 +1,5 @@ +#define _USE_MATH_DEFINES #include "../matplotlibcpp.h" - #include namespace plt = matplotlibcpp; From ef0383f1315d32e0156335e10b82e90b334f6d9f Mon Sep 17 00:00:00 2001 From: Kenta Yonekura Date: Tue, 13 Apr 2021 14:36:10 +0900 Subject: [PATCH 110/110] Enable cmake include definition --- cmake/matplotlib_cppConfig.cmake.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmake/matplotlib_cppConfig.cmake.in b/cmake/matplotlib_cppConfig.cmake.in index 1793f29..86d25d0 100644 --- a/cmake/matplotlib_cppConfig.cmake.in +++ b/cmake/matplotlib_cppConfig.cmake.in @@ -4,4 +4,7 @@ if(NOT TARGET matplotlib_cpp::matplotlib_cpp) find_package(Python3 COMPONENTS Interpreter Development REQUIRED) find_package(Python3 COMPONENTS NumPy) include("${matplotlib_cpp_CMAKE_DIR}/matplotlib_cppTargets.cmake") + + get_target_property(matplotlib_cpp_INCLUDE_DIRS matplotlib_cpp::matplotlib_cpp INTERFACE_INCLUDE_DIRECTORIES) + endif()