Browse Source

Enable variadic plot command when c++11 is available

Benno Evers 10 years ago
parent
commit
029a61315f
5 changed files with 182 additions and 15 deletions
  1. 37 5
      README.md
  2. 1 2
      examples/minimal.cpp
  3. 19 3
      examples/modern.cpp
  4. BIN
      examples/modern.png
  5. 125 5
      matplotlibcpp.h

+ 37 - 5
README.md

@@ -11,8 +11,7 @@ Complete minimal example:
     #include "matplotlibcpp.h"
     namespace plt = matplotlibcpp;
     int main() {
-        std::vector<double> v {1,2,3,4};
-        plt::plot(v);
+        plt::plot({1,2,3,4});
         plt::show();
     }
     
@@ -53,8 +52,41 @@ A more comprehensive example:
         plt::show();
     }
 
+    // g++ basic.cpp -lpython2.7
+
 Result: ![Basic example](./examples/basic.png)
 
+matplotlib-cpp doesn't require C++11, but will enable some additional syntactic sugar when available:
+
+    #include <cmath>
+    #include "matplotlibcpp.h"
+
+    using namespace std;
+    namespace plt = matplotlibcpp;
+
+    int main() 
+    {    
+        // Prepare data.
+        int n = 5000; // number of data points
+        vector<double> x(n),y(n); 
+        for(int i=0; i<n; ++i) {
+            double t = 2*M_PI*i/n;
+            x.at(i) = 16*sin(t)*sin(t)*sin(t);
+            y.at(i) = 13*cos(t) - 5*cos(2*t) - 2*cos(3*t) - cos(4*t);
+        }
+
+        // plot() takes an arbitrary number of (x,y,format)-triples. 
+        // x must be iterable (that is, anything providing begin(x) and end(x)),
+        // y must either be callable (providing operator() const) or iterable. 
+        plt::plot(x, y, "r-", x, [](double d) { return 12.5+abs(sin(d)); }, "k-");
+
+
+        // show plots
+        plt::show();
+    }    
+
+Result: ![Modern example](./examples/modern.png)
+
 Installation
 ------------
 matplotlib-cpp works by wrapping the popular python plotting library matplotlib. (matplotlib.org)
@@ -63,9 +95,9 @@ 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
+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
+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)
 
@@ -75,5 +107,5 @@ 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
+* Right now, only a small subset of matplotlibs functionality is exposed. Stuff like save()/xlabel()/ylabel() etc. should
   be easy to add.

+ 1 - 2
examples/minimal.cpp

@@ -3,7 +3,6 @@
 namespace plt = matplotlibcpp;
 
 int main() {
-    std::vector<double> v {1,2,3,4};
-    plt::plot(v);
+    plt::plot({1,2,3,4});
     plt::show();
 }

+ 19 - 3
examples/modern.cpp

@@ -1,13 +1,29 @@
 #include "../matplotlibcpp.h"
 
+#include <cmath>
+
+using namespace std;
 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-");
+	//plt::plot({1,2,3,4}); 
+	
+	// Prepare data for parametric plot.
+	int n = 5000; // number of data points
+	vector<double> x(n),y(n); 
+	for(int i=0; i<n; ++i) {
+		double t = 2*M_PI*i/n;
+		x.at(i) = 16*sin(t)*sin(t)*sin(t);
+		y.at(i) = 13*cos(t) - 5*cos(2*t) - 2*cos(3*t) - cos(4*t);
+	}
+
+	// plot() takes an arbitrary number of (x,y,format)-triples. 
+	// x must be iterable (that is, anything providing begin(x) and end(x)),
+	// y must either be callable (providing operator() const) or iterable. 
+	plt::plot(x, y, "r-", x, [](double d) { return 12.5+abs(sin(d)); }, "k-");
+
 
 	// show plots
 	plt::show();

BIN
examples/modern.png


+ 125 - 5
matplotlibcpp.h

@@ -4,6 +4,11 @@
 #include <map>
 #include <numeric>
 #include <stdexcept>
+#include <iostream>
+
+#if __cplusplus > 199711L
+#include <functional>
+#endif
 
 #include <python2.7/Python.h>
 
