Skip to content

Commit 97a4fb3

Browse files
author
Benno Evers
committed
Added save() function
* Can now save plots to images using save() * Updated README and basic example to use this
1 parent 08113f2 commit 97a4fb3

File tree

3 files changed

+80
-36
lines changed

3 files changed

+80
-36
lines changed

README.md

+24-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
matplotlib-cpp
22
==============
33

4-
This is matplotlib-cpp, probably the simplest C++ plotting library.
4+
Welcome to matplotlib-cpp, probably the simplest C++ plotting library.
55
It is built to resemble the plotting API used by Matlab and matplotlib.
66

7+
8+
79
Usage
810
-----
911
Complete minimal example:
@@ -48,8 +50,8 @@ A more comprehensive example:
4850
plt::xlim(0, 1000*1000);
4951
// Enable legend.
5052
plt::legend();
51-
// Show plot
52-
plt::show();
53+
// Save the image (file format is determined by the extension)
54+
plt::save("./basic.png");
5355
}
5456

5557
// g++ basic.cpp -lpython2.7
@@ -83,10 +85,27 @@ matplotlib-cpp doesn't require C++11, but will enable some additional syntactic
8385

8486
// show plots
8587
plt::show();
86-
}
88+
}
89+
90+
// g++ modern.cpp -std=c++11 -lpython
8791

8892
Result: ![Modern example](./examples/modern.png)
8993

94+
Why?
95+
----
96+
I initially started this library during my diploma thesis. My previous approach of
97+
writing data from my c++ algorithm to a yaml file, and afterwards parsing and plotting
98+
it in python using matplotlib proved insufficient: Mainly, keeping the algorithm
99+
and plotting code in sync is cumbersome when the C++ code frequently and substantially
100+
changes. Additionally, the python yaml parser is not able to cope with files that
101+
exceed a few hundred megabytes in size.
102+
103+
Therefore, I was looking for a C++ plotting library that was extremely to use
104+
and easy to add into an existing codebase, preferrably header-only. When I found
105+
none, I decided to create this library, which is basically a C++ wrapper around
106+
matplotlib. As you can see from the above examples, plotting data and saving it
107+
to an image file can be done is as few as two lines of code.
108+
90109
Installation
91110
------------
92111
matplotlib-cpp works by wrapping the popular python plotting library matplotlib. (matplotlib.org)
@@ -107,7 +126,7 @@ Todo/Issues/Wishlist
107126
* It would be nice to have a more object-oriented design with a Plot class which would allow
108127
multiple independent plots per program.
109128

110-
* Right now, only a small subset of matplotlibs functionality is exposed. Stuff like save()/xlabel()/ylabel() etc. should
129+
* Right now, only a small subset of matplotlibs functionality is exposed. Stuff like xlabel()/ylabel() etc. should
111130
be easy to add.
112131

113132
* A lot of copying could be avoided if we generate numpy arrays directly instead of python lists

examples/basic.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@ int main()
2727
// Enable legend.
2828
plt::legend();
2929
// Show plot
30-
plt::show();
30+
plt::save("./basic.png");
3131
}

matplotlibcpp.h

