# Graphs, visualizing data

- create graphs, from a single line to animated volumes;
- control a particle system;
- write various mathematical functions;
- change behavior while in play mode;
- use the
`Start`

and`Update`

Unity event methods; - write loops, both single and nested;
- use arrays, enumerations, and delegates;

You're assumed to know your way around Unity's editor and know the basics of creating C# scripts. If you've completed the Clock tutorial you're good to go.

Note that I will often omit chunks of code that have remained the same. The context of the new code should be clear.

## Preparations

*4 Split*is a handy predefined view configuration so let's select that. It's in

*Window / Layout / 4 Spit*or in the dropdown list at the top right of the screen. Set the view mode of all of them to

*Textured*. Also rotate the perspective view so that all three axes are pointing towards you.

Now we create a cube via *GameObject / Create Other / Cube*
and set its position to (0.5, 0.5, 0.5). This gives us a reference point for calibrating our views. Now
zoom and pan the views so they're focused on the unit cube.

Finally, select the **Main Camera** and make it match the perpective view via
*GameObject / Align With View* (we did the opposite in the
Clock tutorial). If that doesn't
work right, make sure the correct view is active by clicking in it and then try again.

*GameObject / Create Other / Particle System*and reset its transform. Right now it's producing random particles, which we don't want. So we deactivate everything except its renderer.

Uncheck *Looping*, *Play On Awake*, *Emission*, and *Shape*.
This leaves us with an inert particle system that we can use to visualize graph data.

## Creating the first graph

Rename the particle system object to *Graph 1*,
create a C# script named *Grapher1* as a minimal `GameObject`

class, and
add it as a component to our object.

using UnityEngine; public class Grapher1 : MonoBehaviour {}

`Start`

method, which is a Unity event method that is
called once before updates start happening.
How much particles should we use? The more particles, the higher the sample resolution of our graph. Let's make it customizable, with a default resolution of 10.

using UnityEngine; public class Grapher1 : MonoBehaviour { public int resolution = 10; private ParticleSystem.Particle[] points; void Start () { points = new ParticleSystem.Particle[resolution]; } }

*resolution*to any number we want, which is a bit too generous. Technically it must be at least zero, and a very high resolution will result in very bad performance.

We can make sure that the variable is within bounds when we initialize the array. In case the resolution is out of bounds, we'll reset it to the minimum and log a warning message. Let's use a sane range of 10–100.

void Start () { if (resolution < 10 || resolution > 100) { Debug.LogWarning("Grapher resolution out of bounds, resetting to minimum.", this); resolution = 10; } points = new ParticleSystem.Particle[resolution]; }

Besides the position, we can also use color to provide the same information. We'll make the red color component of the points equal to their position along the X axis.

We'll use a `for`

loop to iterate over all points and set both their position and
color, which are `struct`

values of type `Vector3`

and `Color`

. We also
need to set the size of the particles, otherwise they won't show up. A size of 0.1 is fine.

void Start () { if (resolution < 10 || resolution > 100) { Debug.LogWarning("Grapher resolution out of bounds, resetting to minimum.", this); resolution = 10; } points = new ParticleSystem.Particle[resolution]; float increment = 1f / (resolution - 1); for (int i = 0; i < resolution; i++) { float x = i * increment; points[i].position = new Vector3(x, 0f, 0f); points[i].color = new Color(x, 0f, 0f); points[i].size = 0.1f; } }

`particleSystem`

property that we
can use to access its particle system, if it has one. All we need to do is call its `SetParticles`

methods, providing our array of particles and the amount of particles we wish it to use. Because we want
the system to use all particles, we simply provide the array's length.
We will add an `Update`

method to do this every frame.void Update () { particleSystem.SetParticles(points, points.Length); }

*resolution*field before entering play mode.

*resolution*is only taken into account when the graph is initialized. Updating its value while in play mode doesn't do anything. Let's change that.

A simple way to detect a change of *resolution* is by storing it twice and
then constantly checking whether both values are still the same. If at some point they're different,
we need to rebuild the graph. We'll create a private variable *currentResolution*
for this purpose.

Because rebuilding the points is the same as their first initialization, let's move
that code into a new private method which we name `CreatePoints`

