From cf974ef2fe73ae7831d108fa32ff5d86e54022cf Mon Sep 17 00:00:00 2001
From: Pietro Incardona <i-bird@localhost.localdomain>
Date: Tue, 19 Jan 2016 16:06:25 -0500
Subject: [PATCH] Adding Google Chart

---
 src/Plot/GoogleChart.hpp     | 199 +++++++++++++++++++++++++++++++++--
 src/Plot/Plot_unit_tests.hpp |  98 +++++++++++++++++
 2 files changed, 289 insertions(+), 8 deletions(-)

diff --git a/src/Plot/GoogleChart.hpp b/src/Plot/GoogleChart.hpp
index f718a69a..36445e19 100644
--- a/src/Plot/GoogleChart.hpp
+++ b/src/Plot/GoogleChart.hpp
@@ -11,6 +11,7 @@
 #include <fstream>
 
 #define GGRAPH_COLUMS 1
+#define GGRAPH_POINTS 2
 
 /*! \brief Google chart options
  *
@@ -38,8 +39,34 @@ struct GCoptions
 	size_t width=900;
 	size_t heigh=500;
 
+	//! Flag that specify if the colums are stacked
+	//! Check in Google Chart for is stacked option
+	bool isStacked = false;
+
+	//! Width of the line
+	size_t lineWidth = 4;
+
+	//! Style for all the intervals
+	//! Check Google Chart API intervals option
+	std::string intervalsext;
+
+	//! Style for each interval
+	//! Check Google Chart API interval option
+	std::string intervalext;
+
 	GCoptions & operator=(const GCoptions & opt)
 	{
+		title = opt.title;
+		yAxis = opt.yAxis;
+		xAxis = opt.xAxis;
+		stype = opt.stype;
+		stypeext = opt.stypeext;
+		width=opt.width;
+		heigh=opt.heigh;
+
+		lineWidth = opt.lineWidth;
+		intervalsext = opt.intervalsext;
+
 		return *this;
 	}
 };
@@ -93,14 +120,83 @@ class GoogleChart
 	// set inject HTML;
 	openfpm::vector<std::string> injectHTML;
 
-	/*! \brief Given X and Y vector
+	/*! \brief Given X and Y vector return the string representing the data section of the Google Chart
+	 *
+	 * \param X vector
+	 * \param Y vector
+	 * \param i counter
+	 *
+	 * \return string with the data section
+	 *
+	 */
+	template<typename X, typename Y> std::string get_points_plot_data(const openfpm::vector<X> & x, const openfpm::vector<Y> & y, const openfpm::vector<std::string> & yn, const GCoptions & opt, size_t i)
+	{
+		std::stringstream data;
+
+		size_t interval = 0;
+
+		// we require that the number of x elements are the same as y elements
+
+		if (x.size() != y.size())
+			std::cerr << "Error: " << __FILE__ << ":" << __LINE__ << " vector x and the vector y must have the same number of elements " << x.size() << "!=" << y.size() << "\n";
+
+		// Google chart visualization
+        data << "var data" << i << " = new google.visualization.DataTable();\n";
+		if (std::is_same<X,typename std::string>::value == true)
+			data << "data" << i << ".addColumn("  << "'string'" << "," << "'" << opt.xAxis <<"');\n";
+		else
+			data << "data" << i << ".addColumn("  << "'number'" << "," << "'" << opt.xAxis <<"');\n";
+
+        for (size_t j = 0 ; j < y.last().size() ; j++)
+        {
+        	if (yn.get(j) == std::string("interval"))
+        	{
+        		data << "data" << i << ".addColumn({id:'i" << interval/2 << "', type:'number', role:'interval'});\n";
+        		interval++;
+        	}
+        	else
+        		data << "data" << i << ".addColumn("  << "'number'" << "," << "'" << yn.get(j) <<"');\n";
+        }
+
+        data << "data" << i << ".addRows([\n";
+        for (size_t i = 0 ; i < y.size() ; i++)
+        {
+
+        	for (size_t j = 0 ; j < y.get(i).size()+1 ; j++)
+        	{
+        		// the first is x
+        		if (j == 0)
+        		{
+        			if (std::is_same<X,typename std::string>::value == true)
+        				data << "['"  << x.get(i) << "'";
+        			else
+        				data << "["  << x.get(i);
+        		}
+        		else
+        			data << "," << y.get(i).get(j-1);
+        	}
+        	data << "],\n";
+        }
+
+		return data.str();
+	}
+
+	/*! \brief Given X and Y vector return the string representing the data section of the Google Chart
+	 *
+	 * \param X vector
+	 * \param Y vector
 	 *
+	 * \return string with the data section
 	 *
 	 */
