1
0
Benno Evers 10 жил өмнө
parent
commit
3f25969719

+ 11 - 0
Makefile

@@ -0,0 +1,11 @@
+examples: minimal basic modern
+
+minimal: examples/minimal.cpp matplotlibcpp.h
+	cd examples && g++ minimal.cpp -lpython2.7 -o minimal -std=c++11
+
+basic: examples/basic.cpp matplotlibcpp.h
+	cd examples && g++ basic.cpp -lpython2.7 -o basic
+
+modern: examples/modern.cpp matplotlibcpp.h
+	cd examples && g++ modern.cpp -lpython2.7 -o modern -std=c++11
+

+ 79 - 0
README

@@ -0,0 +1,79 @@
+matplotlib-cpp
+==============
+
+This is matplotlib-cpp, probably the simplest C++ plotting library.
+It is built to resemble the plotting API used by Matlab and matplotlib.
+
+Usage
+-----
+Complete minimal example:
+
+    #include "matplotlibcpp.h"
+    namespace plt = matplotlibcpp;
+    int main() {
+        std::vector<double> v {1,2,3,4};
+        plt::plot(v);
+        plt::show();
+    }
+    
+    // g++ minimal.cpp -std=c++11 -lpython2.7
+
+Result: ![Minimal example](./examples/minimal.png)
+
+A more comprehensive example:
+
+    #include "matplotlibcpp.h"
+    #include <cmath>
+
+    namespace plt = matplotlibcpp;
+
+    int main() 
+    {
+        // Prepare data.
+        int n = 5000;
+        std::vector<double> 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);
+        }
+
+        // Plot line from given x and y data. Color is selected automatically.
+        plt::plot(x, y);
+        // Plot a red dashed line from given x and y data.
+        plt::plot(x, w,"r--");
+        // Plot a line whose name will show up as "log(x)" in the legend.
+        plt::named_plot("log(x)", x, z);
+
+        // Set x-axis to interval [0,1000000]
+        plt::xlim(0, 1000*1000);
+        // Enable legend.
+        plt::legend();
+        // Show plot
+        plt::show();
+    }
+
+Result: ![Basic example](./examples/basic.png)
+
+Installation
+------------
+matplotlib-cpp works by wrapping the popular python plotting library matplotlib. (matplotlib.org)
+This means you have to have a working python installation, including development headers.
+On Ubuntu:
+
+    sudo aptitude install python-matplotlib python2.7-dev
+
+The C++-part of the library consists of the single header file matplotlibcpp.h which can be placed
+anywhere.
+Since a python interpreter is opened internally, it is necessary to link against libpython2.7 in order to use
+matplotlib-cpp.
+(There should be no problems using python3 instead of python2.7, if desired)
+
+
+Todo/Issues/Wishlist
+--------------------
+* It would be nice to have a more object-oriented design with a Plot class which would allow
+  multiple independent plots per program.
+
+* Right now, only a small subset of matplotlibs functionality is exposed. Stuff like xlabel()/ylabel() etc. should
+  be easy to add.

+ 31 - 0
examples/basic.cpp

@@ -0,0 +1,31 @@
+#include "../matplotlibcpp.h"
+
+#include <cmath>
+
+namespace plt = matplotlibcpp;
+
+int main() 
+{
+	// Prepare data.
+	int n = 5000;
+	std::vector<double> 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);
+	}
+
+	// Plot line from given x and y data. Color is selected automatically.
+	plt::plot(x, y);
+	// Plot a red dashed line from given x and y data.
+	plt::plot(x, w,"r--");
+	// Plot a line whose name will show up as "log(x)" in the legend.
+	plt::named_plot("log(x)", x, z);
+
+	// Set x-axis to interval [0,1000000]
+	plt::xlim(0, 1000*1000);
+	// Enable legend.
+	plt::legend();
+	// Show plot
+	plt::show();
+}

BIN
examples/basic.png


+ 9 - 0
examples/minimal.cpp

@@ -0,0 +1,9 @@
+#include "../matplotlibcpp.h"
+
+namespace plt = matplotlibcpp;
+
+int main() {
+    std::vector<double> v {1,2,3,4};
+    plt::plot(v);
+    plt::show();
+}

BIN
examples/minimal.png


+ 14 - 0
examples/modern.cpp

@@ -0,0 +1,14 @@
+#include "../matplotlibcpp.h"
+
+namespace plt = matplotlibcpp;
+
+int main() 
+{
+	// plot(y) - the x-coordinates are implicitly set to [0,1,...,n)
+	plt::plot({1,2,3,1,3.5,2.5}); 
+	// plot(x,y,format) - plot as solid red line with circular markers
+	plt::plot({5,4,3,2,-1}, {-1, 4, 2, 7, 1}, "ro-");
+
+	// show plots
+	plt::show();
+}