. That way we can reuse
the code.

using UnityEngine; public class Grapher1 : MonoBehaviour { public int resolution = 10; private int currentResolution; private ParticleSystem.Particle[] points; void Start () { CreatePoints(); } private void CreatePoints () { if (resolution < 10 || resolution > 100) { Debug.LogWarning("Grapher resolution out of bounds, resetting to minimum.", this); resolution = 10; } currentResolution = resolution; points = new ParticleSystem.Particle[resolution]; float increment = 1f / (resolution - 1); for(int i = 0; i < resolution; i++){ float x = i * increment; points[i].position = new Vector3(x, 0f, 0f); points[i].color = new Color(x, 0f, 0f); points[i].size = 0.1f; } } void Update () { if (currentResolution != resolution) { CreatePoints(); } particleSystem.SetParticles(points, points.Length); } }

*resolution*. However, you'll notice that the console will spit out warnings whenever

*resolution*goes out of bounds, even while typing. We can make this experience better by using the

`Range`

attribute to tell the Unity editor to use a slider instead of a number box.
As we only care about valid editor input and won't change our resolution via code, we can now remove our own resolution check, though you may decide to keep it.

[Range(10, 100)] public int resolution = 10;

*y = x*or the function

*f(x) = x.*To do this, we need to loop over all points, get their position, use the X value to compute the Y value, then set their new position. Once again we use a

`for`

loop, which
we'll execute each update.void Update () { if (currentResolution != resolution) { CreatePoints(); } for (int i = 0; i < resolution; i++) { Vector3 p = points[i].position; p.y = p.x; points[i].position = p; } particleSystem.SetParticles(points, points.Length); }

void Update () { if (currentResolution != resolution) { CreatePoints(); } for (int i = 0; i < resolution; i++) { Vector3 p = points[i].position; p.y = p.x; points[i].position = p; Color c = points[i].color; c.g = p.y; points[i].color = c; } particleSystem.SetParticles(points, points.Length); }

`NullReferenceException`

error messages.
This is because our private *points*variable was not remembered by Unity when everything got reloaded.

We could solve this problem by checking whether *points* is `null`

besides checking for a resolution change. This will allow us to stay in play mode all the time while editing our code, which is quite convenient.
Note that this check also removes the need for the `Start`

method, so we can delete it.

using UnityEngine; public class Grapher1 : MonoBehaviour { [Range(10, 100)] public int resolution = 10; private int currentResolution; private ParticleSystem.Particle[] points; private void CreatePoints () { currentResolution = resolution; points = new ParticleSystem.Particle[resolution]; float increment = 1f / (resolution - 1); for(int i = 0; i < resolution; i++){ float x = i * increment; points[i].position = new Vector3(x, 0f, 0f); points[i].color = new Color(x, 0f, 0f); points[i].size = 0.1f; } } void Update () { if (currentResolution != resolution || points == null) { CreatePoints(); } for (int i = 0; i < resolution; i++) { Vector3 p = points[i].position; p.y = p.x; points[i].position = p; Color c = points[i].color; c.g = p.y; points[i].color = c; } particleSystem.SetParticles(points, points.Length); } }

## Showing multiple graphs

`p.y`

, the rest of the code can
stay the same. Let's make this explicit by extracting the code that computes `p.y`

and
put it in its own method, which we'll call `Linear`

. All this method does is mimic the
mathematical function *f(x) = x*. We're making this method

`static`

because it doesn't
require an object to function. All it needs is an input value.void Update () { if (currentResolution != resolution || points == null) { CreatePoints(); } for (int i = 0; i < resolution; i++) { Vector3 p = points[i].position; p.y = Linear(p.x); points[i].position = p; Color c = points[i].color; c.g = p.y; points[i].color = c; } particleSystem.SetParticles(points, points.Length); } private static float Linear (float x) { return x; }

`Linear`

. Let's add three new method. The first is
`Exponential`

, which calculates *f(x) = x*. The second is

^{2}`Parabola`

, which calculates *f(x) = (2x - 1)*. The third is

^{2}`Sine`

, which calculates *f(x) = (sin(2πx) + 1) / 2*.

