@@ -61,6 +61,7 @@ struct _interpreter {
6161 PyObject *s_python_function_hist;
6262 PyObject *s_python_function_imshow;
6363 PyObject *s_python_function_scatter;
64+ PyObject *s_python_function_boxplot;
6465 PyObject *s_python_function_subplot;
6566 PyObject *s_python_function_subplot2grid;
6667 PyObject *s_python_function_legend;
@@ -197,6 +198,7 @@ struct _interpreter {
197198 s_python_function_fill_between = safe_import (pymod, " fill_between" );
198199 s_python_function_hist = safe_import (pymod," hist" );
199200 s_python_function_scatter = safe_import (pymod," scatter" );
201+ s_python_function_boxplot = safe_import (pymod," boxplot" );
200202 s_python_function_subplot = safe_import (pymod, " subplot" );
201203 s_python_function_subplot2grid = safe_import (pymod, " subplot2grid" );
202204 s_python_function_legend = safe_import (pymod, " legend" );
@@ -326,6 +328,27 @@ PyObject* get_2darray(const std::vector<::std::vector<Numeric>>& v)
326328 return reinterpret_cast <PyObject *>(varray);
327329}
328330
331+ // sometimes, for labels and such, we need string arrays
332+ PyObject * get_array (const std::vector<std::string>& strings)
333+ {
334+ PyObject* list = PyList_New (strings.size ());
335+ for (std::size_t i = 0 ; i < strings.size (); ++i) {
336+ PyList_SetItem (list, i, PyString_FromString (strings[i].c_str ()));
337+ }
338+ return list;
339+ }
340+
341+ // not all matplotlib need 2d arrays, some prefer lists of lists
342+ template <typename Numeric>
343+ PyObject* get_listlist (const std::vector<std::vector<Numeric>>& ll)
344+ {
345+ PyObject* listlist = PyList_New (ll.size ());
346+ for (std::size_t i = 0 ; i < ll.size (); ++i) {
347+ PyList_SetItem (listlist, i, get_array (ll[i]));
348+ }
349+ return listlist;
350+ }
351+
329352#else // fallback if we don't have numpy: copy every element of the given vector
330353
331354template <typename Numeric>
@@ -698,6 +721,62 @@ bool scatter(const std::vector<NumericX>& x,
698721 return res;
699722}
700723
724+ template <typename Numeric>
725+ bool boxplot (const std::vector<std::vector<Numeric>>& data,
726+ const std::vector<std::string>& labels = {},
727+ const std::unordered_map<std::string, std::string> & keywords = {})
728+ {
729+ PyObject* listlist = get_listlist (data);
730+ PyObject* args = PyTuple_New (1 );
731+ PyTuple_SetItem (args, 0 , listlist);
732+
733+ PyObject* kwargs = PyDict_New ();
734+
735+ // kwargs needs the labels, if there are (the correct number of) labels
736+ if (!labels.empty () && labels.size () == data.size ()) {
737+ PyDict_SetItemString (kwargs, " labels" , get_array (labels));
738+ }
739+
740+ // take care of the remaining keywords
741+ for (const auto & it : keywords)
742+ {
743+ PyDict_SetItemString (kwargs, it.first .c_str (), PyString_FromString (it.second .c_str ()));
744+ }
745+
746+ PyObject* res = PyObject_Call (detail::_interpreter::get ().s_python_function_boxplot , args, kwargs);
747+
748+ Py_DECREF (args);
749+ Py_DECREF (kwargs);
750+
751+ if (res) Py_DECREF (res);
752+
753+ return res;
754+ }
755+
756+ template <typename Numeric>
757+ bool boxplot (const std::vector<Numeric>& data,
758+ const std::unordered_map<std::string, std::string> & keywords = {})
759+ {
760+ PyObject* vector = get_array (data);
761+ PyObject* args = PyTuple_New (1 );
762+ PyTuple_SetItem (args, 0 , vector);
763+
764+ PyObject* kwargs = PyDict_New ();
765+ for (const auto & it : keywords)
766+ {
767+ PyDict_SetItemString (kwargs, it.first .c_str (), PyString_FromString (it.second .c_str ()));
768+ }
769+
770+ PyObject* res = PyObject_Call (detail::_interpreter::get ().s_python_function_boxplot , args, kwargs);
771+
772+ Py_DECREF (args);
773+ Py_DECREF (kwargs);
774+
775+ if (res) Py_DECREF (res);
776+
777+ return res;
778+ }
779+
701780template <typename Numeric>
702781bool bar (const std::vector<Numeric> & x,
703782 const std::vector<Numeric> & y,
0 commit comments