Equana

Back to Tutorials

3D Surface Plots

Visualize functions of two variables with mesh and surf

Run AllReset

This tutorial teaches you how to create 3D surface visualizations in Equana using mesh() and surf() functions. You'll learn to visualize mathematical functions of two variables, create coordinate grids with meshgrid, and produce interactive 3D plots.

Key Concepts: Surface plots display a function z=f(x,y)z = f(x, y) as a 3D surface. The surf() function creates filled surfaces while mesh() creates wireframes.

Basic Surface Plot

The simplest way to create a surface is to pass a matrix of Z values directly. The X and Y coordinates are automatically generated as row and column indices.

Code [1]Run
# Create a simple 3D surface from a matrix
# Each element Z(i,j) represents the height at position (i,j)

Z = [1, 2, 3, 4; 2, 4, 6, 8; 3, 6, 9, 12; 4, 8, 12, 16]

surf(Z)
title("Basic Surface Plot")
xlabel("Column")
ylabel("Row")
zlabel("Value")

println("Matrix Z:")
println(Z)

Creating Coordinate Grids with meshgrid

For mathematical surfaces, you typically want to define X and Y coordinates explicitly. The meshgrid() function creates 2D coordinate matrices from 1D vectors — essential for evaluating functions over a rectangular domain.

Given vectors x and y, meshgrid(x, y) returns:

  • X: A matrix where each row contains the x values
  • Y: A matrix where each column contains the y values

This allows you to compute Z=f(X,Y)Z = f(X, Y) using element-wise operations.

Code [2]Run
# meshgrid creates 2D coordinate matrices from vectors
# This is essential for defining surfaces over a domain

x = linspace(-2, 2, 5)
y = linspace(-2, 2, 5)

(X, Y) = meshgrid(x, y)

# Now X and Y are matrices where:
# - X has the same value across each row
# - Y has the same value down each column

println("Vector x:")
println(x)
println("Vector y:")
println(y)
println("X matrix (x values repeated in rows):")
println(X)
println("Y matrix (y values repeated in columns):")
println(Y)

Gaussian Surface (Bell Curve)

A 2D Gaussian creates a classic bell curve shape. The function is:

z=e(x2+y2)z = e^{-(x^2 + y^2)}

Notice how element-wise operations (.^) work naturally with meshgrid outputs. The X and Y variables from the previous cell persist, but we'll create a finer grid for a smoother surface.

Code [3]Run
# Create a 2D Gaussian surface (bell curve)
# Using a finer grid for smoother visualization

(X, Y) = meshgrid(linspace(-3, 3, 31), linspace(-3, 3, 31))

# Gaussian function: exp(-(x^2 + y^2))
# Use element-wise power .^ for matrices
Z = exp(-(X.^2 + Y.^2))

surf(X, Y, Z)
title("2D Gaussian (Bell Curve)")
xlabel("X")
ylabel("Y")
zlabel("Z = exp(-(X^2 + Y^2))")

Mesh vs Surf: Wireframe and Filled Surfaces

Equana provides two main 3D surface functions:

  • mesh(X, Y, Z): Creates a wireframe view showing only grid lines
  • surf(X, Y, Z): Creates a filled surface with colored faces

Wireframe plots are useful for:

  • Seeing through the surface to understand its structure
  • Dense grids where filled surfaces would be cluttered
  • Quickly previewing data before final visualization
Code [4]Run
# Compare mesh() wireframe vs surf() filled surface

(X, Y) = meshgrid(linspace(-2, 2, 14), linspace(-2, 2, 14))
Z = sin(sqrt(X.^2 + Y.^2))

# First, show wireframe mesh
mesh(X, Y, Z)
title("mesh() - Wireframe View")
xlabel("X")
ylabel("Y")
zlabel("Z")
Code [5]Run
# Same surface with surf() - filled and colored

surf(X, Y, Z)
title("surf() - Filled Surface")
xlabel("X")
ylabel("Y")
zlabel("Z")

println("The mesh and surf variables persist across cells.")
println("You can modify X, Y, Z and re-run to see different surfaces.")

The peaks() Function

The peaks() function generates a classic test surface — a combination of Gaussian peaks and valleys. It evaluates the formula:

z=3(1x)2ex2(y+1)210(x5x3y5)ex2y213e(x+1)2y2z = 3(1-x)^2 e^{-x^2-(y+1)^2} - 10\left(\frac{x}{5}-x^3-y^5\right)e^{-x^2-y^2} - \frac{1}{3}e^{-(x+1)^2-y^2}

This creates an interesting landscape with three peaks and three valleys, perfect for demonstrations and testing visualization capabilities.