private static float Exponential (float x) { return x * x; } private static float Parabola (float x){ x = 2f * x - 1f; return x * x; } private static float Sine (float x){ return 0.5f + 0.5f * Mathf.Sin(2 * Mathf.PI * x); }

`FunctionOption`

, but because we define it inside our
class it's officially known as `Grapher1.FunctionOption`

.
Add a `public`

variable named `function`

of the new
type. This gives us a nice field in the inspector for selecting functions.

public enum FunctionOption { Linear, Exponential, Parabola, Sine } public FunctionOption function;

*function*. There are various ways to do this and we'll use an array of delegates.

We first define a `delegate`

type for methods that have a single `float`

as both input and output, which corresponds to our function methods. We call it
*FunctionDelegate*. Then we add a `static`

array named
*functionDelegates* and fill it with delegates to our methods,
in the same order that we named them in our enumeration.

Now we can select the desired `delegate`

from the array based on our
*function* variable, by casting it to an integer. We store this
`delegate`

in a temporary variable and use it to calculate the value of Y.

private delegate float FunctionDelegate (float x); private static FunctionDelegate[] functionDelegates = { Linear, Exponential, Parabola, Sine }; void Update () { if(currentResolution != resolution){ CreatePoints(); } FunctionDelegate f = functionDelegates[(int)function]; for(int i = 0; i < resolution; i++){ Vector3 p = points[i].position; p.y = f(p.x); points[i].position = p; Color c = points[i].color; c.g = p.y; points[i].color = c; } particleSystem.SetParticles(points, points.Length); }

Although we need to recreate the graph each time we select another function, the rest of the time
nothing really changes. So it's not required to compute the points each update. However, things
change if we add time to the functions. As an example, let's change the `Sine`

method so it calculates *f(x) = (sin(2πx + Δ) + 1) / 2*, where
*Δ* is equal to the play time. This results in a slowly
animating sine wave. Here's the entire script.

using UnityEngine; public class Grapher1 : MonoBehaviour { public enum FunctionOption { Linear, Exponential, Parabola, Sine } private delegate float FunctionDelegate (float x); private static FunctionDelegate[] functionDelegates = { Linear, Exponential, Parabola, Sine }; public FunctionOption function; [Range(10, 100)] public int resolution = 10; private int currentResolution; private ParticleSystem.Particle[] points; private void CreatePoints () { currentResolution = resolution; points = new ParticleSystem.Particle[resolution]; float increment = 1f / (resolution - 1); for (int i = 0; i < resolution; i++) { float x = i * increment; points[i].position = new Vector3(x, 0f, 0f); points[i].color = new Color(x, 0f, 0f); points[i].size = 0.1f; } } void Update () { if (currentResolution != resolution || points == null) { CreatePoints(); } FunctionDelegate f = functionDelegates[(int)function]; for (int i = 0; i < resolution; i++) { Vector3 p = points[i].position; p.y = f(p.x); points[i].position = p; Color c = points[i].color; c.g = p.y; points[i].color = c; } particleSystem.SetParticles(points, points.Length); } private static float Linear (float x) { return x; } private static float Exponential (float x) { return x * x; } private static float Parabola (float x){ x = 2f * x - 1f; return x * x; } private static float Sine (float x){ return 0.5f + 0.5f * Mathf.Sin(2 * Mathf.PI * x + Time.timeSinceLevelLoad); } }

## Adding an extra dimension

Make sure that you're not in play mode. Create a new Unity object just like *Graph 1* along with a new grapher script,
calling them *Graph 2* and *Grapher2* instead. You can speed this
up by duplicating them and then making the necessary changes. Disable *Graph 1* by
toggling the checkbox in front of its name field, because we're not using it anymore. Copy the code
from `Grapher1`

to `Grapher2`

, only changing the `class`

name to
`Grapher2`

. We'll modify the rest of the code in a moment.

The fastest way to do the above is to duplicate the script and edit its class name, then duplicate the object, rename it, and drag the new script on top of the old one.

`CreatePoints`

method of `Grapher2`

. We need to create a lot more points and use a nested `for`

