From 10bd8e245c2a4be1076c359e254ead1a3b3b93d2 Mon Sep 17 00:00:00 2001 From: Andrei Costinescu Date: Sun, 27 Jul 2025 15:55:43 +0200 Subject: [PATCH 1/3] Update plot3 function in matplotlibcpp.h to consider newer versions of python's matplotlib --- matplotlibcpp.h | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index d95d46a..7f3f4ba 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -723,20 +723,42 @@ void plot3(const std::vector &x, } if (!fig) throw std::runtime_error("Call to figure() failed."); - PyObject *gca_kwargs = PyDict_New(); - PyDict_SetItemString(gca_kwargs, "projection", PyString_FromString("3d")); + // Try modern approach: fig.add_subplot(111, projection='3d') + PyObject *axis = nullptr; - 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 *add_subplot = PyObject_GetAttrString(fig, "add_subplot"); + if (!add_subplot) throw std::runtime_error("No add_subplot"); - if (!axis) throw std::runtime_error("No axis"); - Py_INCREF(axis); + PyObject *subplot_args = PyTuple_New(1); + PyTuple_SetItem(subplot_args, 0, PyLong_FromLong(111)); - Py_DECREF(gca); - Py_DECREF(gca_kwargs); + PyObject *subplot_kwargs = PyDict_New(); + PyDict_SetItemString(subplot_kwargs, "projection", PyString_FromString("3d")); + + axis = PyObject_Call(add_subplot, subplot_args, subplot_kwargs); + + // Clean up + Py_DECREF(add_subplot); + Py_DECREF(subplot_args); + Py_DECREF(subplot_kwargs); + + if (!axis) { + // Fallback for older matplotlib: fig.gca(projection='3d') + PyObject *gca = PyObject_GetAttrString(fig, "gca"); + if (!gca) throw std::runtime_error("No gca"); + Py_INCREF(gca); + + PyObject *gca_kwargs = PyDict_New(); + PyDict_SetItemString(gca_kwargs, "projection", PyString_FromString("3d")); + + axis = PyObject_Call(gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); + + Py_DECREF(gca); + Py_DECREF(gca_kwargs); + + if (!axis) throw std::runtime_error("No axis (both add_subplot and gca failed)"); + } + Py_INCREF(axis); PyObject *plot3 = PyObject_GetAttrString(axis, "plot"); if (!plot3) throw std::runtime_error("No 3D line plot"); From e60eca6d6199bec3843f4d8253e18f3d55bc2025 Mon Sep 17 00:00:00 2001 From: Andrei Costinescu Date: Thu, 31 Jul 2025 16:51:24 +0200 Subject: [PATCH 2/3] Update plot3 so that it stores the last used axis to replot data on it... --- matplotlibcpp.h | 81 ++++++++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/matplotlibcpp.h b/matplotlibcpp.h index 7f3f4ba..b12be2c 100644 --- a/matplotlibcpp.h +++ b/matplotlibcpp.h @@ -656,6 +656,17 @@ void spy(const std::vector<::std::vector> &x, } #endif // WITHOUT_NUMPY +class PyAxes { +public: + ~PyAxes() { + for (auto &axis: this->axes) { + if (axis.second) Py_DECREF(axis.second); + } + } + + std::map axes; +}; + template void plot3(const std::vector &x, const std::vector &y, @@ -723,42 +734,48 @@ void plot3(const std::vector &x, } if (!fig) throw std::runtime_error("Call to figure() failed."); - // Try modern approach: fig.add_subplot(111, projection='3d') + static PyAxes axes; PyObject *axis = nullptr; + auto it = axes.axes.find(fig_number); + if (it != axes.axes.end()) { + axis = it->second; + Py_INCREF(axis); // keep it alive for the rest of this function + } else { + // create new axis + PyObject *add_subplot = PyObject_GetAttrString(fig, "add_subplot"); + if (!add_subplot) throw std::runtime_error("No add_subplot"); + PyObject *subplot_args = PyTuple_New(1); + PyTuple_SetItem(subplot_args, 0, PyLong_FromLong(111)); + PyObject *subplot_kwargs = PyDict_New(); + PyDict_SetItemString(subplot_kwargs, "projection", PyString_FromString("3d")); + + axis = PyObject_Call(add_subplot, subplot_args, subplot_kwargs); + if (!axis) { + 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); + // Fallback for older matplotlib: fig.gca(projection='3d') + axis = PyObject_Call(gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); + std::cout << "Got old axis: " << axis << std::endl; + + if (!axis) throw std::runtime_error("No axis (both add_subplot and gca failed)"); + + Py_INCREF(axis); + Py_DECREF(gca); + Py_DECREF(gca_kwargs); + } else { + Py_INCREF(axis); + } - PyObject *add_subplot = PyObject_GetAttrString(fig, "add_subplot"); - if (!add_subplot) throw std::runtime_error("No add_subplot"); - - PyObject *subplot_args = PyTuple_New(1); - PyTuple_SetItem(subplot_args, 0, PyLong_FromLong(111)); - - PyObject *subplot_kwargs = PyDict_New(); - PyDict_SetItemString(subplot_kwargs, "projection", PyString_FromString("3d")); - - axis = PyObject_Call(add_subplot, subplot_args, subplot_kwargs); - - // Clean up - Py_DECREF(add_subplot); - Py_DECREF(subplot_args); - Py_DECREF(subplot_kwargs); - - if (!axis) { - // Fallback for older matplotlib: fig.gca(projection='3d') - PyObject *gca = PyObject_GetAttrString(fig, "gca"); - if (!gca) throw std::runtime_error("No gca"); - Py_INCREF(gca); - - PyObject *gca_kwargs = PyDict_New(); - PyDict_SetItemString(gca_kwargs, "projection", PyString_FromString("3d")); - - axis = PyObject_Call(gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); - - Py_DECREF(gca); - Py_DECREF(gca_kwargs); + axes.axes[fig_number] = axis; - if (!axis) throw std::runtime_error("No axis (both add_subplot and gca failed)"); + Py_DECREF(add_subplot); + Py_DECREF(subplot_args); + Py_DECREF(subplot_kwargs); } - Py_INCREF(axis); PyObject *plot3 = PyObject_GetAttrString(axis, "plot"); if (!plot3) throw std::runtime_error("No 3D line plot"); From a5dd562f7641e652f670c954a5cc37d9177d0c0d Mon Sep 17 00:00:00 2001 From: Andrei Costinescu Date: Sat, 9 Aug 2025 06:26:22 +0200 Subject: [PATCH 3/3] Update gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 1c4a1b0..ef08db8 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,11 @@ # Build /examples/build/* +build-* +cmake-build-* + +# IDE +.idea # vim temp files *.sw*