diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..0b4b36b --- /dev/null +++ b/.clang-format @@ -0,0 +1,83 @@ +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignConsecutiveMacros: AcrossEmptyLinesAndComments +AlignEscapedNewlines: Right +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterFunction: true + AfterControlStatement: false + SplitEmptyFunction: false + AfterEnum: false + AfterNamespace: true + AfterStruct: true + AfterUnion: false + AfterExternBlock: false + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: true + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: All +BreakBeforeConceptDeclarations: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 80 +CompactNamespaces: false +Cpp11BracedListStyle: true +DerivePointerAlignment: false +EmptyLineBeforeAccessModifier: Never +FixNamespaceComments: true +IncludeBlocks: Merge +IndentCaseLabels: false +IndentExternBlock: Indent +IndentRequires: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +NamespaceIndentation: All +PointerAlignment: Left +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: Never +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +UseTab: Never diff --git a/CMakeLists.txt b/CMakeLists.txt index bb2decd..21e0dab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,6 @@ 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 @@ -100,6 +99,22 @@ if(Python3_NumPy_FOUND) 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") + + add_executable(span examples/span.cpp) + target_link_libraries(span PRIVATE matplotlib_cpp) + target_compile_features(span PRIVATE cxx_std_20) + set_target_properties(span PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + + add_executable(ranges examples/ranges.cpp) + target_link_libraries(ranges PRIVATE matplotlib_cpp) + target_compile_features(ranges PRIVATE cxx_std_20) + set_target_properties(ranges PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + + add_executable(timeseries examples/timeseries.cpp) + target_link_libraries(timeseries PRIVATE matplotlib_cpp) + target_compile_features(timeseries PRIVATE cxx_std_20) + set_target_properties(timeseries PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + endif() diff --git a/datetime_utils.h b/datetime_utils.h new file mode 100644 index 0000000..ace9080 --- /dev/null +++ b/datetime_utils.h @@ -0,0 +1,138 @@ +#pragma once + +#include "matplotlibcpp.h" +#include +#include +#include +#include +#include +#include +#include + +// Convenience functions for converting C/C++ time objects to datetime.datetime +// objects. These are outside the matplotlibcpp namespace because they do not +// exist in matplotlib.pyplot. +template +PyObject* toPyDateTime(const TimePoint& t, int dummy = 0) +{ + using namespace std::chrono; + auto tsec = time_point_cast(t); + auto us = duration_cast(t - tsec); + + time_t tt = system_clock::to_time_t(t); + PyObject* obj = toPyDateTime(tt, us.count()); + + return obj; +} + +template<> +PyObject* toPyDateTime(const time_t& t, int us) +{ + tm tm{}; + gmtime_r(&t, &tm); // compatible with matlab, inverse of datenum. + + if(!PyDateTimeAPI) { PyDateTime_IMPORT; } + + PyObject* obj = PyDateTime_FromDateAndTime(tm.tm_year + 1900, tm.tm_mon + 1, + tm.tm_mday, tm.tm_hour, + tm.tm_min, tm.tm_sec, us); + if(obj) { + PyDateTime_Check(obj); + Py_INCREF(obj); + } + + return obj; +} + +template +PyObject* toPyDateTimeList(const Time_t* t, size_t nt) +{ + PyObject* tlist = PyList_New(nt); + if(tlist == nullptr) return nullptr; + + if(!PyDateTimeAPI) { PyDateTime_IMPORT; } + + for(size_t i = 0; i < nt; i++) { + PyObject* ti = toPyDateTime(t[i], 0); + PyList_SET_ITEM(tlist, i, ti); + } + + return tlist; +} + +template +class DateTimeList +{ +public: + + DateTimeList() = default; + + DateTimeList(const Time_t* t, size_t nt) + { + matplotlibcpp::detail::_interpreter::get(); + tlist = (PyListObject*)toPyDateTimeList(t, nt); + } + + ~DateTimeList() + { + if(tlist) Py_DECREF((PyObject*)tlist); + } + + DateTimeList& operator=(const DateTimeList& rhs) { + tlist=rhs.tlist; + Py_INCREF(tlist); + return *this; + } + + PyListObject* get() const { return tlist; } + size_t size() const { return tlist ? PyList_Size((PyObject*)tlist) : 0; } +private: + mutable PyListObject* tlist = nullptr; +}; + +namespace matplotlibcpp +{ + // special purpose function to plot against python datetime objects. + template + bool plot(const DateTimeList& t, const ContainerY& y, + const std::string& fmt = "") + { + detail::_interpreter::get(); + + // DECREF decrements the ref counts of all objects in the plot_args + // tuple, In particular, it decreasesthe ref count of the time array x. + // We want to maintain that unchanged though, so we can reuse it. + PyListObject* tarray = t.get(); + Py_INCREF(tarray); + + NPY_TYPES ytype + = detail::select_npy_type::type; + + npy_intp tsize = PyList_Size((PyObject*)tarray); + assert(y.size() % tsize == 0 + && "length of y must be a multiple of length of x!"); + + npy_intp yrows = tsize, ycols = y.size() / yrows; + npy_intp ysize[] = {yrows, ycols}; // ysize[0] must equal tsize + + PyObject* yarray = PyArray_New(&PyArray_Type, 2, ysize, ytype, nullptr, + (void*)y.data(), 0, NPY_ARRAY_FARRAY, + nullptr); // col major + + PyObject* pystring = PyString_FromString(fmt.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, (PyObject*)tarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_plot, plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return true; + } + +} // namespace matplotlibcpp diff --git a/examples/animation.cpp b/examples/animation.cpp index d979430..320c0e5 100644 --- a/examples/animation.cpp +++ b/examples/animation.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o animation $(python-config --includes) animation.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include #include "../matplotlibcpp.h" @@ -6,31 +10,34 @@ namespace plt = matplotlibcpp; int main() { - int n = 1000; - std::vector x, y, z; - - for(int i=0; i x, y, z; + + for(int i=0; i @@ -14,5 +18,6 @@ int main(int argc, char **argv) { plt::bar(test_data); plt::show(); + plt::detail::_interpreter::kill(); return (0); } diff --git a/examples/basic.cpp b/examples/basic.cpp index 2dc34c7..b4d3c8a 100644 --- a/examples/basic.cpp +++ b/examples/basic.cpp @@ -1,3 +1,8 @@ +// +// g++ -g -Wall -std=c++20 -o basic -I/usr/include/python3.9 basic.cpp -lpython3.9 +// g++ -g -Wall -std=c++20 -o basic $(python-config --includes) basic.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include #include @@ -5,7 +10,7 @@ namespace plt = matplotlibcpp; -int main() +int main() { // Prepare data. int n = 5000; @@ -15,7 +20,7 @@ int main() y.at(i) = sin(2*M_PI*i/360.0); z.at(i) = log(i); } - + // Set the size of output image = 1200x780 pixels plt::figure_size(1200, 780); @@ -41,4 +46,7 @@ int main() const char* filename = "./basic.png"; std::cout << "Saving result to " << filename << std::endl;; plt::save(filename); + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/basic.png b/examples/basic.png index 4d87ff0..e6f4f14 100644 Binary files a/examples/basic.png and b/examples/basic.png differ diff --git a/examples/colorbar.cpp b/examples/colorbar.cpp index f53e01d..afca7d3 100644 --- a/examples/colorbar.cpp +++ b/examples/colorbar.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o colorbar $(python-config --includes) colorbar.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include #include @@ -29,4 +33,7 @@ int main() plt::show(); plt::close(); Py_DECREF(mat); + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/contour.cpp b/examples/contour.cpp index 9289d0a..11ab67c 100644 --- a/examples/contour.cpp +++ b/examples/contour.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o contour $(python-config --includes) contour.cpp $(python-config --ldflags --embed) +// + #include "../matplotlibcpp.h" #include @@ -21,4 +25,7 @@ int main() plt::contour(x, y, z); plt::show(); + + plt::detail::_interpreter::kill(); + return (0); } diff --git a/examples/fill.cpp b/examples/fill.cpp index 6059b47..8bcb2d7 100644 --- a/examples/fill.cpp +++ b/examples/fill.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o fill $(python-config --includes) fill.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include "../matplotlibcpp.h" #include @@ -32,4 +36,7 @@ int main() { plt::fill(x1, y1, {}); } plt::show(); + + plt::detail::_interpreter::kill(); + return (0); } diff --git a/examples/fill_inbetween.cpp b/examples/fill_inbetween.cpp index 788d008..6a99883 100644 --- a/examples/fill_inbetween.cpp +++ b/examples/fill_inbetween.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o fill_inbetween $(python-config --includes) fill_inbetween.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include "../matplotlibcpp.h" #include @@ -7,22 +11,25 @@ using namespace std; 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 < n; ++i) { - x.at(i) = i * i; - y.at(i) = sin(2 * M_PI * i / 360.0); - z.at(i) = log(i); - } + // Prepare data. + int n = 5000; + std::vector x(n), y(n), z(n), w(n, 2); + for (int i = 0; i < n; ++i) { + x.at(i) = i * i; + y.at(i) = sin(2 * M_PI * i / 360.0); + z.at(i) = log(i); + } + + // Prepare keywords to pass to PolyCollection. See + // https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.fill_between.html + std::map keywords; + keywords["alpha"] = "0.4"; + keywords["color"] = "grey"; + keywords["hatch"] = "-"; - // Prepare keywords to pass to PolyCollection. See - // https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.fill_between.html - std::map keywords; - keywords["alpha"] = "0.4"; - keywords["color"] = "grey"; - keywords["hatch"] = "-"; + plt::fill_between(x, y, z, keywords); + plt::show(); - plt::fill_between(x, y, z, keywords); - plt::show(); + plt::detail::_interpreter::kill(); + return (0); } diff --git a/examples/imshow.cpp b/examples/imshow.cpp index b11661e..f466971 100644 --- a/examples/imshow.cpp +++ b/examples/imshow.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -std=c++20 -o imshow $(python-config --includes) imshow.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include #include @@ -26,4 +30,7 @@ int main() // Show plots plt::save("imshow.png"); std::cout << "Result saved to 'imshow.png'.\n"; + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/lines3d.cpp b/examples/lines3d.cpp index fd4610d..84ef657 100644 --- a/examples/lines3d.cpp +++ b/examples/lines3d.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o lines3d $(python-config --includes) lines3d.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include "../matplotlibcpp.h" #include @@ -9,7 +13,7 @@ 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); @@ -27,4 +31,7 @@ int main() plt::set_zlabel("z label"); // set_zlabel rather than just zlabel, in accordance with the Axes3D method plt::legend(); plt::show(); + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/minimal.cpp b/examples/minimal.cpp index fbe1e1c..fd76a42 100644 --- a/examples/minimal.cpp +++ b/examples/minimal.cpp @@ -1,3 +1,6 @@ +// +// g++ -g -Wall -o minimal $(python-config --includes) minimal.cpp $(python-config --ldflags --embed) +// #include "../matplotlibcpp.h" namespace plt = matplotlibcpp; @@ -5,4 +8,7 @@ namespace plt = matplotlibcpp; int main() { plt::plot({1,3,2,4}); plt::show(); + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/modern.cpp b/examples/modern.cpp index 871ef2b..27011d8 100644 --- a/examples/modern.cpp +++ b/examples/modern.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o modern $(python-config --includes) modern.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include #include "../matplotlibcpp.h" @@ -5,29 +9,32 @@ using namespace std; namespace plt = matplotlibcpp; -int main() +int main() { - // plot(y) - the x-coordinates are implicitly set to [0,1,...,n) - //plt::plot({1,2,3,4}); - - // Prepare data for parametric plot. - int n = 5000; // number of data points - vector x(n),y(n); - for(int i=0; i x(n),y(n); + for(int i=0; i #include "../matplotlibcpp.h" @@ -43,4 +47,7 @@ int main() cout << "matplotlibcpp::show() is working in an non-blocking mode" << endl; getchar(); + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/quiver.cpp b/examples/quiver.cpp index ea3c3ec..2a8ef96 100644 --- a/examples/quiver.cpp +++ b/examples/quiver.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o quiver $(python-config --includes) quiver.cpp $(python-config --ldflags --embed) +// + #include "../matplotlibcpp.h" namespace plt = matplotlibcpp; @@ -17,4 +21,7 @@ int main() plt::quiver(x, y, u, v); plt::show(); -} \ No newline at end of file + + plt::detail::_interpreter::kill(); + return 0; +} diff --git a/examples/ranges.cpp b/examples/ranges.cpp new file mode 100644 index 0000000..0ef0714 --- /dev/null +++ b/examples/ranges.cpp @@ -0,0 +1,116 @@ +// +// g++ -std=c++20 -g -Wall -o ranges $(python-config --includes) ranges.cpp $(python-config --ldflags --embed) +// +// g++ -std=c++17 -g -Wall -o ranges $(python-config --includes) ranges.cpp $(python-config --ldflags --embed) +// +#include "../matplotlibcpp.h" + +#include +#include +#include +#include +#include +#include + +#define UNUSED(x) (void)(x) + +using namespace std; +namespace plt = matplotlibcpp; + +int main() +{ + plt::detail::_interpreter::get(); + + // C-style arrays with multiple rows. + +#if __cplusplus >= CPP20 + + time_t t[]={1, 2, 3, 4}; + + // Care with column-major vs row-major! + // C and Python are row-major, but usually a time series is column-major + // and we want to plot the columns. + // In the example below, these columns are [3,1,4,5] and [5,4,1,3], so + // the data must be stored like this: + double data [] = { + 3, 1, 4, 5, + 5, 4, 1, 3 + }; // contiguous data, column major! + + // Use std::span() to convert to a contiguous range (O(1)). + // Data won't be copied, but passed as a pointer to Python. + plt::plot(span(t, 4), span(data, 8)); + plt::grid(true); + plt::title("C-arrays, with multiple columns"); + plt::show(); + +#else + + cerr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << ": " + << "No support for C-style arrays in C++ <= 17" << endl; + +#endif + + // vectors + + // Vectors are also contiguous ranges. + // In C++20, as with span, plot resolves to plot(contiguous_range). + // In C++ < 20 plot resolves to plot(vector). + vector x={1, 2, 3, 4, 5}; + vector y={0, 1, 0, -1, 0}; + plt::plot(x, y); + plt::grid(true); + plt::title("vectors"); + plt::show(); + + + // lists + + // By contrast, lists are non-contiguous (but iterable) containers, so + // plot resolves to plot(iterable). + list u { 1, 2, 3, 4, 5}; + list v { 0, -1, 0, 1, 0}; + plt::plot(u, v, ""); + plt::grid(true); + plt::title("Lists (non-contiguous ranges)"); + plt::show(); + + + // All together now +#if __cplusplus >= CPP20 + + // If span is not last, plot resolves to plot(iterable), which copies data. + // That's because in the dispatch plot() we have plot_impl() && plot() + // (i.e. plot_impl() comes first), and we only have iterable and + // callable plot_impl(). That sends us to the iterable plot_impl(), + // rather than to plot(contiguous_range). + // + // TODO: have 3 tags plot_impl(): iterable, callable and contiguous range. + plt::plot(span(t, 4), span(data, 8), "", x, y, "b", u, v, "r"); + plt::grid(true); + plt::title("Variadic templates recursion, span first (copy)"); + plt::show(); + + // This resolves to plot(contiguous_range) and does not copy data. + plt::plot(x, y, "b", u, v, "r", span(t, 4), span(data, 8)); + plt::grid(true); + plt::title("Variadic templates recursion, span last (passthrough)"); + plt::show(); + +#else + + // no C-arrays + cerr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << ": " + << "No support for C-style arrays in C++ <= 17" << endl; + + plt::plot(x, y, "b", u, v, "r"); + plt::grid(true); + plt::title("Variadic templates recursion"); + plt::show(); + +#endif + + plt::detail::_interpreter::kill(); + + return 0; +} diff --git a/examples/span.cpp b/examples/span.cpp new file mode 100644 index 0000000..2370595 --- /dev/null +++ b/examples/span.cpp @@ -0,0 +1,43 @@ +// +// g++ -std=c++20 -g -Wall -o span $(python-config --includes) span.cpp $(python-config --ldflags --embed) +// +#include "../matplotlibcpp.h" + +#include +#include +#include + +using namespace std; +namespace plt = matplotlibcpp; + +int main() +{ + // C-style arrays with multiple rows + time_t t[]={1, 2, 3, 4}; + int data[]={ + 3, 1, 4, 5, + 5, 4, 1, 3, + 3, 3, 3, 3 + }; + + // vector xticks {1, 2, 3, 4}; + // vector xlabels {"a", "b", "c", "d"}; + vector xticks {1, 1.5, 2, 2.5, 3, 3.5, 4}; + vector xlabels {"aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg"}; + + // https://matplotlib.org/stable/api/text_api.html#matplotlib.text.Text + map kwargs {{"rotation", "45"}, + {"fontsize", "xx-large"}, + {"color", "m"} + }; + + plt::plot(span(t, 4), span(data, 12)); + // plt::xticks(xticks, xlabels); + plt::xticks(xticks, xlabels, kwargs); + plt::grid(true); + plt::show(); + + plt::detail::_interpreter::kill(); + + return 0; +} diff --git a/examples/spy.cpp b/examples/spy.cpp index 6027a48..286957f 100644 --- a/examples/spy.cpp +++ b/examples/spy.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o spy $(python-config --includes) spy.cpp $(python-config --ldflags --embed) +// + #include "../matplotlibcpp.h" #include @@ -26,5 +30,6 @@ int main() plt::spy(matrix, 5, {{"marker", "o"}}); plt::show(); + plt::detail::_interpreter::kill(); return 0; } diff --git a/examples/subplot2grid.cpp b/examples/subplot2grid.cpp index f590e51..1869852 100644 --- a/examples/subplot2grid.cpp +++ b/examples/subplot2grid.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o subplot2grid $(python-config --includes) subplot2grid.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include #include "../matplotlibcpp.h" @@ -40,5 +44,8 @@ int main() // Show plots - plt::show(); + plt::show(); + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/surface.cpp b/examples/surface.cpp index 4865f06..beec66a 100644 --- a/examples/surface.cpp +++ b/examples/surface.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o surface $(python-config --includes) surface.cpp $(python-config --ldflags --embed) +// + #include "../matplotlibcpp.h" #include @@ -21,4 +25,7 @@ int main() plt::plot_surface(x, y, z); plt::show(); + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/timeseries.cpp b/examples/timeseries.cpp new file mode 100644 index 0000000..ce33934 --- /dev/null +++ b/examples/timeseries.cpp @@ -0,0 +1,98 @@ +// +// g++ -std=c++20 -g -Wall -o timeseries $(python-config --includes) timeseries.cpp $(python-config --ldflags --embed) +// g++ -std=c++20 -g -Wall -fsanitize=address -o timeseries $(python-config --includes) timeseries.cpp $(python-config --ldflags --embed) -lasan +// +#include "../matplotlibcpp.h" +#include "../datetime_utils.h" + +#include +#include +#include +#include +#include + +using namespace std; +namespace plt = matplotlibcpp; + +// In python2.7 there was a dedicated function PyString_AsString, but no more. +string tostring(PyObject* obj) +{ + string out; + PyObject* repr=PyObject_Repr(obj); // unicode object + if (repr) { + PyObject* str=PyUnicode_AsEncodedString(repr, 0, 0); + if (str) { + const char* bytes=PyBytes_AS_STRING(str); + out=bytes; + Py_DECREF(str); + } + + Py_DECREF(repr); + } + + return out; +} + +int main() +{ + using namespace std::chrono; + using clk=std::chrono::high_resolution_clock; + + plt::detail::_interpreter::get(); // initialize everything + + // Test toPyDateTime functions. + PyObject* now=toPyDateTime(time(0)); + cout << tostring(now) << endl; + Py_DECREF(now); + + now=toPyDateTime(clk::now()); + cout << tostring(now) << endl; + Py_DECREF(now); + + + // Time series plot + + // We have a time array (e.g. from a csv file): + size_t n=10; + vector tvec; + time_t tstart=time(0); + for (size_t i=0; i tarray(tvec.data(), tvec.size()); + + // Create 2-column data and plot it against tarray: + vector data(2*n); + for (size_t i=0; i<2*n; i++) data[i]=2.0*i+5; + + plt::plot(tarray, data); + plt::xticks({{"rotation", "20"}}); + plt::title("Linear"); + plt::grid(true); + plt::show(); + + // // Modify some data and plot again, reusing the time array: + // for (size_t i=0; i(); + plt::detail::_interpreter::kill(); + + return 0; +} diff --git a/examples/update.cpp b/examples/update.cpp index 64f4906..5a34c1c 100644 --- a/examples/update.cpp +++ b/examples/update.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o update $(python-config --includes) update.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include #include "../matplotlibcpp.h" @@ -57,4 +61,7 @@ int main() plt::pause(0.1); } } + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/examples/xkcd.cpp b/examples/xkcd.cpp index fa41cfc..38e48d2 100644 --- a/examples/xkcd.cpp +++ b/examples/xkcd.cpp @@ -1,3 +1,7 @@ +// +// g++ -g -Wall -o xkcd $(python-config --includes) xkcd.cpp $(python-config --ldflags --embed) +// + #define _USE_MATH_DEFINES #include #include "../matplotlibcpp.h" @@ -18,5 +22,8 @@ int main() { plt::plot(t, x); plt::title("AN ORDINARY SIN WAVE"); plt::show(); + + plt::detail::_interpreter::kill(); + return 0; } diff --git a/matplotlibcpp.h b/matplotlibcpp.h index d95d46a..48d540f 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -3,2984 +3,3762 @@ // Python headers must be included before any system headers, since // they define _POSIX_C_SOURCE #include - -#include -#include +#include #include +#include +#include +#include +#include +#include #include -#include +#include #include -#include -#include // requires c++11 support -#include -#include // std::stod +#include +#include #ifndef WITHOUT_NUMPY -# define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION -# include +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION +#include -# ifdef WITH_OPENCV -# include -# endif // WITH_OPENCV +#ifdef WITH_OPENCV +#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 +#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 -# define PyString_FromString PyUnicode_FromString -# define PyInt_FromLong PyLong_FromLong -# define PyString_FromString PyUnicode_FromString +#define PyString_FromString PyUnicode_FromString +#define PyInt_FromLong PyLong_FromLong #endif +#define CPP20 202002L -namespace matplotlibcpp { -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; - 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_contour; - 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_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; - 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; - PyObject *s_python_function_axhline; - 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; - 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; - 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; - 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; - 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 - 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() { - return interkeeper(false); - } - - static _interpreter& kill() { - 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) - ctx.~_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: +namespace matplotlibcpp +{ + 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; + 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_contour; + 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_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; + 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; + PyObject* s_python_function_axhline; + 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; + 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; + 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; + 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; + 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 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() { return interkeeper(false); } + + static _interpreter& kill() { 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) ctx.~_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 -# if PY_MAJOR_VERSION >= 3 - - void *import_numpy() { - import_array(); // initialize C-API - return NULL; - } - -# else - - void import_numpy() { - import_array(); // initialize C-API - } +#if PY_MAJOR_VERSION >= 3 -# endif + void* import_numpy() { + import_array(); // initialize C-API + return NULL; + } +#else + void import_numpy() { + import_array(); // initialize C-API + } #endif +#endif + + _interpreter() { + // optional but recommended +// #if PY_MAJOR_VERSION >= 3 +// wchar_t name[] = L"plotting"; +// #else +// char name[] = "plotting"; +// #endif - _interpreter() { + // FIXME: + // https://docs.python.org/3.12/c-api/init_config.html#init-config - // optional but recommended #if PY_MAJOR_VERSION >= 3 - wchar_t name[] = L"plotting"; +#if PY_MINOR_VERSION >= 8 + + // https://docs.python.org/3/c-api/init_config.html + PyStatus status; + + PyConfig config; + PyConfig_InitPythonConfig(&config); + config.isolated = 1; + + // const char* dummy_args[] + // = {"Python", NULL}; // const is needed because literals + // // must not be modified + // char* const* argv = dummy_args; + // char* const* argv = nullptr; + // const char* argv_[] = {"PyPlot", NULL}; + const char* const argv_[] = {"PyPlot", NULL}; + // int argc = sizeof(dummy_args) / sizeof(dummy_args[0]) - 1; + int argc_ = sizeof(argv_) / sizeof(argv_[0]) - 1; + // int argc=1; + + /* Decode command line arguments. + Implicitly preinitialize Python (in isolated mode). */ + status = PyConfig_SetBytesArgv(&config, argc_, (char* const*)argv_); + if (PyStatus_Exception(status)) { + // goto exception; + throw std::runtime_error(status.err_msg); + } + + status = Py_InitializeFromConfig(&config); + if (PyStatus_Exception(status)) { + throw std::runtime_error("Initialization from config failed"); + // goto exception; + } + PyConfig_Clear(&config); + + // Py_RunMain(); + #else - char name[] = "plotting"; -#endif - 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; + wchar_t name[] = L"plotting"; + Py_SetProgramName(name); + Py_Initialize(); -#if PY_MAJOR_VERSION >= 3 - PySys_SetArgv(argc, const_cast(argv)); + 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)); +#endif #else - PySys_SetArgv(argc, (char **)(argv)); + PySys_SetArgv(argc, (char**)(argv)); #endif #ifndef WITHOUT_NUMPY - import_numpy(); // initialize numpy C-API + import_numpy(); // initialize numpy C-API #endif - PyObject* matplotlibname = PyString_FromString("matplotlib"); - PyObject* pyplotname = PyString_FromString("matplotlib.pyplot"); - PyObject* cmname = PyString_FromString("matplotlib.cm"); - PyObject* pylabname = PyString_FromString("pylab"); - if (!pyplotname || !pylabname || !matplotlibname || !cmname) { - throw std::runtime_error("couldnt create string"); - } - - PyObject* matplotlib = PyImport_Import(matplotlibname); - - Py_DECREF(matplotlibname); - 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 - if (!s_backend.empty()) { - 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!"); } - - 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!"); } - - 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"); - 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_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"); - 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_boxplot = safe_import(pymod,"boxplot"); - s_python_function_subplot = safe_import(pymod, "subplot"); - s_python_function_subplot2grid = safe_import(pymod, "subplot2grid"); - s_python_function_legend = safe_import(pymod, "legend"); - s_python_function_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"); - 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"); - s_python_function_ylabel = safe_import(pymod, "ylabel"); - s_python_function_gca = safe_import(pymod, "gca"); - s_python_function_xticks = safe_import(pymod, "xticks"); - s_python_function_yticks = safe_import(pymod, "yticks"); - s_python_function_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_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_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"); - 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_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"); - s_python_function_spy = PyObject_GetAttrString(pymod, "spy"); + PyObject* matplotlibname = PyString_FromString("matplotlib"); + PyObject* pyplotname = PyString_FromString("matplotlib.pyplot"); + PyObject* cmname = PyString_FromString("matplotlib.cm"); + PyObject* pylabname = PyString_FromString("pylab"); + if(!pyplotname || !pylabname || !matplotlibname || !cmname) { + throw std::runtime_error("couldnt create string"); + } + + PyObject* matplotlib = PyImport_Import(matplotlibname); + + Py_DECREF(matplotlibname); + 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 + if(!s_backend.empty()) { + 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!"); + } + + 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!"); + } + + 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"); + 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_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"); + 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_boxplot = safe_import(pymod, "boxplot"); + s_python_function_subplot = safe_import(pymod, "subplot"); + s_python_function_subplot2grid + = safe_import(pymod, "subplot2grid"); + s_python_function_legend = safe_import(pymod, "legend"); + s_python_function_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"); + 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"); + s_python_function_ylabel = safe_import(pymod, "ylabel"); + s_python_function_gca = safe_import(pymod, "gca"); + s_python_function_xticks = safe_import(pymod, "xticks"); + s_python_function_yticks = safe_import(pymod, "yticks"); + s_python_function_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_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_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"); + 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_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"); + s_python_function_spy = PyObject_GetAttrString(pymod, "spy"); #ifndef WITHOUT_NUMPY - s_python_function_imshow = safe_import(pymod, "imshow"); + s_python_function_imshow = safe_import(pymod, "imshow"); #endif - s_python_empty_tuple = PyTuple_New(0); - } + s_python_empty_tuple = PyTuple_New(0); + } + + ~_interpreter() { Py_Finalize(); } + }; + + } // end namespace detail + + /// 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 + void backend(const std::string& name) { detail::s_backend = name; } + + bool annotate(std::string annotation, double x, double y) + { + detail::_interpreter::get(); - ~_interpreter() { - Py_Finalize(); - } -}; + PyObject* xy = PyTuple_New(2); + PyObject* str = PyString_FromString(annotation.c_str()); -} // end namespace detail + PyTuple_SetItem(xy, 0, PyFloat_FromDouble(x)); + PyTuple_SetItem(xy, 1, PyFloat_FromDouble(y)); -/// 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; -} + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "xy", xy); -inline bool annotate(std::string annotation, double x, double y) -{ - detail::_interpreter::get(); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, str); - PyObject * xy = PyTuple_New(2); - PyObject * str = PyString_FromString(annotation.c_str()); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_annotate, args, + kwargs); - PyTuple_SetItem(xy,0,PyFloat_FromDouble(x)); - PyTuple_SetItem(xy,1,PyFloat_FromDouble(y)); + Py_DECREF(args); + Py_DECREF(kwargs); - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "xy", xy); + if(res) Py_DECREF(res); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, str); + return res; + } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_annotate, args, kwargs); + namespace detail + { +#ifndef WITHOUT_NUMPY + // Type selector for numpy array conversion + template + struct select_npy_type + { + const static NPY_TYPES type = NPY_NOTYPE; + }; // Default - Py_DECREF(args); - Py_DECREF(kwargs); + template<> + struct select_npy_type + { + const static NPY_TYPES type = NPY_DOUBLE; + }; - if(res) Py_DECREF(res); + template<> + struct select_npy_type + { + const static NPY_TYPES type = NPY_FLOAT; + }; - return res; -} + template<> + struct select_npy_type + { + const static NPY_TYPES type = NPY_BOOL; + }; -namespace detail { + template<> + struct select_npy_type + { + const static NPY_TYPES type = NPY_INT8; + }; -#ifndef WITHOUT_NUMPY -// Type selector for numpy array conversion -template struct select_npy_type { const static NPY_TYPES type = NPY_NOTYPE; }; //Default -template <> struct select_npy_type { const static NPY_TYPES type = NPY_DOUBLE; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_FLOAT; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_BOOL; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT8; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_SHORT; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT64; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT8; }; -template <> struct select_npy_type { const static NPY_TYPES type = NPY_USHORT; }; -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; }; - -template -PyObject* get_array(const std::vector& v) -{ - npy_intp vsize = v.size(); - NPY_TYPES type = select_npy_type::type; - 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; - } + template<> + struct select_npy_type + { + const static NPY_TYPES type = NPY_SHORT; + }; - PyObject* varray = PyArray_SimpleNewFromData(1, &vsize, type, (void*)(v.data())); - return varray; -} + template<> + struct select_npy_type + { + const static NPY_TYPES type = NPY_INT; + }; + template<> + struct select_npy_type + { + const static NPY_TYPES type = NPY_INT64; + }; -template -PyObject* get_2darray(const std::vector<::std::vector>& v) -{ - if (v.size() < 1) throw std::runtime_error("get_2d_array v too small"); + template<> + struct select_npy_type + { + const static NPY_TYPES type = NPY_UINT8; + }; - npy_intp vsize[2] = {static_cast(v.size()), - static_cast(v[0].size())}; + template<> + struct select_npy_type + { + const static NPY_TYPES type = NPY_USHORT; + }; - PyArrayObject *varray = - (PyArrayObject *)PyArray_SimpleNew(2, vsize, NPY_DOUBLE); + template<> + struct select_npy_type + { + const static NPY_TYPES type = NPY_ULONG; + }; - double *vd_begin = static_cast(PyArray_DATA(varray)); + template<> + struct select_npy_type + { + const static NPY_TYPES type = NPY_UINT64; + }; - 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]; - } + // 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); - return reinterpret_cast(varray); -} + template<> + struct select_npy_type + { + const static NPY_TYPES type = NPY_INT64; + }; -#else // fallback if we don't have numpy: copy every element of the given vector + static_assert(sizeof(unsigned long long) == 8); -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; -} + template<> + struct select_npy_type + { + const static NPY_TYPES type = NPY_UINT64; + }; -#endif // WITHOUT_NUMPY + template + PyObject* get_array(const std::vector& v) + { + npy_intp vsize = v.size(); + NPY_TYPES type = select_npy_type::type; + if(type == NPY_NOTYPE) { + size_t memsize = v.size() * sizeof(double); + double* dp = static_cast(::malloc(memsize)); + for(size_t i = 0; i < v.size(); ++i) dp[i] = v[i]; + PyObject* varray + = PyArray_SimpleNewFromData(1, &vsize, NPY_DOUBLE, dp); + PyArray_UpdateFlags(reinterpret_cast(varray), + NPY_ARRAY_OWNDATA); + return varray; + } + + PyObject* varray + = PyArray_SimpleNewFromData(1, &vsize, type, (void*)(v.data())); + return varray; + } -// sometimes, for labels and such, we need string arrays -inline PyObject * get_array(const std::vector& strings) -{ - PyObject* list = PyList_New(strings.size()); - for (std::size_t i = 0; i < strings.size(); ++i) { - PyList_SetItem(list, i, PyString_FromString(strings[i].c_str())); - } - return list; -} - -// not all matplotlib need 2d arrays, some prefer lists of lists -template -PyObject* get_listlist(const std::vector>& ll) -{ - PyObject* listlist = PyList_New(ll.size()); - for (std::size_t i = 0; i < ll.size(); ++i) { - PyList_SetItem(listlist, i, get_array(ll[i])); - } - return listlist; -} - -} // namespace detail - -/// Plot a line through the given x and y data points.. -/// -/// See: https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.pyplot.plot.html -template -bool plot(const std::vector &x, const std::vector &y, const std::map& keywords) -{ - assert(x.size() == y.size()); + template + PyObject* get_2darray(const std::vector<::std::vector>& v) + { + if(v.size() < 1) + throw std::runtime_error("get_2d_array v too small"); - detail::_interpreter::get(); + npy_intp vsize[2] = {static_cast(v.size()), + static_cast(v[0].size())}; - // using numpy arrays - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + PyArrayObject* varray + = (PyArrayObject*)PyArray_SimpleNew(2, vsize, NPY_DOUBLE); - // construct positional args - PyObject* args = PyTuple_New(2); - PyTuple_SetItem(args, 0, xarray); - PyTuple_SetItem(args, 1, yarray); + double* vd_begin = static_cast(PyArray_DATA(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(), PyString_FromString(it->second.c_str())); - } + 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]; + } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, args, kwargs); + return reinterpret_cast(varray); + } - Py_DECREF(args); - Py_DECREF(kwargs); - if(res) Py_DECREF(res); +#else // fallback if we don't have numpy: copy every element of the given vector - return res; -} + 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; + } -// TODO - it should be possible to make this work by implementing -// a non-numpy alternative for `detail::get_2darray()`. -#ifndef WITHOUT_NUMPY -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(), - const long fig_number=0) -{ - 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 - // 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()); - - // 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(); - 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) { - 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_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 *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 -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); -} - -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 -template -void plot3(const std::vector &x, - const std::vector &y, - const std::vector &z, - const std::map &keywords = - std::map(), - const long fig_number=0) -{ - 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); - } - 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) -{ - assert(x.size() == y.size()); - - detail::_interpreter::get(); - - // using numpy arrays - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::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 (std::map::const_iterator it = - keywords.begin(); it != keywords.end(); ++it) { - PyDict_SetItemString(kwargs, it->first.c_str(), - PyString_FromString(it->second.c_str())); - } + // 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; + } - PyObject* res = PyObject_Call( - detail::_interpreter::get().s_python_function_stem, args, kwargs); + // 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; + } - Py_DECREF(args); - Py_DECREF(kwargs); - if (res) - Py_DECREF(res); + } // 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 + + // If a container (range) has ::data() that converts to ::value and it has + // ::size() that is convertible to pointer differences, then it's + // contiguous, in which case we need not copy the data. + // https://stackoverflow.com/questions/42851957/contiguous-iterator-detection + // + // TODO: get rid of the plot(vector) specializations, they unnecessarily + // copy the data. + // TODO: provide contiguous and non-contiguous implementations for ranges, + // using iterator_tag. + // https://stackoverflow.com/questions/4688177/how-does-iterator-category-in-c-work + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1474r0.pdf + // + +#if __cplusplus >= CPP20 + + // Support for contiguous ranges, e.g. vector, span, etc. In this case + // we can pass the data pointer directly to python, without copying. + // + // Non-contiguous (but iterable) arrays (e.g. lists), call a different plot, + // one which has to copy the items into a contiguous C-style array before + // passing them to python. + template + bool plot(const ContainerX& x, const ContainerY& y, + const std::string& fmt = "") + { + assert(y.size() % x.size() == 0 + && "length of y must be a multiple of length of x!"); - return res; -} + detail::_interpreter::get(); -template< typename Numeric > -bool fill(const std::vector& x, const std::vector& y, const std::map& keywords) -{ - assert(x.size() == y.size()); + NPY_TYPES xtype + = detail::select_npy_type::type; + NPY_TYPES ytype + = detail::select_npy_type::type; + + npy_intp xsize = x.size(); + npy_intp yrows = xsize, ycols = y.size() / x.size(); + npy_intp ysize[] = {yrows, ycols}; // ysize[0] must equal xsize + + // We have 2 options to pass existing data buffers to PyObject: + // PyArray_SimpleFromData() - by default creates C-style (row major) + // array PyArray_New() - uses flags, so we can specify Fotran-style (col + // major) + // + // In python, + // a=np.array([[3,5], [1,4], [4,1], [5,3]]) + // is stored in row-major mode and has columns + // a[:,0]=[3,1,4,5] and a[:,1]=[5,4,1,3] + // Then, + // plt.plot(a) + // plots the columns of a. So in python the array is row-major, but + // plotted columnwise. + // + // For dataframes (and time series), however, it is more natural to + // assume that the data is stored contiguously in column-major mode, + // i.e. double a[] = [3, 1, 4, 5 + // 5, 4, 1, 3]; + // We let python know that is the case, by specifying the + // NPY_ARRAY_FARRAY flag. This rules out the usage of + // PyArray_SimpleFromData(). + // + // Of course, in the vector-only version of this plot() function all + // this is irrelevant, because that plot is 1-dimensional anyway. + // + // If there are real-world applications that need to assume that the + // C-data is in row-major mode, we should address that. + // + // TODO: + // To that end, perhaps we can introduce C++ tags cmajor_tag and + // rmajor_tag and define a custom concept from contiguous_range, by + // further conditioning on the storage tags. The caller then specifies + // the major storage mode when wrapping the C-style array into a range. + PyObject* xarray + = PyArray_New(&PyArray_Type, 1, &xsize, xtype, nullptr, + (void*)x.data(), 0, NPY_ARRAY_FARRAY, nullptr); + PyObject* yarray = PyArray_New(&PyArray_Type, 2, ysize, ytype, nullptr, + (void*)y.data(), 0, NPY_ARRAY_FARRAY, + nullptr); // column major by design! + + PyObject* pystring = PyString_FromString(fmt.c_str()); - detail::_interpreter::get(); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - // using numpy arrays - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_plot, plot_args); - // construct positional args - PyObject* args = PyTuple_New(2); - PyTuple_SetItem(args, 0, xarray); - PyTuple_SetItem(args, 1, yarray); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - // 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())); + return res; } - 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) -{ - 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); - PyObject* y2array = detail::get_array(y2); - - // construct positional args - PyObject* args = PyTuple_New(3); - PyTuple_SetItem(args, 0, xarray); - PyTuple_SetItem(args, 1, y1array); - PyTuple_SetItem(args, 2, y2array); - - // 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_fill_between, args, kwargs); - - Py_DECREF(args); - Py_DECREF(kwargs); - if(res) Py_DECREF(res); - - 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) -{ - detail::_interpreter::get(); - - PyObject* yarray = detail::get_array(y); - - PyObject* kwargs = PyDict_New(); - 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); - - PyTuple_SetItem(plot_args, 0, yarray); + template + bool plot(const ContainerX& x, const ContainerY& y, + const std::map& keywords) + { + assert(y.size() % x.size() == 0 + && "length of y must be a multiple of length of x!"); + detail::_interpreter::get(); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_hist, plot_args, kwargs); + NPY_TYPES xtype + = detail::select_npy_type::type; + NPY_TYPES ytype + = detail::select_npy_type::type; + npy_intp xsize = x.size(); + npy_intp yrows = xsize, ycols = y.size() / x.size(); + npy_intp ysize[] = {yrows, ycols}; // ysize[0] must equal xsize - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if(res) Py_DECREF(res); + // Same comments as above. + PyObject* xarray + = PyArray_New(&PyArray_Type, 1, &xsize, xtype, nullptr, + (void*)x.data(), 0, NPY_ARRAY_FARRAY, nullptr); + PyObject* yarray = PyArray_New(&PyArray_Type, 2, ysize, ytype, nullptr, + (void*)y.data(), 0, NPY_ARRAY_FARRAY, + nullptr); // column major by design! - return res; -} + // construct positional args + PyObject* args = PyTuple_New(2); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); -#ifndef WITHOUT_NUMPY -namespace detail { + // 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())); + } -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); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_plot, args, kwargs); - detail::_interpreter::get(); + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); - // 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)); + return res; + } - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + template + bool plot(const ContainerY& y, const std::string& format = "") { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + std::vector x(y.size()); + std::iota(x.begin(), x.end(), 0); + return plot(x, y, format); } - 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 detail - -inline void imshow(const unsigned char *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}, PyObject** out = nullptr) -{ - detail::imshow((void *) ptr, NPY_UINT8, rows, columns, colors, keywords, out); -} + template + bool plot(const ContainerY& y, + const std::map& keywords) + { + std::vector x(y.size()); + std::iota(x.begin(), x.end(), 0); + return plot(x, y, keywords); + } -inline void imshow(const float *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}, PyObject** out = nullptr) -{ - detail::imshow((void *) ptr, NPY_FLOAT, rows, columns, colors, keywords, out); -} +#else -#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); - } - - detail::imshow(image2.data, npy_type, image2.rows, image2.cols, image2.channels(), keywords); -} -#endif // WITH_OPENCV -#endif // WITHOUT_NUMPY + // For the less fortunate ones who can't or won't use C++ 20, we stick with + // vectors, since there's no ranges concept before that. -template -bool scatter(const std::vector& x, - const std::vector& y, - const double s=1.0, // The marker size in points**2 - const std::map & keywords = {}) -{ - detail::_interpreter::get(); + template + bool plot(const std::vector& x, const std::vector& y, + const std::map& keywords) + { + assert(y.size() % x.size() == 0 + && "length of y must be a multiple of length of x!"); - assert(x.size() == y.size()); + detail::_interpreter::get(); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + // using numpy arrays + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - 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())); - } + // construct positional args + PyObject* args = PyTuple_New(2); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); - PyObject* plot_args = PyTuple_New(2); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); + // 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_scatter, plot_args, kwargs); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_plot, args, kwargs); - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if(res) Py_DECREF(res); + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); - return res; -} + 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 = {}) + template + bool plot(const std::vector& x, const std::vector& y, + const std::string& s = "") { - detail::_interpreter::get(); + assert(y.size() % x.size() == 0 + && "length of y must be a multiple of length of x!"); - assert(x.size() == y.size()); + detail::_interpreter::get(); 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* pystring = PyString_FromString(s.c_str()); - PyObject* plot_args = PyTuple_New(2); + 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_scatter, plot_args, kwargs); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_plot, plot_args); 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, - const std::vector& z, - const double s=1.0, // The marker size in points**2 - 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 - // 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 = {}, - const std::map & keywords = {}) -{ - detail::_interpreter::get(); - - PyObject* listlist = detail::get_listlist(data); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, listlist); - - PyObject* kwargs = PyDict_New(); - // kwargs needs the labels, if there are (the correct number of) labels - if (!labels.empty() && labels.size() == data.size()) { - PyDict_SetItemString(kwargs, "labels", detail::get_array(labels)); + template + bool plot(const std::vector& y, const std::string& format = "") + { + std::vector x(y.size()); + for(size_t i = 0; i < x.size(); ++i) x.at(i) = i; + return plot(x, y, format); } - // take care of the remaining keywords - for (const auto& it : keywords) + template + bool plot(const std::vector& y, + const std::map& keywords) { - PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); + std::vector x(y.size()); + for(size_t i = 0; i < x.size(); ++i) x.at(i) = i; + return plot(x, y, keywords); } - 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::map & keywords = {}) -{ - detail::_interpreter::get(); - - PyObject* vector = detail::get_array(data); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, vector); +#endif - PyObject* kwargs = PyDict_New(); - for (const auto& it : keywords) +// TODO - it should be possible to make this work by implementing +// a non-numpy alternative for `detail::get_2darray()`. +#ifndef WITHOUT_NUMPY + 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(), + const long fig_number = 0) { - 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); + detail::_interpreter::get(); - Py_DECREF(args); - Py_DECREF(kwargs); + // 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!"); + } + } - if(res) Py_DECREF(res); + assert(x.size() == y.size()); + assert(y.size() == z.size()); - return res; -} + // using numpy arrays + PyObject* xarray = detail::get_2darray(x); + PyObject* yarray = detail::get_2darray(y); + PyObject* zarray = detail::get_2darray(z); -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 = {}) -{ - detail::_interpreter::get(); + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + PyTuple_SetItem(args, 2, zarray); - PyObject * xarray = detail::get_array(x); - PyObject * yarray = detail::get_array(y); + // 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) { + 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 * kwargs = PyDict_New(); + 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."); - 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* gca_kwargs = PyDict_New(); + PyDict_SetItemString(gca_kwargs, "projection", + PyString_FromString("3d")); - 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* 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); - PyObject * plot_args = PyTuple_New(2); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); + if(!axis) throw std::runtime_error("No axis"); + Py_INCREF(axis); - PyObject * res = PyObject_Call( - detail::_interpreter::get().s_python_function_bar, plot_args, kwargs); + Py_DECREF(gca); + Py_DECREF(gca_kwargs); - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if (res) Py_DECREF(res); + 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); - return res; -} + Py_DECREF(axis); + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + } -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; + 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(); - detail::_interpreter::get(); + // using numpy arrays + PyObject* xarray = detail::get_2darray(x); + PyObject* yarray = detail::get_2darray(y); + PyObject* zarray = detail::get_2darray(z); - std::vector x; - for (std::size_t i = 0; i < y.size(); i++) { x.push_back(i); } + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + PyTuple_SetItem(args, 2, zarray); - return bar(x, y, ec, ls, lw, keywords); -} + // Build up the kw args. + PyObject* kwargs = PyDict_New(); + PyObject* python_colormap_coolwarm = PyObject_GetAttrString( + detail::_interpreter::get().s_python_colormap, "coolwarm"); -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); + PyDict_SetItemString(kwargs, "cmap", python_colormap_coolwarm); - 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, "ec", PyString_FromString(ec.c_str())); - PyDict_SetItemString(kwargs, "ls", PyString_FromString(ls.c_str())); - PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_contour, args, + kwargs); + if(!res) throw std::runtime_error("failed contour"); - 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())); + Py_DECREF(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); - - PyObject *res = PyObject_Call(detail::_interpreter::get().s_python_function_barh, plot_args, kwargs); + 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(); - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if (res) Py_DECREF(res); + PyObject* xarray = detail::get_2darray(x); - return res; -} + 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); -inline bool subplots_adjust(const std::map& keywords = {}) -{ - detail::_interpreter::get(); + PyObject* res + = PyObject_Call(detail::_interpreter::get().s_python_function_spy, + plot_args, kwargs); - 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)); + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); } +#endif // WITHOUT_NUMPY + template + void plot3(const std::vector& x, const std::vector& y, + const std::vector& z, + const std::map& keywords + = std::map(), + const long fig_number = 0) + { + detail::_interpreter::get(); - PyObject* plot_args = PyTuple_New(0); + // 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* res = PyObject_Call(detail::_interpreter::get().s_python_function_subplots_adjust, plot_args, kwargs); + assert(x.size() == y.size()); + assert(y.size() == z.size()); - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if(res) Py_DECREF(res); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + PyObject* zarray = detail::get_array(z); - return res; -} + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + PyTuple_SetItem(args, 2, zarray); -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(); + // Build up the kw args. + PyObject* kwargs = PyDict_New(); - PyObject* yarray = detail::get_array(y); + 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* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "label", PyString_FromString(label.c_str())); - PyDict_SetItemString(kwargs, "bins", PyLong_FromLong(bins)); - PyDict_SetItemString(kwargs, "color", PyString_FromString(color.c_str())); - PyDict_SetItemString(kwargs, "alpha", PyFloat_FromDouble(alpha)); + 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(); + PyDict_SetItemString(gca_kwargs, "projection", + PyString_FromString("3d")); - PyObject* plot_args = PyTuple_New(1); - PyTuple_SetItem(plot_args, 0, yarray); + 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); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_hist, plot_args, kwargs); + if(!axis) throw std::runtime_error("No axis"); + Py_INCREF(axis); - Py_DECREF(plot_args); - Py_DECREF(kwargs); - if(res) Py_DECREF(res); + Py_DECREF(gca); + Py_DECREF(gca_kwargs); - return res; -} + 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); -template -bool plot(const std::vector& x, const std::vector& y, const std::string& s = "") -{ - assert(x.size() == y.size()); + Py_DECREF(axis); + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + } - detail::_interpreter::get(); + template + bool stem(const std::vector& x, const std::vector& y, + const std::map& keywords) + { + assert(x.size() == y.size()); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + detail::_interpreter::get(); - PyObject* pystring = PyString_FromString(s.c_str()); + // using numpy arrays + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + // construct positional args + PyObject* args = PyTuple_New(2); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); + // 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())); + } - Py_DECREF(plot_args); - if(res) Py_DECREF(res); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_stem, args, kwargs); - return res; -} + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(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()); + return res; + } - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); - PyObject* zarray = detail::get_array(z); + template + bool fill(const std::vector& x, const std::vector& y, + const std::map& keywords) + { + assert(x.size() == y.size()); - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, zarray); + detail::_interpreter::get(); + + // using numpy arrays + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::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); - // 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())); + if(res) Py_DECREF(res); + + return res; } - PyObject* res = - PyObject_Call(detail::_interpreter::get().s_python_function_contour, plot_args, kwargs); + template + bool fill_between(const std::vector& x, + const std::vector& y1, + const std::vector& y2, + const std::map& keywords) + { + assert(x.size() == y1.size()); + assert(x.size() == y2.size()); - Py_DECREF(kwargs); - Py_DECREF(plot_args); - if (res) - Py_DECREF(res); + detail::_interpreter::get(); - return res; -} + // using numpy arrays + PyObject* xarray = detail::get_array(x); + PyObject* y1array = detail::get_array(y1); + PyObject* y2array = detail::get_array(y2); -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()); + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, y1array); + PyTuple_SetItem(args, 2, y2array); + + // 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())); + } - detail::_interpreter::get(); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_fill_between, args, + kwargs); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); - PyObject* uarray = detail::get_array(u); - PyObject* warray = detail::get_array(w); + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); - 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); + return res; + } - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + 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) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + 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; } - PyObject* res = PyObject_Call( - detail::_interpreter::get().s_python_function_quiver, plot_args, kwargs); + template + bool hist(const std::vector& y, long bins = 10, + std::string color = "b", double alpha = 1.0, + bool cumulative = false) + { + detail::_interpreter::get(); - Py_DECREF(kwargs); - Py_DECREF(plot_args); - if (res) - Py_DECREF(res); + PyObject* yarray = detail::get_array(y); - return res; -} + PyObject* kwargs = PyDict_New(); + 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); -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 = "") -{ - assert(x.size() == y.size()); + PyObject* plot_args = PyTuple_New(1); - detail::_interpreter::get(); + PyTuple_SetItem(plot_args, 0, yarray); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + PyObject* res + = PyObject_Call(detail::_interpreter::get().s_python_function_hist, + plot_args, kwargs); - PyObject* pystring = PyString_FromString(s.c_str()); + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + return res; + } - PyObject* res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_stem, plot_args); +#ifndef WITHOUT_NUMPY + namespace detail + { + 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(); + + // 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); + } - Py_DECREF(plot_args); - if (res) - Py_DECREF(res); + } // namespace detail - return res; -} + void imshow(const unsigned char* ptr, const int rows, const int columns, + const int colors, + const std::map& keywords = {}, + PyObject** out = nullptr) + { + detail::imshow((void*)ptr, NPY_UINT8, rows, columns, colors, keywords, + out); + } -template -bool semilogx(const std::vector& x, const std::vector& y, const std::string& s = "") -{ - assert(x.size() == y.size()); + void imshow(const float* ptr, const int rows, const int columns, + const int colors, + const std::map& keywords = {}, + PyObject** out = nullptr) + { + detail::imshow((void*)ptr, NPY_FLOAT, rows, columns, colors, keywords, + out); + } - detail::_interpreter::get(); +#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); + } - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + detail::imshow(image2.data, npy_type, image2.rows, image2.cols, + image2.channels(), keywords); + } +#endif // WITH_OPENCV +#endif // WITHOUT_NUMPY - PyObject* pystring = PyString_FromString(s.c_str()); + template + bool scatter(const std::vector& x, const std::vector& y, + 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* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + 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* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_semilogx, plot_args); + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); - Py_DECREF(plot_args); - if(res) Py_DECREF(res); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_scatter, plot_args, + kwargs); - return res; -} + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); -template -bool semilogy(const std::vector& x, const std::vector& y, const std::string& s = "") -{ - assert(x.size() == y.size()); + return res; + } - detail::_interpreter::get(); + 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(); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + assert(x.size() == y.size()); - PyObject* pystring = PyString_FromString(s.c_str()); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + PyObject* colors_array = detail::get_array(colors); - 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* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "s", PyLong_FromLong(s)); + PyDict_SetItemString(kwargs, "c", colors_array); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_semilogy, plot_args); + for(const auto& it : keywords) { + PyDict_SetItemString(kwargs, it.first.c_str(), + PyString_FromString(it.second.c_str())); + } - Py_DECREF(plot_args); - 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_scatter, plot_args, + kwargs); -template -bool loglog(const std::vector& x, const std::vector& y, const std::string& s = "") -{ - assert(x.size() == y.size()); + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); - detail::_interpreter::get(); + return res; + } - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + 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 std::map& keywords = {}, + const long fig_number = 0) + { + detail::_interpreter::get(); - PyObject* pystring = PyString_FromString(s.c_str()); + // 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* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + assert(x.size() == y.size()); + assert(y.size() == z.size()); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_loglog, plot_args); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + PyObject* zarray = detail::get_array(z); - Py_DECREF(plot_args); - if(res) Py_DECREF(res); + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + PyTuple_SetItem(args, 2, zarray); - return res; -} + // Build up the kw args. + PyObject* kwargs = PyDict_New(); -template -bool errorbar(const std::vector &x, const std::vector &y, const std::vector &yerr, const std::map &keywords = {}) -{ - assert(x.size() == y.size()); + 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 = {}, + const std::map& keywords = {}) + { + detail::_interpreter::get(); + + PyObject* listlist = detail::get_listlist(data); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, listlist); + + PyObject* kwargs = PyDict_New(); + + // kwargs needs the labels, if there are (the correct number of) labels + if(!labels.empty() && labels.size() == data.size()) { + PyDict_SetItemString(kwargs, "labels", detail::get_array(labels)); + } - detail::_interpreter::get(); + // 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); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); - PyObject* yerrarray = detail::get_array(yerr); + return res; + } - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + template + bool boxplot(const std::vector& data, + const std::map& keywords = {}) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + detail::_interpreter::get(); + + PyObject* vector = detail::get_array(data); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, vector); + + PyObject* kwargs = PyDict_New(); + for(const auto& it : keywords) { + PyDict_SetItemString(kwargs, it.first.c_str(), + PyString_FromString(it.second.c_str())); + } + + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_boxplot, args, + kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + + if(res) Py_DECREF(res); + + return res; } - PyDict_SetItemString(kwargs, "yerr", yerrarray); + 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 = {}) + { + detail::_interpreter::get(); - PyObject *plot_args = PyTuple_New(2); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - PyObject *res = PyObject_Call(detail::_interpreter::get().s_python_function_errorbar, plot_args, kwargs); + PyObject* kwargs = PyDict_New(); - Py_DECREF(kwargs); - Py_DECREF(plot_args); + 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)); - if (res) - Py_DECREF(res); - else - throw std::runtime_error("Call to errorbar() failed."); + 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())); + } - return res; -} + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); -template -bool named_plot(const std::string& name, const std::vector& y, const std::string& format = "") -{ - detail::_interpreter::get(); + 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; + } + + 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; + + detail::_interpreter::get(); + + std::vector x; + for(std::size_t i = 0; i < y.size(); i++) { x.push_back(i); } + + 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; + } + + bool subplots_adjust(const std::map& keywords = {}) + { + detail::_interpreter::get(); + + 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 + 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(); + PyDict_SetItemString(kwargs, "label", + PyString_FromString(label.c_str())); + PyDict_SetItemString(kwargs, "bins", PyLong_FromLong(bins)); + 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); + + PyObject* res + = PyObject_Call(detail::_interpreter::get().s_python_function_hist, + plot_args, kwargs); + + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + + 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 = 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); + 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 = {}) + { + 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); + PyObject* warray = detail::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(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& 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 = "") + { + assert(x.size() == y.size()); + + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(s.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_CallObject( + detail::_interpreter::get().s_python_function_stem, plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; + } + + template + bool semilogx(const std::vector& x, + const std::vector& y, const std::string& s = "") + { + assert(x.size() == y.size()); + + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(s.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_CallObject( + detail::_interpreter::get().s_python_function_semilogx, plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; + } + + template + bool semilogy(const std::vector& x, + const std::vector& y, const std::string& s = "") + { + assert(x.size() == y.size()); - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + detail::_interpreter::get(); - PyObject* yarray = detail::get_array(y); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(s.c_str()); - 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* plot_args = PyTuple_New(2); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_semilogy, plot_args); - PyTuple_SetItem(plot_args, 0, yarray); - PyTuple_SetItem(plot_args, 1, pystring); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, plot_args, kwargs); + return res; + } - Py_DECREF(kwargs); - Py_DECREF(plot_args); - if (res) Py_DECREF(res); + template + bool loglog(const std::vector& x, const std::vector& y, + const std::string& s = "") + { + assert(x.size() == y.size()); + + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(s.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_CallObject( + detail::_interpreter::get().s_python_function_loglog, plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; + } + + template + bool errorbar(const std::vector& x, + const std::vector& y, + const std::vector& yerr, + const std::map& keywords = {}) + { + 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); + + // 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* 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_errorbar, plot_args, + kwargs); + + Py_DECREF(kwargs); + Py_DECREF(plot_args); - return res; -} + if(res) + Py_DECREF(res); + else + throw std::runtime_error("Call to errorbar() failed."); -template -bool named_plot(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") -{ - detail::_interpreter::get(); + return res; + } - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + template + bool named_plot(const std::string& name, const std::vector& y, + const std::string& format = "") + { + detail::_interpreter::get(); - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", + PyString_FromString(name.c_str())); - PyObject* pystring = PyString_FromString(format.c_str()); + PyObject* yarray = detail::get_array(y); - 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* pystring = PyString_FromString(format.c_str()); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, plot_args, kwargs); + PyObject* plot_args = PyTuple_New(2); - Py_DECREF(kwargs); - Py_DECREF(plot_args); - if (res) Py_DECREF(res); + PyTuple_SetItem(plot_args, 0, yarray); + PyTuple_SetItem(plot_args, 1, pystring); - return res; -} + PyObject* res + = PyObject_Call(detail::_interpreter::get().s_python_function_plot, + plot_args, kwargs); -template -bool named_semilogx(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") -{ - detail::_interpreter::get(); + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + return res; + } - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + template + bool named_plot(const std::string& name, const std::vector& x, + const std::vector& y, + const std::string& format = "") + { + detail::_interpreter::get(); - PyObject* pystring = PyString_FromString(format.c_str()); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", + PyString_FromString(name.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* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_semilogx, plot_args, kwargs); + PyObject* pystring = PyString_FromString(format.c_str()); - Py_DECREF(kwargs); - Py_DECREF(plot_args); - if (res) Py_DECREF(res); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - return res; -} + PyObject* res + = PyObject_Call(detail::_interpreter::get().s_python_function_plot, + plot_args, kwargs); -template -bool named_semilogy(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") -{ - detail::_interpreter::get(); + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + return res; + } - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + template + bool named_semilogx(const std::string& name, const std::vector& x, + const std::vector& y, + const std::string& format = "") + { + detail::_interpreter::get(); - PyObject* pystring = PyString_FromString(format.c_str()); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", + PyString_FromString(name.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* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_semilogy, plot_args, kwargs); + PyObject* pystring = PyString_FromString(format.c_str()); - Py_DECREF(kwargs); - Py_DECREF(plot_args); - if (res) Py_DECREF(res); + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); - return res; -} + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_semilogx, plot_args, + kwargs); -template -bool named_loglog(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") -{ - detail::_interpreter::get(); + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + return res; + } - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + template + bool named_semilogy(const std::string& name, const std::vector& x, + const std::vector& y, + const std::string& format = "") + { + detail::_interpreter::get(); - PyObject* pystring = PyString_FromString(format.c_str()); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", + PyString_FromString(name.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_loglog, plot_args, kwargs); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - Py_DECREF(kwargs); - Py_DECREF(plot_args); - if (res) Py_DECREF(res); + PyObject* pystring = PyString_FromString(format.c_str()); - return res; -} + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); -template -bool plot(const std::vector& y, const std::string& format = "") -{ - std::vector x(y.size()); - for(size_t i=0; i -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 = "") -{ - std::vector x(y.size()); - for (size_t i = 0; i < x.size(); ++i) x.at(i) = i; - return stem(x, y, format); -} + return res; + } -template -void text(Numeric x, Numeric y, const std::string& s = "") -{ - detail::_interpreter::get(); + template + bool named_loglog(const std::string& name, const std::vector& x, + const std::vector& y, + const std::string& format = "") + { + detail::_interpreter::get(); - 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(s.c_str())); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", + PyString_FromString(name.c_str())); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_text, args); - if(!res) throw std::runtime_error("Call to text() failed."); + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); - Py_DECREF(args); - Py_DECREF(res); -} + PyObject* pystring = PyString_FromString(format.c_str()); -inline void colorbar(PyObject* mappable = NULL, const std::map& keywords = {}) -{ - if (mappable == NULL) - throw std::runtime_error("Must call colorbar with PyObject* returned from an image, contour, surface, etc."); + PyObject* 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_loglog, plot_args, + kwargs); - detail::_interpreter::get(); + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, mappable); + return res; + } - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + template + bool stem(const std::vector& y, const std::string& format = "") { - PyDict_SetItemString(kwargs, it->first.c_str(), PyFloat_FromDouble(it->second)); + std::vector x(y.size()); + for(size_t i = 0; i < x.size(); ++i) x.at(i) = i; + return stem(x, y, format); } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_colorbar, args, kwargs); - if(!res) throw std::runtime_error("Call to colorbar() failed."); + template + void text(Numeric x, Numeric y, const std::string& s = "") + { + detail::_interpreter::get(); - Py_DECREF(args); - Py_DECREF(kwargs); - Py_DECREF(res); -} + 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(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."); -inline long figure(long number = -1) -{ - detail::_interpreter::get(); + Py_DECREF(args); + Py_DECREF(res); + } - 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); + 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."); - // 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); - } + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, mappable); - if(!res) throw std::runtime_error("Call to figure() failed."); + 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* 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); + 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(num); - Py_DECREF(res); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); + } - return figureNumber; -} + long figure(long number = -1) + { + detail::_interpreter::get(); -inline bool fignum_exists(long number) -{ - detail::_interpreter::get(); + 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); - 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."); + // Make sure interpreter is initialised + detail::_interpreter::get(); - bool ret = PyObject_IsTrue(res); - Py_DECREF(res); - Py_DECREF(args); + 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); + } - return ret; -} + if(!res) throw std::runtime_error("Call to figure() failed."); -inline void figure_size(size_t w, size_t h) -{ - detail::_interpreter::get(); + 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); - 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)); + Py_DECREF(num); + Py_DECREF(res); - PyObject* kwargs = PyDict_New(); - PyDict_SetItemString(kwargs, "figsize", size); - PyDict_SetItemString(kwargs, "dpi", PyLong_FromSize_t(dpi)); + return figureNumber; + } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_figure, - detail::_interpreter::get().s_python_empty_tuple, kwargs); + bool fignum_exists(long number) + { + detail::_interpreter::get(); - Py_DECREF(kwargs); + 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."); - if(!res) throw std::runtime_error("Call to figure_size() failed."); - Py_DECREF(res); -} + bool ret = PyObject_IsTrue(res); + Py_DECREF(res); + Py_DECREF(args); -inline void legend() -{ - detail::_interpreter::get(); + return ret; + } - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_legend, detail::_interpreter::get().s_python_empty_tuple); - if(!res) throw std::runtime_error("Call to legend() failed."); + void figure_size(size_t w, size_t h) + { + detail::_interpreter::get(); - Py_DECREF(res); -} + 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)); -inline void legend(const std::map& keywords) -{ - detail::_interpreter::get(); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "figsize", size); + PyDict_SetItemString(kwargs, "dpi", PyLong_FromSize_t(dpi)); - // 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_figure, + detail::_interpreter::get().s_python_empty_tuple, kwargs); - 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(kwargs); - Py_DECREF(res); -} + if(!res) throw std::runtime_error("Call to figure_size() failed."); + Py_DECREF(res); + } -template -inline void set_aspect(Numeric ratio) -{ - detail::_interpreter::get(); + void legend() + { + detail::_interpreter::get(); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, PyFloat_FromDouble(ratio)); - PyObject* kwargs = PyDict_New(); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_legend, + detail::_interpreter::get().s_python_empty_tuple); + if(!res) throw std::runtime_error("Call to legend() failed."); - 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); + Py_DECREF(res); + } - PyObject *set_aspect = PyObject_GetAttrString(ax, "set_aspect"); - if (!set_aspect) throw std::runtime_error("Attribute set_aspect not found."); - Py_INCREF(set_aspect); + void legend(const std::map& keywords) + { + detail::_interpreter::get(); - PyObject *res = PyObject_Call(set_aspect, args, kwargs); - if (!res) throw std::runtime_error("Call to set_aspect() failed."); - Py_DECREF(set_aspect); + // 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())); + } - Py_DECREF(ax); - Py_DECREF(args); - Py_DECREF(kwargs); -} + 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."); -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) -{ - detail::_interpreter::get(); + Py_DECREF(kwargs); + Py_DECREF(res); + } - PyObject* list = PyList_New(2); - PyList_SetItem(list, 0, PyFloat_FromDouble(left)); - PyList_SetItem(list, 1, PyFloat_FromDouble(right)); + template + void legend(const Labels& labels, const std::map& keywords={}) + { + detail::_interpreter::get(); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, list); + PyObject* llist = PyList_New(labels.size()); + if(llist == nullptr) + throw "Can't allocate labels list in legend() function."; - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_ylim, args); - if(!res) throw std::runtime_error("Call to ylim() failed."); + // iterate over labels, hopefully not too many! + size_t i=0; + for (const auto& l : labels) { + PyObject* str = PyString_FromString(l.c_str()); + PyList_SET_ITEM(llist, i++, str); + } - Py_DECREF(args); - Py_DECREF(res); -} + // 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())); + } -template -void xlim(Numeric left, Numeric right) -{ - detail::_interpreter::get(); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, llist); - PyObject* list = PyList_New(2); - PyList_SetItem(list, 0, PyFloat_FromDouble(left)); - PyList_SetItem(list, 1, PyFloat_FromDouble(right)); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_legend, + args, kwargs); + // 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."); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, list); + Py_DECREF(kwargs); + Py_DECREF(res); + } - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_xlim, args); - if(!res) throw std::runtime_error("Call to xlim() failed."); + template + void set_aspect(Numeric ratio) + { + detail::_interpreter::get(); - Py_DECREF(args); - Py_DECREF(res); -} + 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); -inline std::array xlim() -{ - PyObject* args = PyTuple_New(0); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_xlim, args); + PyObject* set_aspect = PyObject_GetAttrString(ax, "set_aspect"); + if(!set_aspect) + throw std::runtime_error("Attribute set_aspect not found."); + Py_INCREF(set_aspect); - if(!res) throw std::runtime_error("Call to xlim() failed."); + 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(res); + Py_DECREF(ax); + Py_DECREF(args); + Py_DECREF(kwargs); + } - PyObject* left = PyTuple_GetItem(res,0); - PyObject* right = PyTuple_GetItem(res,1); - return { PyFloat_AsDouble(left), PyFloat_AsDouble(right) }; -} + 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(); -inline std::array ylim() -{ - PyObject* args = PyTuple_New(0); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_ylim, args); + 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); - if(!res) throw std::runtime_error("Call to ylim() failed."); + PyObject* set_aspect = PyObject_GetAttrString(ax, "set_aspect"); + if(!set_aspect) + throw std::runtime_error("Attribute set_aspect not found."); + Py_INCREF(set_aspect); - Py_DECREF(res); + PyObject* res = PyObject_Call(set_aspect, args, kwargs); + if(!res) throw std::runtime_error("Call to set_aspect() failed."); + Py_DECREF(set_aspect); - PyObject* left = PyTuple_GetItem(res,0); - PyObject* right = PyTuple_GetItem(res,1); - return { PyFloat_AsDouble(left), PyFloat_AsDouble(right) }; -} + Py_DECREF(ax); + Py_DECREF(args); + Py_DECREF(kwargs); + } -template -inline void xticks(const std::vector &ticks, const std::vector &labels = {}, const std::map& keywords = {}) -{ - assert(labels.size() == 0 || ticks.size() == labels.size()); + template + void ylim(Numeric left, Numeric right) + { + detail::_interpreter::get(); - detail::_interpreter::get(); + PyObject* list = PyList_New(2); + PyList_SetItem(list, 0, PyFloat_FromDouble(left)); + PyList_SetItem(list, 1, PyFloat_FromDouble(right)); - // using numpy array - PyObject* ticksarray = detail::get_array(ticks); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, list); - 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())); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_ylim, args); + if(!res) throw std::runtime_error("Call to ylim() failed."); - // construct positional args - args = PyTuple_New(2); - PyTuple_SetItem(args, 0, ticksarray); - PyTuple_SetItem(args, 1, labelstuple); + Py_DECREF(args); + Py_DECREF(res); } - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + template + void xlim(Numeric left, Numeric right) { - 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); + detail::_interpreter::get(); - Py_DECREF(args); - Py_DECREF(kwargs); - if(!res) throw std::runtime_error("Call to xticks() failed"); + PyObject* list = PyList_New(2); + PyList_SetItem(list, 0, PyFloat_FromDouble(left)); + PyList_SetItem(list, 1, PyFloat_FromDouble(right)); - Py_DECREF(res); -} + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, list); -template -inline void xticks(const std::vector &ticks, const std::map& keywords) -{ - xticks(ticks, {}, keywords); -} + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_xlim, args); + if(!res) throw std::runtime_error("Call to xlim() failed."); -template -inline void yticks(const std::vector &ticks, const std::vector &labels = {}, const std::map& keywords = {}) -{ - assert(labels.size() == 0 || ticks.size() == labels.size()); + Py_DECREF(args); + Py_DECREF(res); + } - detail::_interpreter::get(); + std::array xlim() + { + PyObject* args = PyTuple_New(0); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_xlim, args); - // using numpy array - PyObject* ticksarray = detail::get_array(ticks); + if(!res) throw std::runtime_error("Call to xlim() failed."); - 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())); + Py_DECREF(res); - // construct positional args - args = PyTuple_New(2); - PyTuple_SetItem(args, 0, ticksarray); - PyTuple_SetItem(args, 1, labelstuple); + PyObject* left = PyTuple_GetItem(res, 0); + PyObject* right = PyTuple_GetItem(res, 1); + return {PyFloat_AsDouble(left), PyFloat_AsDouble(right)}; } - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + std::array ylim() { - PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); - } + PyObject* args = PyTuple_New(0); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_ylim, args); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_yticks, args, kwargs); + if(!res) throw std::runtime_error("Call to ylim() failed."); - Py_DECREF(args); - Py_DECREF(kwargs); - if(!res) throw std::runtime_error("Call to yticks() failed"); + Py_DECREF(res); - Py_DECREF(res); -} + PyObject* left = PyTuple_GetItem(res, 0); + PyObject* right = PyTuple_GetItem(res, 1); + return {PyFloat_AsDouble(left), PyFloat_AsDouble(right)}; + } -template -inline void yticks(const std::vector &ticks, const std::map& keywords) -{ - yticks(ticks, {}, keywords); -} + template + void xticks(const std::vector& ticks, + const std::vector& labels = {}, + const std::map& keywords = {}) + { + assert(labels.size() == 0 || ticks.size() == labels.size()); -template inline void margins(Numeric margin) -{ - // construct positional args - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, PyFloat_FromDouble(margin)); + detail::_interpreter::get(); - PyObject* res = - PyObject_CallObject(detail::_interpreter::get().s_python_function_margins, args); - if (!res) - throw std::runtime_error("Call to margins() failed."); + // using numpy array + PyObject* ticksarray = detail::get_array(ticks); - Py_DECREF(args); - Py_DECREF(res); -} + 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); + } -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)); + // construct keyword args + // https://docs.python.org/3/c-api/dict.html + // https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.xticks.html + // https://matplotlib.org/stable/api/text_api.html#matplotlib.text.Text + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it + = keywords.begin(); + it != keywords.end(); ++it) { + const auto& [key, val] = *it; + try { + // if it's numeric, add it as number, e.g. rotation = 45 + // https://matplotlib.org/stable/api/text_api.html#matplotlib.text.Text + double x = std::stod(val); + PyDict_SetItemString(kwargs, key.c_str(), PyFloat_FromDouble(x)); + } + catch (const std::exception& e) { + // if it wasn't a number, add it as string + // TODO: allow for other types, e.g. bool + // https://matplotlib.org/stable/api/text_api.html#matplotlib.text.Text + PyDict_SetItemString(kwargs, key.c_str(), + PyString_FromString(val.c_str())); + + } + // PyDict_SetItemString(kwargs, it->first.c_str(), + // PyString_FromString(it->second.c_str())); + } - PyObject* res = - PyObject_CallObject(detail::_interpreter::get().s_python_function_margins, args); - if (!res) - throw std::runtime_error("Call to margins() failed."); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_xticks, args, kwargs); - Py_DECREF(args); - Py_DECREF(res); -} + Py_DECREF(args); + Py_DECREF(kwargs); + if(!res) throw std::runtime_error("Call to xticks() failed"); + Py_DECREF(res); + } -inline void tick_params(const std::map& keywords, const std::string axis = "both") -{ - detail::_interpreter::get(); + template + void xticks(const std::vector& ticks, + const std::map& keywords) + { + xticks(ticks, {}, keywords); + } - // construct positional args - PyObject* args; - args = PyTuple_New(1); - PyTuple_SetItem(args, 0, PyString_FromString(axis.c_str())); + // options only, e.g. plt::xticks(rotation=20) + void xticks(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* args = PyTuple_New(0); + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(const auto& [key, val] : keywords){ + try { + // if it's numeric, add it as number, e.g. rotation = 45 + // https://matplotlib.org/stable/api/text_api.html#matplotlib.text.Text + double x = std::stod(val); + PyDict_SetItemString(kwargs, key.c_str(), PyFloat_FromDouble(x)); + } + catch (const std::exception& e) { + // if it wasn't a number, add it as string + // TODO: allow for other types, e.g. bool + // https://matplotlib.org/stable/api/text_api.html#matplotlib.text.Text + PyDict_SetItemString(kwargs, key.c_str(), + PyString_FromString(val.c_str())); + } + } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_tick_params, args, kwargs); + // PyDict_SetItemString(kwargs, k.c_str(), + // PyString_FromString(v.c_str())); - Py_DECREF(args); - Py_DECREF(kwargs); - if (!res) throw std::runtime_error("Call to tick_params() failed"); + auto* xticks = detail::_interpreter::get().s_python_function_xticks; + PyObject* res = PyObject_Call(xticks, args, kwargs); + // PyObject* res = PyObject_Call( + // detail::_interpreter::get().s_python_function_xticks, args, kwargs); - Py_DECREF(res); -} + Py_DECREF(kwargs); + if(!res) throw std::runtime_error("Call to xticks() failed"); -inline void subplot(long nrows, long ncols, long plot_number) -{ - detail::_interpreter::get(); + Py_DECREF(res); + } - // construct positional args - PyObject* args = PyTuple_New(3); - PyTuple_SetItem(args, 0, PyFloat_FromDouble(nrows)); - PyTuple_SetItem(args, 1, PyFloat_FromDouble(ncols)); - PyTuple_SetItem(args, 2, PyFloat_FromDouble(plot_number)); + template + void yticks(const std::vector& ticks, + const std::vector& labels = {}, + const std::map& keywords = {}) + { + assert(labels.size() == 0 || ticks.size() == labels.size()); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_subplot, args); - if(!res) throw std::runtime_error("Call to subplot() failed."); + detail::_interpreter::get(); - Py_DECREF(args); - Py_DECREF(res); -} + // using numpy array + PyObject* ticksarray = detail::get_array(ticks); -inline void subplot2grid(long nrows, long ncols, long rowid=0, long colid=0, long rowspan=1, long colspan=1) -{ - detail::_interpreter::get(); + 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); + } - PyObject* shape = PyTuple_New(2); - PyTuple_SetItem(shape, 0, PyLong_FromLong(nrows)); - PyTuple_SetItem(shape, 1, PyLong_FromLong(ncols)); + // 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* loc = PyTuple_New(2); - PyTuple_SetItem(loc, 0, PyLong_FromLong(rowid)); - PyTuple_SetItem(loc, 1, PyLong_FromLong(colid)); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_yticks, args, kwargs); - 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)); + Py_DECREF(args); + Py_DECREF(kwargs); + if(!res) throw std::runtime_error("Call to yticks() failed"); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_subplot2grid, args); - if(!res) throw std::runtime_error("Call to subplot2grid() failed."); + Py_DECREF(res); + } - Py_DECREF(shape); - Py_DECREF(loc); - Py_DECREF(args); - Py_DECREF(res); -} + template + void yticks(const std::vector& ticks, + const std::map& keywords) + { + yticks(ticks, {}, keywords); + } -inline void title(const std::string &titlestr, const std::map &keywords = {}) -{ - detail::_interpreter::get(); + template + void margins(Numeric margin) + { + // construct positional args + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(margin)); - 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_margins, args); + if(!res) throw std::runtime_error("Call to margins() failed."); - 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())); + Py_DECREF(args); + Py_DECREF(res); } - 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, const std::map &keywords = {}) -{ - detail::_interpreter::get(); + template + 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* 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_margins, args); + if(!res) throw std::runtime_error("Call to margins() failed."); - 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())); + Py_DECREF(args); + Py_DECREF(res); } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_suptitle, args, kwargs); - if(!res) throw std::runtime_error("Call to suptitle() failed."); + void tick_params(const std::map& keywords, + const std::string axis = "both") + { + detail::_interpreter::get(); + + // construct positional args + PyObject* args; + args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyString_FromString(axis.c_str())); - Py_DECREF(args); - Py_DECREF(kwargs); - Py_DECREF(res); -} + // 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())); + } -inline void axis(const std::string &axisstr) -{ - detail::_interpreter::get(); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_tick_params, args, + kwargs); - PyObject* str = PyString_FromString(axisstr.c_str()); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, str); + Py_DECREF(args); + Py_DECREF(kwargs); + if(!res) throw std::runtime_error("Call to tick_params() failed"); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_axis, args); - if(!res) throw std::runtime_error("Call to title() failed."); + Py_DECREF(res); + } - Py_DECREF(args); - Py_DECREF(res); -} + void subplot(long nrows, long ncols, long plot_number) + { + detail::_interpreter::get(); -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(nrows)); + PyTuple_SetItem(args, 1, PyFloat_FromDouble(ncols)); + PyTuple_SetItem(args, 2, PyFloat_FromDouble(plot_number)); - // 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)); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_subplot, args); + if(!res) throw std::runtime_error("Call to subplot() failed."); - // 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())); + Py_DECREF(args); + Py_DECREF(res); } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_axhline, args, kwargs); + void subplot2grid(long nrows, long ncols, long rowid = 0, long colid = 0, + long rowspan = 1, long colspan = 1) + { + detail::_interpreter::get(); - Py_DECREF(args); - Py_DECREF(kwargs); + PyObject* shape = PyTuple_New(2); + PyTuple_SetItem(shape, 0, PyLong_FromLong(nrows)); + PyTuple_SetItem(shape, 1, PyLong_FromLong(ncols)); - if(res) Py_DECREF(res); -} + PyObject* loc = PyTuple_New(2); + PyTuple_SetItem(loc, 0, PyLong_FromLong(rowid)); + PyTuple_SetItem(loc, 1, PyLong_FromLong(colid)); -inline void axvline(double x, double ymin = 0., double ymax = 1., const std::map& keywords = std::map()) -{ - detail::_interpreter::get(); + 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)); - // 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)); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_subplot2grid, args); + if(!res) throw std::runtime_error("Call to subplot2grid() failed."); - // 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())); + Py_DECREF(shape); + Py_DECREF(loc); + Py_DECREF(args); + Py_DECREF(res); } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_axvline, args, kwargs); - - Py_DECREF(args); - Py_DECREF(kwargs); + void title(const std::string& titlestr, + const std::map& keywords = {}) + { + detail::_interpreter::get(); - if(res) Py_DECREF(res); -} + PyObject* pytitlestr = PyString_FromString(titlestr.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pytitlestr); -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 (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); - 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(); + 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* pystr = PyString_FromString(str.c_str()); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, pystr); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_title, args, kwargs); + if(!res) throw std::runtime_error("Call to title() failed."); - 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())); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); } - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_xlabel, args, kwargs); - if(!res) throw std::runtime_error("Call to xlabel() failed."); + void suptitle(const std::string& suptitlestr, + const std::map& keywords = {}) + { + detail::_interpreter::get(); - Py_DECREF(args); - Py_DECREF(kwargs); - Py_DECREF(res); -} + PyObject* pysuptitlestr = PyString_FromString(suptitlestr.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pysuptitlestr); -inline void ylabel(const std::string &str, const std::map& keywords = {}) -{ - detail::_interpreter::get(); + 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* pystr = PyString_FromString(str.c_str()); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, pystr); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_suptitle, args, + kwargs); + if(!res) throw std::runtime_error("Call to suptitle() failed."); - 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())); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); } - 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); -} - -inline void set_zlabel(const std::string &str, const std::map& keywords = {}) -{ - detail::_interpreter::get(); + void axis(const std::string& axisstr) + { + 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) { - 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"); } + PyObject* str = PyString_FromString(axisstr.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, str); - mpl_toolkitsmod = PyImport_Import(mpl_toolkits); - Py_DECREF(mpl_toolkits); - if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_axis, args); + if(!res) throw std::runtime_error("Call to title() failed."); - axis3dmod = PyImport_Import(axis3d); - Py_DECREF(axis3d); - if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } + Py_DECREF(args); + Py_DECREF(res); } - PyObject* pystr = PyString_FromString(str.c_str()); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, pystr); + void axhline(double y, double xmin = 0., double xmax = 1., + const std::map& keywords + = std::map()) + { + detail::_interpreter::get(); - 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())); - } + // 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)); - 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); + // 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 *zlabel = PyObject_GetAttrString(ax, "set_zlabel"); - if (!zlabel) throw std::runtime_error("Attribute set_zlabel not found."); - Py_INCREF(zlabel); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_axhline, args, + kwargs); - PyObject *res = PyObject_Call(zlabel, args, kwargs); - if (!res) throw std::runtime_error("Call to set_zlabel() failed."); - Py_DECREF(zlabel); + Py_DECREF(args); + Py_DECREF(kwargs); - Py_DECREF(ax); - Py_DECREF(args); - Py_DECREF(kwargs); - if (res) Py_DECREF(res); -} + if(res) Py_DECREF(res); + } -inline void grid(bool flag) -{ - detail::_interpreter::get(); + void axvline(double x, double ymin = 0., double ymax = 1., + const std::map& keywords + = std::map()) + { + detail::_interpreter::get(); - PyObject* pyflag = flag ? Py_True : Py_False; - Py_INCREF(pyflag); + // 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)); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, pyflag); + // 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_CallObject(detail::_interpreter::get().s_python_function_grid, args); - if(!res) throw std::runtime_error("Call to grid() failed."); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_axvline, args, + kwargs); - Py_DECREF(args); - Py_DECREF(res); -} + Py_DECREF(args); + Py_DECREF(kwargs); -inline void show(const bool block = true) -{ - detail::_interpreter::get(); + if(res) Py_DECREF(res); + } - PyObject* res; - if(block) + void axvspan(double xmin, double xmax, double ymin = 0., double ymax = 1., + const std::map& keywords + = std::map()) { - res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_show, - detail::_interpreter::get().s_python_empty_tuple); + // 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(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); + Py_DECREF(args); + Py_DECREF(kwargs); + + if(res) Py_DECREF(res); } - else + + void xlabel(const std::string& str, + const std::map& keywords = {}) { - 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); - } + detail::_interpreter::get(); + PyObject* pystr = PyString_FromString(str.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pystr); - if (!res) throw std::runtime_error("Call to show() failed."); + 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())); + } - Py_DECREF(res); -} + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_xlabel, args, kwargs); + if(!res) throw std::runtime_error("Call to xlabel() failed."); -inline void close() -{ - detail::_interpreter::get(); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); + } - PyObject* res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_close, - detail::_interpreter::get().s_python_empty_tuple); + void ylabel(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); - if (!res) throw std::runtime_error("Call to close() failed."); + 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())); + } - Py_DECREF(res); -} + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_ylabel, args, kwargs); + if(!res) throw std::runtime_error("Call to ylabel() failed."); -inline void xkcd() { - detail::_interpreter::get(); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); + } - PyObject* res; - PyObject *kwargs = PyDict_New(); + void set_zlabel(const std::string& str, + const std::map& keywords = {}) + { + detail::_interpreter::get(); - res = PyObject_Call(detail::_interpreter::get().s_python_function_xkcd, - detail::_interpreter::get().s_python_empty_tuple, kwargs); + // 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) { + 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!"); + } + } - Py_DECREF(kwargs); + PyObject* pystr = PyString_FromString(str.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pystr); - if (!res) - throw std::runtime_error("Call to show() failed."); + 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())); + } - Py_DECREF(res); -} + 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); -inline void draw() -{ - detail::_interpreter::get(); + PyObject* zlabel = PyObject_GetAttrString(ax, "set_zlabel"); + if(!zlabel) throw std::runtime_error("Attribute set_zlabel not found."); + Py_INCREF(zlabel); - PyObject* res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_draw, - detail::_interpreter::get().s_python_empty_tuple); + PyObject* res = PyObject_Call(zlabel, args, kwargs); + if(!res) throw std::runtime_error("Call to set_zlabel() failed."); + Py_DECREF(zlabel); - if (!res) throw std::runtime_error("Call to draw() failed."); + Py_DECREF(ax); + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + } - Py_DECREF(res); -} + void grid(bool flag) + { + detail::_interpreter::get(); -template -inline void pause(Numeric interval) -{ - detail::_interpreter::get(); + PyObject* pyflag = flag ? Py_True : Py_False; + Py_INCREF(pyflag); - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, PyFloat_FromDouble(interval)); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pyflag); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_pause, args); - if(!res) throw std::runtime_error("Call to pause() failed."); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_grid, args); + if(!res) throw std::runtime_error("Call to grid() failed."); - Py_DECREF(args); - Py_DECREF(res); -} + Py_DECREF(args); + Py_DECREF(res); + } -inline void save(const std::string& filename, const int dpi=0) -{ - detail::_interpreter::get(); + void show(const bool block = true) + { + detail::_interpreter::get(); - PyObject* pyfilename = PyString_FromString(filename.c_str()); + PyObject* res; + if(block) { + res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_show, + detail::_interpreter::get().s_python_empty_tuple); + } + else { + 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); + } - PyObject* args = PyTuple_New(1); - PyTuple_SetItem(args, 0, pyfilename); + if(!res) throw std::runtime_error("Call to show() failed."); - PyObject* kwargs = PyDict_New(); + Py_DECREF(res); + } - if(dpi > 0) + void close() { - PyDict_SetItemString(kwargs, "dpi", PyLong_FromLong(dpi)); - } + detail::_interpreter::get(); - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_save, args, kwargs); - if (!res) throw std::runtime_error("Call to save() failed."); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_close, + detail::_interpreter::get().s_python_empty_tuple); - Py_DECREF(args); - Py_DECREF(kwargs); - Py_DECREF(res); -} + if(!res) throw std::runtime_error("Call to close() failed."); -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())); + Py_DECREF(res); } - - 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(); + void xkcd() + { + detail::_interpreter::get(); - PyObject *res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_clf, - detail::_interpreter::get().s_python_empty_tuple); + PyObject* res; + PyObject* kwargs = PyDict_New(); - if (!res) throw std::runtime_error("Call to clf() failed."); + res = PyObject_Call(detail::_interpreter::get().s_python_function_xkcd, + detail::_interpreter::get().s_python_empty_tuple, + kwargs); - Py_DECREF(res); -} + Py_DECREF(kwargs); -inline void cla() { - detail::_interpreter::get(); + if(!res) throw std::runtime_error("Call to show() failed."); - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_cla, - detail::_interpreter::get().s_python_empty_tuple); + Py_DECREF(res); + } - if (!res) - throw std::runtime_error("Call to cla() failed."); + void draw() + { + detail::_interpreter::get(); - Py_DECREF(res); -} + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_draw, + detail::_interpreter::get().s_python_empty_tuple); -inline void ion() { - detail::_interpreter::get(); + if(!res) throw std::runtime_error("Call to draw() failed."); - PyObject *res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_ion, - detail::_interpreter::get().s_python_empty_tuple); + Py_DECREF(res); + } - if (!res) throw std::runtime_error("Call to ion() failed."); + template + void pause(Numeric interval) + { + detail::_interpreter::get(); - Py_DECREF(res); -} + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(interval)); -inline std::vector> ginput(const int numClicks = 1, const std::map& keywords = {}) -{ - detail::_interpreter::get(); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_pause, args); + if(!res) throw std::runtime_error("Call to pause() failed."); - PyObject *args = PyTuple_New(1); - PyTuple_SetItem(args, 0, PyLong_FromLong(numClicks)); + Py_DECREF(args); + Py_DECREF(res); + } - // construct keyword args - PyObject* kwargs = PyDict_New(); - for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + void save(const std::string& filename, const int dpi = 0) { - PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); - } + detail::_interpreter::get(); - PyObject* res = PyObject_Call( - detail::_interpreter::get().s_python_function_ginput, args, kwargs); + PyObject* pyfilename = PyString_FromString(filename.c_str()); - Py_DECREF(kwargs); - Py_DECREF(args); - if (!res) throw std::runtime_error("Call to ginput() failed."); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pyfilename); - 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); + PyObject* kwargs = PyDict_New(); - return out; -} + if(dpi > 0) { + PyDict_SetItemString(kwargs, "dpi", PyLong_FromLong(dpi)); + } -// Actually, is there any reason not to call this automatically for every plot? -inline void tight_layout() { - detail::_interpreter::get(); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_save, args, kwargs); + if(!res) throw std::runtime_error("Call to save() failed."); - PyObject *res = PyObject_CallObject( - detail::_interpreter::get().s_python_function_tight_layout, - detail::_interpreter::get().s_python_empty_tuple); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); + } - if (!res) throw std::runtime_error("Call to tight_layout() failed."); + 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())); + } - Py_DECREF(res); -} + 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); + } -// Support for variadic plot() and initializer lists: + void clf() + { + detail::_interpreter::get(); -namespace detail { + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_clf, + detail::_interpreter::get().s_python_empty_tuple); -template -using is_function = typename std::is_function>>::type; + if(!res) throw std::runtime_error("Call to clf() failed."); -template -struct is_callable_impl; + Py_DECREF(res); + } -template -struct is_callable_impl -{ - typedef is_function type; -}; // a non-object is callable iff it is a function + void cla() + { + detail::_interpreter::get(); -template -struct is_callable_impl -{ - struct Fallback { void operator()(); }; - struct Derived : T, Fallback { }; + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_cla, + detail::_interpreter::get().s_python_empty_tuple); - template struct Check; + if(!res) throw std::runtime_error("Call to cla() failed."); - template - static std::true_type test( ... ); // use a variadic function to make sure (1) it accepts everything and (2) its always the worst match + Py_DECREF(res); + } - template - static std::false_type test( Check* ); + void ion() + { + detail::_interpreter::get(); -public: - typedef decltype(test(nullptr)) type; - typedef decltype(&Fallback::operator()) dtype; - static constexpr bool value = type::value; -}; // an object is callable iff it defines operator() + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_ion, + detail::_interpreter::get().s_python_empty_tuple); -template -struct is_callable -{ - // dispatch to is_callable_impl or is_callable_impl depending on whether T is of class type or not - typedef typename is_callable_impl::value, T>::type type; -}; + if(!res) throw std::runtime_error("Call to ion() failed."); -template -struct plot_impl { }; + Py_DECREF(res); + } -template<> -struct plot_impl -{ - template - bool operator()(const IterableX& x, const IterableY& y, const std::string& format) + std::vector> + ginput(const int numClicks = 1, + const std::map& keywords = {}) { detail::_interpreter::get(); - // 2-phase lookup for distance, begin, end - using std::distance; - using std::begin; - using std::end; + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyLong_FromLong(numClicks)); - auto xs = distance(begin(x), end(x)); - auto ys = distance(begin(y), end(y)); - assert(xs == ys && "x and y data must have the same number of elements!"); + // 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* xlist = PyList_New(xs); - PyObject* ylist = PyList_New(ys); - PyObject* pystring = PyString_FromString(format.c_str()); + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_ginput, args, kwargs); - auto itx = begin(x), ity = begin(y); - for(size_t i = 0; i < xs; ++i) { - PyList_SetItem(xlist, i, PyFloat_FromDouble(*itx++)); - PyList_SetItem(ylist, i, PyFloat_FromDouble(*ity++)); + 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); - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xlist); - PyTuple_SetItem(plot_args, 1, ylist); - PyTuple_SetItem(plot_args, 2, pystring); + return out; + } - PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); + // Actually, is there any reason not to call this automatically for every + // plot? + void tight_layout() + { + detail::_interpreter::get(); - Py_DECREF(plot_args); - if(res) Py_DECREF(res); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_tight_layout, + detail::_interpreter::get().s_python_empty_tuple); - return res; + if(!res) throw std::runtime_error("Call to tight_layout() failed."); + + Py_DECREF(res); } -}; -template<> -struct plot_impl -{ - template - bool operator()(const Iterable& ticks, const Callable& f, const std::string& format) + // Support for variadic plot() and initializer lists: + + namespace detail { - if(begin(ticks) == end(ticks)) return true; + template + using is_function = typename std::is_function< + std::remove_pointer>>::type; - // We could use additional meta-programming to deduce the correct element type of y, - // but all values have to be convertible to double anyways - std::vector y; - for(auto x : ticks) y.push_back(f(x)); - return plot_impl()(ticks,y,format); - } -}; + template + struct is_callable_impl; -} // end namespace detail + template + struct is_callable_impl + { + typedef is_function type; + }; // a non-object is callable iff it is a function -// recursion stop for the above -template -bool plot() { return true; } + template + struct is_callable_impl + { + struct Fallback + { + void operator()(); + }; + struct Derived : T, Fallback + {}; + + template + struct Check; + + template + static std::true_type + test(...); // use a variadic function to make sure (1) it accepts + // everything and (2) its always the worst match + + template + static std::false_type + test(Check*); + public: + typedef decltype(test(nullptr)) type; + typedef decltype(&Fallback::operator()) dtype; + static constexpr bool value = type::value; + }; // an object is callable iff it defines operator() + + template + struct is_callable + { + // dispatch to is_callable_impl or is_callable_impl depending on whether T is of class type or not + typedef typename is_callable_impl::value, T>::type + type; + }; -template -bool plot(const A& a, const B& b, const std::string& format, Args... args) -{ - return detail::plot_impl::type>()(a,b,format) && plot(args...); -} + template + struct plot_impl + {}; -/* - * This group of plot() functions is needed to support initializer lists, i.e. calling - * plot( {1,2,3,4} ) - */ -inline bool plot(const std::vector& x, const std::vector& y, const std::string& format = "") { - return plot(x,y,format); -} +#ifdef WITHOUT_NUMPY -inline bool plot(const std::vector& y, const std::string& format = "") { - return plot(y,format); -} + template<> + 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::begin; + using std::distance; + using std::end; + + auto xs = distance(begin(x), end(x)); + auto ys = distance(begin(y), end(y)); + + assert(xs == ys + && "x and y must have the same number of elements!"); + + // No PyArray, must use lists, and they must have the same + // length. + PyObject* xlist = PyList_New(xs); + PyObject* ylist = PyList_New(ys); + PyObject* pystring = PyString_FromString(format.c_str()); + + auto itx = begin(x), ity = begin(y); + for(decltype(xs) i = 0; i < xs; ++i) { + PyList_SetItem(xlist, i, PyFloat_FromDouble(*itx++)); + PyList_SetItem(ylist, i, PyFloat_FromDouble(*ity++)); + } + + 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_CallObject( + detail::_interpreter::get().s_python_function_plot, + plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; + } + }; -inline bool plot(const std::vector& x, const std::vector& y, const std::map& keywords) { - return plot(x,y,keywords); -} +#else -/* - * 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 = "") { - detail::_interpreter::get(); + template<> + struct plot_impl + { + template + bool operator()(const IterableX& x, const IterableY& y, + const std::string& format) + { + // pyassert(PyArray_API, "NumPy needed"); - assert(x.size() == y.size()); + detail::_interpreter::get(); - PyObject* kwargs = PyDict_New(); - if(name != "") - PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + // 2-phase lookup for distance, begin, end + using std::begin; + using std::distance; + using std::end; - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + auto xs = distance(begin(x), end(x)); + auto ys = distance(begin(y), end(y)); - PyObject* pystring = PyString_FromString(format.c_str()); + assert(ys % xs == 0 + && "length of y must be a multiple of length of x!"); - PyObject* plot_args = PyTuple_New(3); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); - PyTuple_SetItem(plot_args, 2, pystring); + typedef typename IterableX::value_type NumberX; + typedef typename IterableY::value_type NumberY; - PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, plot_args, kwargs); + NPY_TYPES xtype = detail::select_npy_type::type; + NPY_TYPES ytype = detail::select_npy_type::type; - Py_DECREF(kwargs); - Py_DECREF(plot_args); + npy_intp xsize = xs; + npy_intp yrows = xsize, ycols = ys / yrows; + npy_intp ysize[] = {yrows, ycols}; // ysize[0] must equal xsize - if(res) - { - line= PyList_GetItem(res, 0); + PyObject* xarray + = PyArray_New(&PyArray_Type, 1, &xsize, xtype, nullptr, + nullptr, 0, NPY_ARRAY_FARRAY, nullptr); + PyObject* yarray = PyArray_New( + &PyArray_Type, 2, ysize, ytype, nullptr, nullptr, 0, + NPY_ARRAY_FARRAY, nullptr); // column major! + PyObject* pystring = PyString_FromString(format.c_str()); - if(line) - set_data_fct = PyObject_GetAttrString(line,"set_data"); - else - Py_DECREF(line); - Py_DECREF(res); - } - } + // fill the data + auto itx = begin(x), ity = begin(y); + NumberX* xdata = (NumberX*)PyArray_DATA((PyArrayObject*)xarray); + for(decltype(xs) i = 0; i < xs; ++i) xdata[i] = *itx++; - // 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) {} + NumberY* ydata = (NumberY*)PyArray_DATA((PyArrayObject*)yarray); + for(decltype(ys) i = 0; i < ys; ++i) ydata[i] = *ity++; - template - bool update(const std::vector& x, const std::vector& y) { - assert(x.size() == y.size()); - if(set_data_fct) - { - PyObject* xarray = detail::get_array(x); - PyObject* yarray = detail::get_array(y); + 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* plot_args = PyTuple_New(2); - PyTuple_SetItem(plot_args, 0, xarray); - PyTuple_SetItem(plot_args, 1, yarray); + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_plot, + plot_args); - PyObject* res = PyObject_CallObject(set_data_fct, plot_args); - if (res) Py_DECREF(res); - return res; - } - return false; - } + Py_DECREF(plot_args); + if(res) Py_DECREF(res); - // clears the plot but keep it available - bool clear() { - return update(std::vector(), std::vector()); - } + return res; + } + }; + +#endif - // definitely remove this line - void remove() { - if(line) + template<> + struct plot_impl { - 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(); + template + bool operator()(const Iterable& ticks, const Callable& f, + const std::string& format) + { + if(begin(ticks) == end(ticks)) return true; + + // We could use additional meta-programming to deduce the + // correct element type of y, but all values have to be + // convertible to double anyways + std::vector y; + for(auto x : ticks) y.push_back(f(x)); + return plot_impl()(ticks, y, format); + } + }; + + } // end namespace detail + + // recursion stop for the above + template + bool plot() + { + return true; } - ~Plot() { - decref(); + template + bool plot(const A& a, const B& b, const std::string& format, Args... args) + { + return detail::plot_impl::type>()( + a, b, format) + && plot(args...); } -private: - void decref() { - if(line) - Py_DECREF(line); - if(set_data_fct) - Py_DECREF(set_data_fct); + /* + * This group of plot() functions is needed to support initializer lists, + * i.e. calling plot( {1,2,3,4} ) + */ + bool plot(const std::vector& x, const std::vector& y, + const std::string& format = "") + { +#if __cplusplus >= CPP20 + return plot, std::vector>(x, y, format); +#else + return plot(x, y, format); +#endif } + bool plot(const std::vector& y, const std::string& format = "") + { +#if __cplusplus >= CPP20 + return plot>(y, format); +#else + return plot(y, format); +#endif + } - PyObject* line = nullptr; - PyObject* set_data_fct = nullptr; -}; + /* + * 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 = "") + { + detail::_interpreter::get(); + + assert(x.size() == y.size()); + + PyObject* kwargs = PyDict_New(); + if(name != "") + PyDict_SetItemString(kwargs, "label", + PyString_FromString(name.c_str())); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::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 = detail::get_array(x); + PyObject* yarray = detail::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