loop
to initialize them. We now set the Z position and the blue color component too.
private void CreatePoints () { currentResolution = resolution; points = new ParticleSystem.Particle[resolution * resolution]; float increment = 1f / (resolution - 1); int i = 0; for (int x = 0; x < resolution; x++) { for (int z = 0; z < resolution; z++) { Vector3 p = new Vector3(x * increment, 0f, z * increment); points[i].position = p; points[i].color = new Color(p.x, 0f, p.z); points[i++].size = 0.1f; } } }

*Linear*function? It does, but currently only for the first row of points along the Z axis. If you select a different function, only these points will change while the rest remain as they are. This is because in the

`Update`

method currently only loops over `resolution`

points, while it should
loop over all of them.void Update () { if (currentResolution != resolution || points == null) { CreatePoints(); } FunctionDelegate f = functionDelegates[(int)function]; for (int i = 0; i < points.Length; i++) { Vector3 p = points[i].position; p.y = f(p.x); points[i].position = p; Color c = points[i].color; c.g = p.y; points[i].color = c; } particleSystem.SetParticles(points, points.Length); }

*Sort Mode*of the particle system's

*Renderer*module to

*By Distance*instead of

*None*. While this makes sure that the graph is shown correctly from all view angles, it results in a performance hit as well. So don't use it when you're displaying a huge amount of points. Fortunately, if we only look at the graphs from the correct direction, we can get away with not sorting at all.

`FunctionDelegate`

to a vector and a float instead of
just a single float. While we could specify the X and Z position separately, we'll simply give it the
entire position vector. We'll also include the current time, instead of having to look it up inside
the functions themselves.
private delegate float FunctionDelegate (Vector3 p, float t);

`delegate`

is called.
void Update () { if (currentResolution != resolution || points == null) { CreatePoints(); } FunctionDelegate f = functionDelegates[(int)function]; float t = Time.timeSinceLevelLoad; for (int i = 0; i < points.Length; i++) { Vector3 p = points[i].position; p.y = f(p, t); points[i].position = p; Color c = points[i].color; c.g = p.y; points[i].color = c; } particleSystem.SetParticles(points, points.Length); } private static float Linear (Vector3 p, float t) { return p.x; } private static float Exponential (Vector3 p, float t) { return p.x * p.x; } private static float Parabola (Vector3 p, float t){ p.x = 2f * p.x - 1f; return p.x * p.x; } private static float Sine (Vector3 p, float t){ return 0.5f + 0.5f * Mathf.Sin(2 * Mathf.PI * p.x + t); }

*Parabola*function to

*f(x,z) = 1 - (2x - 1)*. We can also go wild with the

^{2}× (2z - 1)^{2}*Sine*function, layering multiple sines to get a complex oscillating effect.

private static float Parabola (Vector3 p, float t){ p.x += p.x - 1f; p.z += p.z - 1f; return 1f - p.x * p.x * p.z * p.z; } private static float Sine (Vector3 p, float t){ return 0.50f + 0.25f * Mathf.Sin(4f * Mathf.PI * p.x + 4f * t) * Mathf.Sin(2f * Mathf.PI * p.z + t) + 0.10f * Mathf.Cos(3f * Mathf.PI * p.x + 5f * t) * Mathf.Cos(5f * Mathf.PI * p.z + 3f * t) + 0.15f * Mathf.Sin(Mathf.PI * p.x + 0.6f * t); }

`Grapher2`

by adding a *Ripple*function, which is a single sine wave emanating from the center of the grid. Here's the entire script.