@@ -26,7 +31,8 @@ namespace matplotlibcpp {
 
 			private:
 			_pyplot_global() {
-				Py_SetProgramName("plotting");	/* optional but recommended */
+				char name[] = "plotting"; // silence compiler warning abount const strings
+				Py_SetProgramName(name);  // optional but recommended
 				Py_Initialize();
 
 				PyObject* pyname = PyString_FromString("matplotlib.pyplot");
@@ -109,13 +115,16 @@ namespace matplotlibcpp {
 	}
 
 
-	template<typename Numeric>
-	bool plot(const std::vector<Numeric>& x, const std::vector<Numeric>& y, const std::string& format = "") {
+	template<typename NumericX, typename NumericY>
+	bool plot(const std::vector<NumericX>& x, const std::vector<NumericY>& y, const std::string& s = "")
+	{
 		assert(x.size() == y.size());
 
+		//std::string format(s);
+
 		PyObject* xlist = PyList_New(x.size());
 		PyObject* ylist = PyList_New(y.size());
-		PyObject* pystring = PyString_FromString(format.c_str());
+		PyObject* pystring = PyString_FromString(s.c_str());
 
 		for(size_t i = 0; i < x.size(); ++i) {
 			PyList_SetItem(xlist, i, PyFloat_FromDouble(x.at(i)));
@@ -181,7 +190,7 @@ namespace matplotlibcpp {
 	 *    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);
+		return plot<double,double>(x,y,format);
 	}
 
 	bool plot(const std::vector<double>& y, const std::string& format = "") {
@@ -196,6 +205,117 @@ namespace matplotlibcpp {
 		return named_plot<double>(name,x,y,format);
 	}
 
+#if __cplusplus > 199711L
+
+	template<typename T>
+	using is_function = typename std::is_function<std::remove_pointer<std::remove_reference<T>>>::type;
+
+	template<bool obj, typename T>
+	struct is_callable_impl;
+
+	template<typename T>
+	struct is_callable_impl<false, T>
+	{
+		typedef is_function<T> type;		
+	}; // a non-object is callable iff it is a function
+
+	template<typename T>
+	struct is_callable_impl<true, T>
+	{
+		struct Fallback { void operator()(); };
+		struct Derived : T, Fallback { };
+
+		template<typename U, U> struct Check;
+
+		template<typename U>
+		static std::true_type test( ... ); // use a variadic function to make use (1) it accepts everything and (2) its always the worst match
+
+		template<typename U>
+		static std::false_type test( Check<void(Fallback::*)(), &U::operator()>* );
+
+	public:
+		typedef decltype(test<Derived>(nullptr)) type;
+		typedef decltype(&Fallback::operator()) dtype;
+		static constexpr bool value = type::value;
+	}; // an object is callable iff it defines operator()
+
+	template<typename T>
+	struct is_callable
+	{
+		// dispatch to is_callable_impl<true, T> or is_callable_impl<false, T> depending on whether T is of class type or not
+		typedef typename is_callable_impl<std::is_class<T>::value, T>::type type; // todo: restore remove_reference
+	};
+
+	template<typename IsYDataCallable>
+	struct plot_impl { };
+
+	template<>
+	struct plot_impl<std::false_type>
+	{
+		template<typename IterableX, typename IterableY>
+		bool operator()(const IterableX& x, const IterableY& y, const std::string& format)
+		{
+			// It's annoying that we have to repeat the code of plot() above
+			auto xs = std::distance(std::begin(x), std::end(x));
+			auto ys = std::distance(std::begin(y), std::end(y));
+			assert(xs == ys && "x and y data must have the same number of elements!");
+
+			PyObject* xlist = PyList_New(xs);
+			PyObject* ylist = PyList_New(ys);
+			PyObject* pystring = PyString_FromString(format.c_str());
+
+			auto itx = std::begin(x), ity = std::begin(y);
+			for(size_t 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, 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<>
+	struct plot_impl<std::true_type>
+	{
+		template<typename Iterable, typename Callable>
+		bool operator()(const Iterable& ticks, const Callable& f, const std::string& format)
+		{
+			//std::cout << "Callable impl called" << std::endl;
+
+			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<double> y;
+			for(auto x : ticks) y.push_back(f(x)); 
+			return plot_impl<std::false_type>()(ticks,y,format);
+		}
+	};
+
+	// recursion stop for the above
+	template<typename... Args>
+	bool plot() { return true; }
+
+	template<typename A, typename B, typename... Args>
+	bool plot(const A& a, const B& b, const std::string& format, Args... args)
+	{
+		return plot_impl<typename is_callable<B>::type>()(a,b,format) && plot(args...);
+	}
+
+#endif
+
 	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.");