-

Visualising Functions And Data Using Graphs

18 Aug, 2015Ariel Schoch

Graphs are the standard way of visualising functions and are useful when debugging the return values of a method, given a specific input value.
An alternative to creating a graph system from scratch within Unity or finding an existing solution on the asset store, is to search for browser based solutions such as chart.js.

One of the benefits to this approach is that it isn't application dependant, meaning you can later use the same graph system when working outside of Unity. You can also easily style and modify things inside of a browser using simple css and html.
On the other hand, using this setup is clearly not suited for realtime plotting of in game values.

The way that the data is passed to the graph is either with a GET request (data is added in the url) or with a POST request (useful for large amounts of data).
Here's an example of a graph, passing the data in the url:
www.bytesheepdigital.com/graphs/?data=0,-1,0,-1,0,-1,0,-1,0,-1,0,1,0,1,0, 1,0,1,0,1,0&labels=-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10

f(x) = x % 2 with input range (-10, 10)

The code on the Unity side of things is rather simple, all you need to do is create a string of comma separated values ("1,2,3,4,...") from the output of the function you want to graph.

public float Mod (float _dividend, float _divisor)
{
	return _dividend % _divisor;
}

public void GetData (float _min, float _max, float _step)
{
	int steps = Mathf.FloorToInt ((_max - _min) / _step) + 1;
	float[] data = new float[steps];
	float[] labels = new float[steps];
	
	for (int i = 0; i < steps; i++)
	{
		float x = _min + i * _step;
		labels[i] = x;
		data[i] = Mod (x, 2f);
	}
	// Load graph using data we just collected
	LoadGraph (data, labels);
}

// Open URL containing graph data
public void LoadGraph (float[] _data, float[] _labels)
{
	string url = "http://www.bytesheepdigital.com/graphs/?";
	Application.OpenURL (url + "data=" + ArrayToParameters (_data) + "&labels=" + ArrayToParameters (_labels));
}

// Return a string of comma separated values
public string ArrayToParameters (float[] _array)
{
	StringBuilder sb = new StringBuilder ();
	for (int i = 0; i < _array.Length; i++)
	{
		sb.Append (_array[i].ToString ());
		// Don't add comma to last element
		if (i < _data.Length - 1)
			sb.Append (",");
	}
	return sb.ToString ();
}

The webpage itself can be hosted on a server or simply be a html file on your local machine and will probably look something like this:

<!-- Read data from URL and pass it to chart.js framework to draw a chart on the canvas object -->
<html>
   <head>
      <title>Graph</title>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.js"></script>
      <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
      <script>
         
         // Return parameter value or true if parameter exists but has no value
         function getUrlParameter(sParam) {
             var sPageURL = decodeURIComponent(window.location.search.substring(1)),
                 sURLVariables = sPageURL.split('&'),
                 sParameterName,
                 i;
         
             for (i = 0; i < sURLVariables.length; i++) {
                 sParameterName = sURLVariables[i].split('=');
         
                 if (sParameterName[0] === sParam) {
                     return sParameterName[1] === undefined ? true : sParameterName[1];
                 }
             }
         };
         
         // Graph settings
         var data = {
            labels: [],
            datasets: [
               {
                  label: "Debug data",
                  fillColor: "rgba(151,187,205,0.2)",
                  strokeColor: "rgba(151,187,205,1)",
                  pointColor: "rgba(151,187,205,1)",
                  pointStrokeColor: "#fff",
                  pointHighlightFill: "#fff",
                  pointHighlightStroke: "rgba(151,187,205,1)",
                  data: []
               }
            ]
         };
         
         $(function() {
            // When document has loaded get the graph data from the url parameters
            var params = getUrlParameter('data').split(',');
            var labels = getUrlParameter('labels').split(',');
            drawGraph(params, labels);
         });
         
         function drawGraph(_params, _labels) {
            // Get context of canvas with jQuery - using jQuery's .get() method
            var ctx = $("#myChart").get(0).getContext("2d");
            // Check whether to use bezier curves
            var bezier = getUrlParameter('bezier');
            // Create a new chart
            var myChart;
            if(getUrlParameter('bar'))
               myChart = new Chart(ctx).Bar(data, { animation: false, responsive: true });
            else
               myChart = new Chart(ctx).Line(data, { bezierCurve: (bezier == true) ? true : false, animation: false, responsive: true });
            
            // Add each point to the graph
            _params.forEach(function(entry, index) {
               if(!_labels[index])
                  _labels[index] = index;
               myChart.addData([entry], _labels[index]);
            });
         }
         
      </script>
   </head>
   <body style="text-align:center;">
      <div style="margin:auto;">
         <canvas id="myChart"></canvas>
      </div>
   </body>
</html>

And finally some more examples:

f(x) = sin(x) with input range (-4, 4)

Bar graph