+55-30
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace matplotlibcpp {
1717
namespace detail {
1818
struct _interpreter {
1919
PyObject *s_python_function_show;
20+
PyObject *s_python_function_save;
2021
PyObject *s_python_function_figure;
2122
PyObject *s_python_function_plot;
2223
PyObject *s_python_function_legend;
@@ -42,12 +43,17 @@ namespace matplotlibcpp {
4243
Py_SetProgramName(name); // optional but recommended
4344
Py_Initialize();
4445

45-
PyObject* pyname = PyString_FromString("matplotlib.pyplot");
46-
if(!pyname) { throw std::runtime_error("couldnt create string"); }
46+
PyObject* pyplotname = PyString_FromString("matplotlib.pyplot");
47+
PyObject* pylabname = PyString_FromString("pylab");
48+
if(!pyplotname || !pylabname) { throw std::runtime_error("couldnt create string"); }
4749

48-
PyObject* pymod = PyImport_Import(pyname);
49-
Py_DECREF(pyname);
50-
if(!pymod) { throw std::runtime_error("Error loading module!"); }
50+
PyObject* pymod = PyImport_Import(pyplotname);
51+
Py_DECREF(pyplotname);
52+
if(!pymod) { throw std::runtime_error("Error loading module matplotlib.pyplot!"); }
53+
54+
PyObject* pylabmod = PyImport_Import(pylabname);
55+
Py_DECREF(pylabname);
56+
if(!pymod) { throw std::runtime_error("Error loading module pylab!"); }
5157

5258
s_python_function_show = PyObject_GetAttrString(pymod, "show");
5359
s_python_function_figure = PyObject_GetAttrString(pymod, "figure");
@@ -56,7 +62,10 @@ namespace matplotlibcpp {
5662
s_python_function_ylim = PyObject_GetAttrString(pymod, "ylim");
5763
s_python_function_xlim = PyObject_GetAttrString(pymod, "xlim");
5864

65+
s_python_function_save = PyObject_GetAttrString(pylabmod, "savefig");
66+
5967
if(!s_python_function_show
68+
|| !s_python_function_save
6069
|| !s_python_function_figure
6170
|| !s_python_function_plot
6271
|| !s_python_function_legend
@@ -65,6 +74,7 @@ namespace matplotlibcpp {
6574
{ throw std::runtime_error("Couldnt find required function!"); }
6675

6776
if(!PyFunction_Check(s_python_function_show)
77+
|| !PyFunction_Check(s_python_function_save)
6878
|| !PyFunction_Check(s_python_function_figure)
6979
|| !PyFunction_Check(s_python_function_plot)
7080
|| !PyFunction_Check(s_python_function_legend)
@@ -127,8 +137,6 @@ namespace matplotlibcpp {
127137
{
128138
assert(x.size() == y.size());
129139

130-
//std::string format(s);
131-
132140
PyObject* xlist = PyList_New(x.size());
133141
PyObject* ylist = PyList_New(y.size());
134142
PyObject* pystring = PyString_FromString(s.c_str());
@@ -192,25 +200,6 @@ namespace matplotlibcpp {
192200
return plot(x,y,format);
193201
}
194202

195-
/*
196-
* This group of plot() functions is needed to support initializer lists, i.e. calling
197-
* plot( {1,2,3,4} )
198-
*/
199-
bool plot(const std::vector<double>& x, const std::vector<double>& y, const std::string& format = "") {
200-
return plot<double,double>(x,y,format);
201-
}
202-
203-
bool plot(const std::vector<double>& y, const std::string& format = "") {
204-
return plot<double>(y,format);
205-
}
206-
207-
bool plot(const std::vector<double>& x, const std::vector<double>& y, const std::map<std::string, std::string>& keywords) {
208-
return plot<double>(x,y,keywords);
209-
}
210-
211-
bool named_plot(const std::string& name, const std::vector<double>& x, const std::vector<double>& y, const std::string& format = "") {
212-
return named_plot<double>(name,x,y,format);
213-
}
214203

215204
inline void legend() {
216205
PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_legend, detail::_interpreter::get().s_python_empty_tuple);
@@ -255,15 +244,31 @@ namespace matplotlibcpp {
255244
Py_DECREF(res);
256245
}
257246

258-
inline void show() {
247+
inline void show()
248+
{
259249
PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_show, detail::_interpreter::get().s_python_empty_tuple);
260250
if(!res) throw std::runtime_error("Call to show() failed.");
261251

262252
Py_DECREF(res);
263253
}
264254

255+
inline void save(const std::string& filename)
256+
{
257+
PyObject* pyfilename = PyString_FromString(filename.c_str());
258+
259+
PyObject* args = PyTuple_New(1);
260+
PyTuple_SetItem(args, 0, pyfilename);
261+
262+
PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_save, args);
263+
if(!res) throw std::runtime_error("Call to save() failed.");
264+
265+
Py_DECREF(pyfilename);
266+
Py_DECREF(args);
267+
Py_DECREF(res);
268+
}
269+
265270
#if __cplusplus > 199711L
266-
// C++11-exclusive content starts here, in particular the variadic plot()
271+
// C++11-exclusive content starts here (variadic plot() and initializer list support)
267272

268273
namespace detail {
269274
template<typename T>
@@ -302,7 +307,7 @@ namespace matplotlibcpp {
302307
struct is_callable
303308
{
304309
// dispatch to is_callable_impl<true, T> or is_callable_impl<false, T> depending on whether T is of class type or not
305-
typedef typename is_callable_impl<std::is_class<T>::value, T>::type type; // todo: restore remove_reference
310+
typedef typename is_callable_impl<std::is_class<T>::value, T>::type type;
306311
};
307312

308313
template<typename IsYDataCallable>
@@ -314,7 +319,7 @@ namespace matplotlibcpp {
314319
template<typename IterableX, typename IterableY>
315320
bool operator()(const IterableX& x, const IterableY& y, const std::string& format)
316321
{
317-
// It's annoying that we have to repeat the code of plot() above
322+
// 2-phase lookup for distance, begin, end
318323
using std::distance;
319324
using std::begin;
320325
using std::end;
@@ -378,6 +383,26 @@ namespace matplotlibcpp {
378383
return detail::plot_impl<typename detail::is_callable<B>::type>()(a,b,format) && plot(args...);
379384
}
380385

386+
/*
387+
* This group of plot() functions is needed to support initializer lists, i.e. calling
388+
* plot( {1,2,3,4} )
389+
*/
390+
bool plot(const std::vector<double>& x, const std::vector<double>& y, const std::string& format = "") {
391+
return plot<double,double>(x,y,format);
392+
}
393+
394+
bool plot(const std::vector<double>& y, const std::string& format = "") {
395+
return plot<double>(y,format);
396+
}
397+
398+
bool plot(const std::vector<double>& x, const std::vector<double>& y, const std::map<std::string, std::string>& keywords) {
399+
return plot<double>(x,y,keywords);
400+
}
401+
402+
bool named_plot(const std::string& name, const std::vector<double>& x, const std::vector<double>& y, const std::string& format = "") {
403+
return named_plot<double>(name,x,y,format);
404+
}
405+
381406
#endif
382407

383408

0 commit comments

Comments
 (0)