Code [6]Run
# The peaks() function - classic test surface
# Default creates a 49x49 grid

surf(peaks())
title("peaks() - Classic Test Surface")
xlabel("X")
ylabel("Y")
zlabel("Z")
Code [7]Run
# peaks(n) creates an n×n surface
# Smaller values render faster, larger values are smoother

surf(peaks(30))
title("peaks(30) - 30x30 Grid")

println("Try changing the grid size:")
println("  peaks(15) - faster, coarser")
println("  peaks(50) - slower, smoother")
Code [8]Run
# Get coordinate matrices with (X, Y, Z) = peaks(n)
# Useful when you need the actual coordinate values

(X, Y, Z) = peaks(25)

surf(X, Y, Z)
title("peaks() with Coordinates")
xlabel("X")
ylabel("Y")
zlabel("Z")

# Display the range of coordinates
println("X range: " + string(min(X)) + " to " + string(max(X)))
println("Y range: " + string(min(Y)) + " to " + string(max(Y)))
println("Z range: " + string(min(Z)) + " to " + string(max(Z)))

Mathematical Surfaces

Let's explore some classic mathematical surfaces. These demonstrate how to translate mathematical formulas into code using element-wise operations.

2D Sinc Function

The sinc function sin(r)r\frac{\sin(r)}{r} creates a classic ripple pattern used in signal processing and optics:

Code [9]Run
# 2D sinc function: sin(r)/r where r = sqrt(x^2 + y^2)
# Creates a ripple pattern emanating from the origin

(X, Y) = meshgrid(linspace(-8, 8, 41), linspace(-8, 8, 41))

# Compute radial distance from origin
R = sqrt(X.^2 + Y.^2) + 0.001  # add small value to avoid division by zero

# Sinc function
Z = sin(R) ./ R

surf(X, Y, Z)
title("2D Sinc Function")
xlabel("X")
ylabel("Y")
zlabel("sin(r)/r")

Saddle Surface (Hyperbolic Paraboloid)

The hyperbolic paraboloid z=x2y2z = x^2 - y^2 is a classic saddle shape. It curves upward in one direction and downward in the perpendicular direction, used in architecture and mathematics.

Code [10]Run
# Saddle surface (hyperbolic paraboloid)
# z = x^2 - y^2

(X, Y) = meshgrid(linspace(-2, 2, 27), linspace(-2, 2, 27))
Z = X.^2 - Y.^2

surf(X, Y, Z)
title("Saddle Surface")
xlabel("X")
ylabel("Y")
zlabel("Z = X^2 - Y^2")

println("The saddle curves up along x-axis and down along y-axis.")
println("It has a saddle point at the origin (0, 0, 0).")

Ripple Effect

Combining trigonometric and exponential functions creates wave patterns that decay with distance from the origin:

Code [11]Run
# Ripple pattern with exponential decay
# Simulates a wave emanating from center

(X, Y) = meshgrid(linspace(-5, 5, 51), linspace(-5, 5, 51))
R = sqrt(X.^2 + Y.^2)

# Cosine wave multiplied by exponential decay
Z = cos(R * 2) .* exp(-R / 3)

surf(X, Y, Z)
title("Ripple Effect")
xlabel("X")
ylabel("Y")
zlabel("Amplitude")

println("Wave amplitude decreases with distance from origin.")

Multiple Gaussian Peaks

You can create complex landscapes by summing multiple Gaussian functions at different locations:

Code [12]Run
# Surface with multiple Gaussian peaks
# Each peak is centered at a different location

(X, Y) = meshgrid(linspace(-3, 3, 41), linspace(-3, 3, 41))

# Peak 1: centered at (1, 1), height 1.0
Z1 = exp(-((X - 1).^2 + (Y - 1).^2))

# Peak 2: centered at (-1, -1), height 0.8
Z2 = exp(-((X + 1).^2 + (Y + 1).^2)) * 0.8

# Peak 3: centered at (1, -1), height 0.6
Z3 = exp(-((X - 1).^2 + (Y + 1).^2)) * 0.6

# Sum all peaks
Z = Z1 + Z2 + Z3

surf(X, Y, Z)
title("Multiple Gaussian Peaks")
xlabel("X")
ylabel("Y")
zlabel("Height")

Advanced Surfaces

Paraboloid

A paraboloid is a bowl-shaped surface defined by z=cx2y2z = c - x^2 - y^2:

Code [13]Run
# Paraboloid: z = 1 - x^2 - y^2
# Creates a bowl shape (inverted parabola in 3D)