-	template<typename X, typename Y, typename Yn> std::string get_data(const openfpm::vector<X> & x, const openfpm::vector<Y> & y, const openfpm::vector<Yn> & yn, const GCoptions & opt)
+	template<typename X, typename Y, typename Yn> std::string get_colums_bar_data(const openfpm::vector<X> & x, const openfpm::vector<Y> & y, const openfpm::vector<Yn> & yn, const GCoptions & opt, size_t i)
 	{
 		std::stringstream data;
 
+		data << "var data";
+		data << i;
+		data << " = google.visualization.arrayToDataTable([\n";
+
 		// we require that the number of x elements are the same as y elements
 
 		if (x.size() != y.size())
@@ -127,7 +223,7 @@ class GoogleChart
 		return data.str();
 	}
 
-	std::string get_option(const GCoptions & opt)
+	std::string get_colums_bar_option(const GCoptions & opt)
 	{
 		std::stringstream str;
 		str << "title : '" << opt.title << "',\n";
@@ -140,6 +236,26 @@ class GoogleChart
 	    return str.str();
 	}
 
+	std::string get_points_plot_option(const GCoptions & opt)
+	{
+		std::stringstream str;
+		str << "title : '" << opt.title << "',\n";
+	    str << "vAxis: {title: '" << opt.yAxis <<  "'},\n";
+	    str << "hAxis: {title: '" << opt.xAxis << "'},\n";
+	    str << "curveType: 'function',\n";
+
+        str << "lineWidth: " << opt.lineWidth << ",\n";
+        if (opt.intervalsext.size() != 0)
+        	str << "intervals: " << opt.intervalsext << ",\n";
+        else
+        	str << "intervals: " << "{ 'style':'area' }" << ",\n";
+
+        if (opt.intervalext.size() != 0)
+        	str << "interval: " << opt.intervalext << "\n";
+
+	    return str.str();
+	}
+
 	/*! \brief Add a graph data variable
 	 *
 	 * \param of file out
@@ -149,9 +265,7 @@ class GoogleChart
 	 */
 	void addData(std::ofstream & of, size_t i, const std::string & data)
 	{
-		of << "var data";
-		of << i;
-		of << " = google.visualization.arrayToDataTable([\n";
+
 		of << data;
 		of << "]);\n";
 	}
@@ -294,8 +408,77 @@ public:
 		}
 
 		set_of_graphs.last().type = GGRAPH_COLUMS;
-		set_of_graphs.last().data = get_data(x,y,yn,opt);
-		set_of_graphs.last().option = get_option(opt);
+		set_of_graphs.last().data = get_colums_bar_data(x,y,yn,opt,set_of_graphs.size()-1);
+		set_of_graphs.last().option = get_colums_bar_option(opt);
+		set_of_graphs.last().opt = opt;
+	}
+
+	/*! \brief Add a simple plot graph
+	 *
+	 * \param y A vector of vector of values (numbers) the size of y indicate how many x values
+	 *          or colums do we have, while the internal vector can store multiple realizations,
+	 *          or min and max, for error bar
+	 *
+	 * \param x Give a name or number to each colums, so can be a string or a number
+	 *
+	 * \param opt Graph options
+	 *
+	 */
+	template<typename X, typename Y> void AddPointsGraph(openfpm::vector<X> & x, openfpm::vector<Y> & y , const GCoptions & opt)
+	{
+		openfpm::vector<std::string> yn;
+
+		if (y.size() == 0)
+		{
+			std::cerr << "Error: " << __FILE__ << ":" << __LINE__ << " vector y must be filled";
+			return;
+		}
+
+		for (size_t i = 0 ; i < y.last().size() ; i++)
+			yn.add(std::string("line") + std::to_string(i));
+
+		AddPointsGraph(x,y,yn,opt);
+	}
+
+	/*! \brief Add a simple plot graph
+	 *
+	 * \param y A vector of vector of values (numbers) the size of y indicate how many x values
+	 *          or colums we have, while the internal vector store multiple lines,
+	 *          or error bars
+	 *
+	 * \param x Give a name or number to each colums, so can be a string or a number
+	 *
+	 * \param yn Give a name to each line, or specify an error bar
+	 *
+	 * \param opt Graph options
+	 *
+	 */
+	template<typename X, typename Y> void AddPointsGraph(openfpm::vector<X> & x, openfpm::vector<Y> & y , const openfpm::vector<std::string> & yn, const GCoptions & opt)
+	{
+		if (y.size() == 0)
+		{
+			std::cerr << "Error: " << __FILE__ << ":" << __LINE__ << " vector y must be filled";
+			return;
+		}
+
+		set_of_graphs.add();
+		injectHTML.add();
+
+		// Check that all the internal vectors has the same number of elements
+
+		if (y.size() != 0)
+		{
+			size_t sz = y.get(0).size();
+			for (size_t i = 0; i < y.size() ; i++)
+			{
+				if (y.get(i).size() != sz)
+					std::cerr << __FILE__ << ":" << __LINE__ << " error all the elements in the y vector must have the same numbers, element " << i << ": " << y.get(i).size() << " " << " mismatch the numbers of elements at 0: " << sz << "/n";
+			}
+		}
+
+		set_of_graphs.last().type = GGRAPH_POINTS;
+		set_of_graphs.last().data = get_points_plot_data(x,y,yn,opt,set_of_graphs.size()-1);
+		set_of_graphs.last().option = get_points_plot_option(opt);
 		set_of_graphs.last().opt = opt;
 	}
 