using UnityEngine; public class Grapher2 : MonoBehaviour { public enum FunctionOption { Linear, Exponential, Parabola, Sine, Ripple } private delegate float FunctionDelegate (Vector3 p, float t); private static FunctionDelegate[] functionDelegates = { Linear, Exponential, Parabola, Sine, Ripple }; public FunctionOption function; [Range(10, 100)] public int resolution = 10; private int currentResolution; private ParticleSystem.Particle[] points; private void CreatePoints () { currentResolution = resolution; points = new ParticleSystem.Particle[resolution * resolution]; float increment = 1f / (resolution - 1); int i = 0; for (int x = 0; x < resolution; x++) { for (int z = 0; z < resolution; z++) { Vector3 p = new Vector3(x * increment, 0f, z * increment); points[i].position = p; points[i].color = new Color(p.x, 0f, p.z); points[i++].size = 0.1f; } } } void Update () { if (currentResolution != resolution || points == null) { CreatePoints(); } FunctionDelegate f = functionDelegates[(int)function]; float t = Time.timeSinceLevelLoad; for (int i = 0; i < points.Length; i++) { Vector3 p = points[i].position; p.y = f(p, t); points[i].position = p; Color c = points[i].color; c.g = p.y; points[i].color = c; } particleSystem.SetParticles(points, points.Length); } private static float Linear (Vector3 p, float t) { return p.x; } private static float Exponential (Vector3 p, float t) { return p.x * p.x; } private static float Parabola (Vector3 p, float t){ p.x = 2f * p.x - 1f; p.z = 2f * p.z - 1f; return 1f - p.x * p.x * p.z * p.z; } private static float Sine (Vector3 p, float t){ return 0.50f + 0.25f * Mathf.Sin(4 * Mathf.PI * p.x + 4 * t) * Mathf.Sin(2 * Mathf.PI * p.z + t) + 0.10f * Mathf.Cos(3 * Mathf.PI * p.x + 5 * t) * Mathf.Cos(5 * Mathf.PI * p.z + 3 * t) + 0.15f * Mathf.Sin(Mathf.PI * p.x + 0.6f * t); } private static float Ripple (Vector3 p, float t){ p.x -= 0.5f; p.z -= 0.5f; float squareRadius = p.x * p.x + p.z * p.z; return 0.5f + Mathf.Sin(15f * Mathf.PI * squareRadius - 2f * t) / (2f + 100f * squareRadius); } }

## Full-blown 3D

Duplicate *Graph 2* and *Grapher2* and change them into
*Graph 3* and *Grapher3*, just like we did for the second graph.
Don't forget to disable *Graph 2* and make sure that you're not in play mode.

`Grapher3`

. First, we'll limit `resolution`

to 30, which translates to 27,000 points. Make sure you adjust the resolution slider so it's in range. If you created it from a duplicate graph set to a higher resolution, it will still have this value.
We also need to initialize the Y position of the points and the green color component.

[Range(10, 30)] public int resolution = 10; private void CreatePoints () { currentResolution = resolution; points = new ParticleSystem.Particle[resolution * resolution * resolution]; float increment = 1f / (resolution - 1); int i = 0; for (int x = 0; x < resolution; x++) { for (int z = 0; z < resolution; z++) { for (int y = 0; y < resolution; y++) { Vector3 p = new Vector3(x, y, z) * increment; points[i].position = p; points[i].color = new Color(p.x, p.y, p.z); points[i++].size = 0.1f; } } } }

*Graph 3*looks the same as

*Graph 2*, except that it might seem a little more solid. This is because we still set the Y positions in the

`Update`

method. So all points with the same X and Z position will collapse to the same
Y position. We must no longer set the Y position, but the color's alpha component instead. That way
our functions will define the volume's density.
void Update () { if (currentResolution != resolution || points == null) { CreatePoints(); } FunctionDelegate f = functionDelegates[(int)function]; float t = Time.timeSinceLevelLoad; for (int i = 0; i < points.Length; i++) { Color c = points[i].color; c.a = f(points[i].position, t); points[i].color = c; } particleSystem.SetParticles(points, points.Length); }

`Sine`

and
`Ripple`

, produce somewhat interesting results.
Let's change the function computed by `Linear`

into *f(x,y,z) = 1 - x - y - z*.
That way it starts solid at (0, 0, 0) and fades to transparent along a straight line. We can do a
similar thing with `Exponential`

as well. Even better, let's animate them a bit so
it's more interesting to look at.

private static float Linear (Vector3 p, float t) { return 1f - p.x - p.y - p.z + 0.5f * Mathf.Sin(t); } private static float Exponential (Vector3 p, float t) { return 1f - p.x * p.x - p.y * p.y - p.z * p.z + 0.5f * Mathf.Sin(t); }

`Parabola`

so it will produce a cylinder, once again with a
little pulsating animation. We also add the third dimension to `Ripple`