(X, Y) = meshgrid(linspace(-1, 1, 26), linspace(-1, 1, 26))
Z = 1 - X.^2 - Y.^2

surf(X, Y, Z)
title("Paraboloid")
xlabel("X")
ylabel("Y")
zlabel("Z = 1 - X^2 - Y^2")

println("Maximum at origin: z = 1")
println("Zero contour forms a circle of radius 1")

Monkey Saddle

The monkey saddle z=x33xy2z = x^3 - 3xy^2 is a fun mathematical surface with three valleys — supposedly designed for a monkey with a tail!

Code [14]Run
# Monkey saddle: z = x^3 - 3xy^2
# Has three valleys (for two legs and a tail!)

(X, Y) = meshgrid(linspace(-1.5, 1.5, 31), linspace(-1.5, 1.5, 31))
Z = X.^3 - 3 * X .* Y.^2

surf(X, Y, Z)
title("Monkey Saddle")
xlabel("X")
ylabel("Y")
zlabel("Z = X^3 - 3XY^2")

println("Three-fold symmetry: three peaks and three valleys.")

Rosenbrock Function

The Rosenbrock function is a classic benchmark for optimization algorithms. It has a narrow, curved valley that's easy to find but hard to navigate:

f(x,y)=(1x)2+100(yx2)2f(x,y) = (1-x)^2 + 100(y-x^2)^2

The global minimum is at (1,1)(1, 1) where f=0f = 0.

Code [15]Run
# Rosenbrock function - optimization benchmark
# f(x,y) = (1-x)^2 + 100(y-x^2)^2
# Global minimum at (1, 1) where f = 0

(X, Y) = meshgrid(linspace(-1.5, 1.5, 31), linspace(-0.5, 2, 26))
Z = (1 - X).^2 + 100 * (Y - X.^2).^2

# Log scale helps visualize the narrow valley
Z_log = log(Z + 1)

surf(X, Y, Z_log)
title("Rosenbrock Function (log scale)")
xlabel("X")
ylabel("Y")
zlabel("log(f+1)")

println("Minimum at (1, 1) - shown as the dark spot in the valley.")
println("The narrow curved valley makes this hard for optimizers.")

Tips and Best Practices

Grid Resolution

The number of points in linspace controls surface smoothness vs. rendering speed:

PointsEffect
11Coarse, fast rendering
26Good balance
51Smooth, slower
101Very smooth, may be slow

Element-wise Operations

Always use .*, ./, and .^ for operations on meshgrid outputs. These apply element-by-element:

OperationMeaning
X .* YElement-wise multiply
X ./ YElement-wise divide
X .^ 2Element-wise power

Interactive Controls

  • Rotate: Click and drag to rotate the 3D view
  • Zoom: Scroll wheel or pinch to zoom
  • Pan: Right-click drag (or use toolbar)
Code [16]Run
# Experiment with grid resolution
# Change n to see the effect on smoothness

n = 17  # Try: 11 (coarse), 17, 31 (fine)

(X, Y) = meshgrid(linspace(-2, 2, n), linspace(-2, 2, n))
Z = sin(X) .* cos(Y)

surf(X, Y, Z)
title("Grid Resolution: " + string(n) + " x " + string(n))
xlabel("X")
ylabel("Y")
zlabel("sin(X) * cos(Y)")

println("Grid dimensions: " + string(n) + " x " + string(n))
println("Total points: " + string(n * n))

Summary

You've learned to create 3D surface visualizations in Equana:

Key Functions

FunctionPurpose
surf(Z)Surface from matrix
surf(X, Y, Z)Surface with coordinates
mesh(X, Y, Z)Wireframe surface
meshgrid(x, y)Create coordinate grids
peaks(n)Classic test surface

Mathematical Surfaces Covered

  1. Gaussian — Bell curve: e(x2+y2)e^{-(x^2+y^2)}
  2. Sinc — Ripple: sin(r)/r\sin(r)/r
  3. Saddle — Hyperbolic paraboloid: x2y2x^2 - y^2
  4. Paraboloid — Bowl: 1x2y21 - x^2 - y^2
  5. Monkey Saddle — Three valleys: x33xy2x^3 - 3xy^2
  6. Rosenbrock — Optimization benchmark

Best Practices

  • Use meshgrid to create coordinate grids
  • Always use element-wise operators (.*, ./, .^)
  • Choose grid resolution based on smoothness vs. speed needs
  • Add labels with title, xlabel, ylabel, zlabel

Further Exploration

Try modifying the code cells above to:

  • Change function parameters
  • Combine multiple functions
  • Adjust grid resolution
  • Switch between mesh and surf

Workbench

Clear
No variables in workbench

Next Steps