+ 250 - 0
matplotlibcpp.h

@@ -0,0 +1,250 @@
+#pragma once
+
+#include <vector>
+#include <map>
+#include <numeric>
+#include <stdexcept>
+
+#include <python2.7/Python.h>
+
+namespace matplotlibcpp {
+
+	namespace detail {
+		struct _pyplot_global {
+			PyObject *s_python_function_show;
+			PyObject *s_python_function_figure;
+			PyObject *s_python_function_plot;
+			PyObject *s_python_function_legend;
+			PyObject *s_python_function_xlim;
+			PyObject *s_python_function_ylim;
+			PyObject *s_python_empty_tuple;
+
+			static _pyplot_global& get() {
+				static _pyplot_global ctx;
+				return ctx;
+			}
+
+			private:
+			_pyplot_global() {
+				Py_SetProgramName("plotting");	/* optional but recommended */
+				Py_Initialize();
+
+				PyObject* pyname = PyString_FromString("matplotlib.pyplot");
+				if(!pyname) { throw std::runtime_error("couldnt create string"); }
+
+				PyObject* pymod = PyImport_Import(pyname);
+				Py_DECREF(pyname);
+				if(!pymod) { throw std::runtime_error("Error loading module!"); }
+
+				s_python_function_show = PyObject_GetAttrString(pymod, "show");
+				s_python_function_figure = PyObject_GetAttrString(pymod, "figure");
+				s_python_function_plot = PyObject_GetAttrString(pymod, "plot");
+				s_python_function_legend = PyObject_GetAttrString(pymod, "legend");
+				s_python_function_ylim = PyObject_GetAttrString(pymod, "ylim");
+				s_python_function_xlim = PyObject_GetAttrString(pymod, "xlim");
+
+				if(!s_python_function_show 
+						|| !s_python_function_figure 
+						|| !s_python_function_plot 
+						|| !s_python_function_legend
+						|| !s_python_function_xlim
+						|| !s_python_function_ylim) 
+				{ throw std::runtime_error("Couldnt find required function!"); }
+
+				if(!PyFunction_Check(s_python_function_show)
+					|| !PyFunction_Check(s_python_function_figure)
+					|| !PyFunction_Check(s_python_function_plot)
+					|| !PyFunction_Check(s_python_function_legend)
+					|| !PyFunction_Check(s_python_function_xlim)
+					|| !PyFunction_Check(s_python_function_ylim)) 
+				{ throw std::runtime_error("Python object is unexpectedly not a PyFunction."); }
+
+				s_python_empty_tuple = PyTuple_New(0);
+			}
+
+			~_pyplot_global() {
+				Py_Finalize();
+			}
+		};
+	}
+
+	
+
+	template<typename Numeric>
+	bool plot(const std::vector<Numeric> &x, const std::vector<Numeric> &y, const std::map<std::string, std::string>& keywords)
+	{
+		assert(x.size() == y.size());
+
+		// using python lists
+		PyObject* xlist = PyList_New(x.size());
+		PyObject* ylist = PyList_New(y.size());
+
+		for(size_t i = 0; i < x.size(); ++i) {
+			PyList_SetItem(xlist, i, PyFloat_FromDouble(x.at(i)));
+			PyList_SetItem(ylist, i, PyFloat_FromDouble(y.at(i)));
+		}
+
+		// construct positional args
+		PyObject* args = PyTuple_New(2);
+		PyTuple_SetItem(args, 0, xlist);
+		PyTuple_SetItem(args, 1, ylist);
+
+		Py_DECREF(xlist);
+		Py_DECREF(ylist);
+
+		// construct keyword args
+		PyObject* kwargs = PyDict_New();
+		for(std::map<std::string, std::string>::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::_pyplot_global::get().s_python_function_plot, args, kwargs);
+
+		Py_DECREF(args);
+		Py_DECREF(kwargs);
+		if(res) Py_DECREF(res);
+
+		return res;
+	}
+
+
+	template<typename Numeric>
+	bool plot(const std::vector<Numeric>& x, const std::vector<Numeric>& y, const std::string& format = "") {
+		assert(x.size() == y.size());
+
+		PyObject* xlist = PyList_New(x.size());
+		PyObject* ylist = PyList_New(y.size());
+		PyObject* pystring = PyString_FromString(format.c_str());
+
+		for(size_t i = 0; i < x.size(); ++i) {
+			PyList_SetItem(xlist, i, PyFloat_FromDouble(x.at(i)));
+			PyList_SetItem(ylist, i, PyFloat_FromDouble(y.at(i)));
+		}
+
+		PyObject* plot_args = PyTuple_New(3);
+		PyTuple_SetItem(plot_args, 0, xlist);
+		PyTuple_SetItem(plot_args, 1, ylist);
+		PyTuple_SetItem(plot_args, 2, pystring);
+
+		PyObject* res = PyObject_CallObject(detail::_pyplot_global::get().s_python_function_plot, plot_args);
+
+		Py_DECREF(xlist);
+		Py_DECREF(ylist);
+		Py_DECREF(plot_args);
+		if(res) Py_DECREF(res);
+
+		return res;
+	}
+
+
+	template<typename Numeric>
+	bool named_plot(const std::string& name, const std::vector<Numeric>& x, const std::vector<Numeric>& y, const std::string& format = "") {
+		PyObject* kwargs = PyDict_New();
+		PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str()));
+
+		PyObject* xlist = PyList_New(x.size());
+		PyObject* ylist = PyList_New(y.size());
+		PyObject* pystring = PyString_FromString(format.c_str());
+
+		for(size_t i = 0; i < x.size(); ++i) {
+			PyList_SetItem(xlist, i, PyFloat_FromDouble(x.at(i)));
+			PyList_SetItem(ylist, i, PyFloat_FromDouble(y.at(i)));
+		}
+
+		PyObject* plot_args = PyTuple_New(3);
+		PyTuple_SetItem(plot_args, 0, xlist);
+		PyTuple_SetItem(plot_args, 1, ylist);
+		PyTuple_SetItem(plot_args, 2, pystring);
+
+		PyObject* res = PyObject_Call(detail::_pyplot_global::get().s_python_function_plot, plot_args, kwargs);
+
+		Py_DECREF(kwargs);
+		Py_DECREF(xlist);
+		Py_DECREF(ylist);
+		Py_DECREF(plot_args);
+		if(res) Py_DECREF(res);
+
+		return res;
+	}
+
+	template<typename Numeric>
+	bool plot(const std::vector<Numeric>& y, const std::string& format = "")
+	{
+		std::vector<Numeric> x(y.size());
+		for(size_t i=0; i<x.size(); ++i) x.at(i) = i;
+		return plot(x,y,format);
+	}
+
+	/*
+	 * This group of plot() functions is needed to support initializer lists, i.e. calling
+	 *    plot( {1,2,3,4} )
+	 */
+	bool plot(const std::vector<double>& x, const std::vector<double>& y, const std::string& format = "") {
+		return plot<double>(x,y,format);
+	}
+
+	bool plot(const std::vector<double>& y, const std::string& format = "") {
+		return plot<double>(y,format);
+	}
+
+	bool plot(const std::vector<double>& x, const std::vector<double>& y, const std::map<std::string, std::string>& keywords) {
+		return plot<double>(x,y,keywords);
+	}
+
+	bool named_plot(const std::string& name, const std::vector<double>& x, const std::vector<double>& y, const std::string& format = "") {
+		return named_plot<double>(name,x,y,format);
+	}
+
+	inline void legend() {
+		PyObject* res = PyObject_CallObject(detail::_pyplot_global::get().s_python_function_legend, detail::_pyplot_global::get().s_python_empty_tuple);
+		if(!res) throw std::runtime_error("Call to legend() failed.");
+
+		Py_DECREF(res);
+	}
+
+	template<typename Numeric>
+	void ylim(Numeric left, Numeric right)
+	{
+		PyObject* list = PyList_New(2);
+		PyList_SetItem(list, 0, PyFloat_FromDouble(left));
+		PyList_SetItem(list, 1, PyFloat_FromDouble(right));
+
+		PyObject* args = PyTuple_New(1);
+		PyTuple_SetItem(args, 0, list);
+
+		PyObject* res = PyObject_CallObject(detail::_pyplot_global::get().s_python_function_ylim, args);
+		if(!res) throw std::runtime_error("Call to ylim() failed.");
+
+		Py_DECREF(list);
+		Py_DECREF(args);
+		Py_DECREF(res);
+	}
+
+	template<typename Numeric>
+	void xlim(Numeric left, Numeric right)
+	{
+		PyObject* list = PyList_New(2);
+		PyList_SetItem(list, 0, PyFloat_FromDouble(left));
+		PyList_SetItem(list, 1, PyFloat_FromDouble(right));
+
+		PyObject* args = PyTuple_New(1);
+		PyTuple_SetItem(args, 0, list);
+
+		PyObject* res = PyObject_CallObject(detail::_pyplot_global::get().s_python_function_xlim, args);
+		if(!res) throw std::runtime_error("Call to xlim() failed.");
+
+		Py_DECREF(list);
+		Py_DECREF(args);
+		Py_DECREF(res);
+	}
+
+	inline void show() {
+		PyObject* res = PyObject_CallObject(detail::_pyplot_global::get().s_python_function_show, detail::_pyplot_global::get().s_python_empty_tuple);
+		if(!res) throw std::runtime_error("Call to show() failed.");
+
+		Py_DECREF(res);
+	}
+
+
+}