,
turning it into a sphere-spawning animation.
private static float Parabola (Vector3 p, float t){ p.x += p.x - 1f; p.z += p.z - 1f; return 1f - p.x * p.x - p.z * p.z + 0.5f * Mathf.Sin(t); } private static float Ripple (Vector3 p, float t){ p.x -= 0.5f; p.y -= 0.5f; p.z -= 0.5f; float squareRadius = p.x * p.x + p.y * p.y + p.z * p.z; return Mathf.Sin(4f * Mathf.PI * squareRadius - 2f * t); }

`Sine`

too. We transform it into eight blobs
by multiplying the square sines of X, Y, and Z together. We only animate the Z-based sine, but we make
a distinction here. The top and bottom half of the graph will move in opposite directions.
private static float Sine (Vector3 p, float t){ float x = Mathf.Sin(2 * Mathf.PI * p.x); float y = Mathf.Sin(2 * Mathf.PI * p.y); float z = Mathf.Sin(2 * Mathf.PI * p.z + (p.y > 0.5f ? t : -t)); return x * x * y * y * z * z; }

`absolute`

field to
toggle such behaviour, along with a `threshold`

field that determines how solid a
voxel must be before it becomes visible. Each update, we check whether `asbolute`

is on
and use that to decide how to set the alpha of the points. Here's the complete script.
using UnityEngine; public class Grapher3 : MonoBehaviour { public enum FunctionOption { Linear, Exponential, Parabola, Sine, Ripple } private delegate float FunctionDelegate (Vector3 p, float t); private static FunctionDelegate[] functionDelegates = { Linear, Exponential, Parabola, Sine, Ripple }; public FunctionOption function; public bool absolute; public float threshold = 0.5f; [Range(10, 30)] public int resolution = 10; private int currentResolution; private ParticleSystem.Particle[] points; private void CreatePoints () { currentResolution = resolution; points = new ParticleSystem.Particle[resolution * resolution * resolution]; float increment = 1f / (resolution - 1); int i = 0; for (int x = 0; x < resolution; x++) { for (int z = 0; z < resolution; z++) { for (int y = 0; y < resolution; y++) { Vector3 p = new Vector3(x, y, z) * increment; points[i].position = p; points[i].color = new Color(p.x, p.y, p.z); points[i++].size = 0.1f; } } } } void Update () { if (currentResolution != resolution || points == null) { CreatePoints(); } FunctionDelegate f = functionDelegates[(int)function]; float t = Time.timeSinceLevelLoad; if (absolute) { for (int i = 0; i < points.Length; i++) { Color c = points[i].color; c.a = f(points[i].position, t) >= threshold ? 1f : 0f; points[i].color = c; } } else { for (int i = 0; i < points.Length; i++) { Color c = points[i].color; c.a = f(points[i].position, t); points[i].color = c; } } particleSystem.SetParticles(points, points.Length); } private static float Linear (Vector3 p, float t) { return 1f - p.x - p.y - p.z + 0.5f * Mathf.Sin(t); } private static float Exponential (Vector3 p, float t) { return 1f - p.x * p.x - p.y * p.y - p.z * p.z + 0.5f * Mathf.Sin(t); } private static float Parabola (Vector3 p, float t){ p.x += p.x - 1f; p.z += p.z - 1f; return 1f - p.x * p.x - p.z * p.z + 0.5f * Mathf.Sin(t); } private static float Sine (Vector3 p, float t){ float x = Mathf.Sin(2 * Mathf.PI * p.x); float y = Mathf.Sin(2 * Mathf.PI * p.y); float z = Mathf.Sin(2 * Mathf.PI * p.z + (p.y > 0.5f ? t : -t)); return x * x * y * y * z * z; } private static float Ripple (Vector3 p, float t){ p.x -= 0.5f; p.y -= 0.5f; p.z -= 0.5f; float squareRadius = p.x * p.x + p.y * p.y + p.z * p.z; return Mathf.Sin(4f * Mathf.PI * squareRadius - 2f * t); } }

Enjoyed the tutorial? Help me make more by becoming a patron!

## Downloads

- graphs.unitypackage
- The finished project.