diff --git a/src/Plot/Plot_unit_tests.hpp b/src/Plot/Plot_unit_tests.hpp
index c7591ecf..f3df91bb 100644
--- a/src/Plot/Plot_unit_tests.hpp
+++ b/src/Plot/Plot_unit_tests.hpp
@@ -284,6 +284,104 @@ BOOST_AUTO_TEST_CASE( google_chart_with_inject_HTML )
 	BOOST_REQUIRE_EQUAL(true,test);
 }
 
+BOOST_AUTO_TEST_CASE( google_chart_linear_plot )
+{
+	openfpm::vector<std::string> x;
+	openfpm::vector<openfpm::vector<double>> y;
+	openfpm::vector<std::string> yn;
+
+	x.add("colum1");
+	x.add("colum2");
+	x.add("colum3");
+	x.add("colum4");
+	x.add("colum5");
+	x.add("colum6");
+
+	// Here we specify how many lines we have
+	// first Line
+	yn.add("line1");
+
+	// second line + 2 intervals (Error bands)
+	yn.add("line2");
+	yn.add("interval");
+	yn.add("interval");
+	yn.add("interval");
+	yn.add("interval");
+
+	// third line + 1 interval (Error bands)
+	yn.add("line3");
+	yn.add("interval");
+	yn.add("interval");
+
+	// Each line can have multiple intervals or error bars
+	// The first number specify the bottom line
+	// The last three numbers specify the top line + error band (min, max)
+	// The middle 5 line specify the middle lines + one external error band + one internal error band
+
+	y.add({0.10,0.20,0.19,0.22,0.195,0.215,0.35,0.34,0.36});
+	y.add({0.11,0.21,0.18,0.22,0.19,0.215,0.36,0.35,0.37});
+	y.add({0.12,0.22,0.21,0.23,0.215,0.225,0.35,0.34,0.36});
+	y.add({0.15,0.25,0.20,0.26,0.22,0.255,0.36,0.35,0.37});
+	y.add({0.09,0.29,0.25,0.30,0.26,0.295,0.35,0.34,0.36});
+	y.add({0.08,0.28,0.27,0.29,0.275,0.285,0.36,0.35,0.37});
+
+	// Google charts options
+	GCoptions options;
+
+	options.title = std::string("Example");
+	options.yAxis = std::string("Y Axis");
+	options.xAxis = std::string("X Axis");
+	options.lineWidth = 1.0;
+	options.intervalext = std::string("{'i2': { 'color': '#4374E0', 'style':'bars', 'lineWidth':4, 'fillOpacity':1 } }");
+
+	GoogleChart cg;
+	cg.AddPointsGraph(x,y,yn,options);
+	cg.write("gc_plot_out.html");
+
+	bool test = compare("gc_plot_out.html","gc_plot_out_test.html");
+	BOOST_REQUIRE_EQUAL(true,test);
+}
+
+BOOST_AUTO_TEST_CASE( google_chart_linear_plot2 )
+{
+	openfpm::vector<std::string> x;
+	openfpm::vector<openfpm::vector<double>> y;
+
+	x.add("colum1");
+	x.add("colum2");
+	x.add("colum3");
+	x.add("colum4");
+	x.add("colum5");
+	x.add("colum6");
+
+	// Each line can have multiple intervals or error bars
+	// The first number specify the bottom line
+	// The last three numbers specify the top line + error band (min, max)
+	// The middle 5 line specify the middle lines + one external error band + one internal error band
+
+	y.add({0.10,0.20,0.19,0.22,0.195,0.215,0.35,0.34,0.36});
+	y.add({0.11,0.21,0.18,0.22,0.19,0.215,0.36,0.35,0.37});
+	y.add({0.12,0.22,0.21,0.23,0.215,0.225,0.35,0.34,0.36});
+	y.add({0.15,0.25,0.20,0.26,0.22,0.255,0.36,0.35,0.37});
+	y.add({0.09,0.29,0.25,0.30,0.26,0.295,0.35,0.34,0.36});
+	y.add({0.08,0.28,0.27,0.29,0.275,0.285,0.36,0.35,0.37});
+
+	// Google charts options
+	GCoptions options;
+
+	options.title = std::string("Example");
+	options.yAxis = std::string("Y Axis");
+	options.xAxis = std::string("X Axis");
+	options.lineWidth = 1.0;
+
+	GoogleChart cg;
+	cg.AddPointsGraph(x,y,options);
+	cg.write("gc_plot2_out.html");
+
+	bool test = compare("gc_plot2_out.html","gc_plot2_out_test.html");
+	BOOST_REQUIRE_EQUAL(true,test);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 #endif /* OPENFPM_DATA_SRC_PLOT_PLOT_UNIT_TESTS_HPP_ */
-- 
GitLab