3D Visualizations¶
ggplotly provides geoms for creating interactive 3D plots powered by Plotly's WebGL renderer.
In [1]:
Copied!
import numpy as np
import pandas as pd
from ggplotly import *
df = pd.DataFrame({
'x': np.random.randn(200),
'y': np.random.randn(200),
'z': np.random.randn(200)
})
(ggplot(df, aes(x='x', y='y', z='z')) + geom_point_3d())
import numpy as np
import pandas as pd
from ggplotly import *
df = pd.DataFrame({
'x': np.random.randn(200),
'y': np.random.randn(200),
'z': np.random.randn(200)
})
(ggplot(df, aes(x='x', y='y', z='z')) + geom_point_3d())
Out[1]:
Colored by Group¶
In [2]:
Copied!
df = pd.DataFrame({
'x': np.random.randn(200),
'y': np.random.randn(200),
'z': np.random.randn(200),
'group': np.random.choice(['A', 'B', 'C'], 200)
})
(ggplot(df, aes(x='x', y='y', z='z', color='group')) + geom_point_3d(size=6))
df = pd.DataFrame({
'x': np.random.randn(200),
'y': np.random.randn(200),
'z': np.random.randn(200),
'group': np.random.choice(['A', 'B', 'C'], 200)
})
(ggplot(df, aes(x='x', y='y', z='z', color='group')) + geom_point_3d(size=6))
Out[2]:
Colored by Continuous Variable¶
In [3]:
Copied!
df = pd.DataFrame({
'x': np.random.randn(200),
'y': np.random.randn(200),
'z': np.random.randn(200),
'value': np.random.rand(200) * 100
})
(ggplot(df, aes(x='x', y='y', z='z', color='value'))
+ geom_point_3d(size=5)
+ scale_color_gradient(low='blue', high='red'))
df = pd.DataFrame({
'x': np.random.randn(200),
'y': np.random.randn(200),
'z': np.random.randn(200),
'value': np.random.rand(200) * 100
})
(ggplot(df, aes(x='x', y='y', z='z', color='value'))
+ geom_point_3d(size=5)
+ scale_color_gradient(low='blue', high='red'))
Out[3]:
In [4]:
Copied!
def make_surface(func, x_range=(-5, 5), y_range=(-5, 5), resolution=50):
"""Generate surface data from a function z = f(x, y)."""
x = np.linspace(x_range[0], x_range[1], resolution)
y = np.linspace(y_range[0], y_range[1], resolution)
X, Y = np.meshgrid(x, y)
Z = func(X, Y)
return pd.DataFrame({
'x': X.flatten(),
'y': Y.flatten(),
'z': Z.flatten()
})
def make_surface(func, x_range=(-5, 5), y_range=(-5, 5), resolution=50):
"""Generate surface data from a function z = f(x, y)."""
x = np.linspace(x_range[0], x_range[1], resolution)
y = np.linspace(y_range[0], y_range[1], resolution)
X, Y = np.meshgrid(x, y)
Z = func(X, Y)
return pd.DataFrame({
'x': X.flatten(),
'y': Y.flatten(),
'z': Z.flatten()
})
Paraboloid¶
In [5]:
Copied!
df = make_surface(lambda x, y: x**2 + y**2)
(ggplot(df, aes(x='x', y='y', z='z')) + geom_surface(colorscale='Viridis'))
df = make_surface(lambda x, y: x**2 + y**2)
(ggplot(df, aes(x='x', y='y', z='z')) + geom_surface(colorscale='Viridis'))
Out[5]:
Saddle Surface (Hyperbolic Paraboloid)¶
In [6]:
Copied!
df = make_surface(lambda x, y: x**2 - y**2)
(ggplot(df, aes(x='x', y='y', z='z'))
+ geom_surface(colorscale='RdBu')
+ labs(title='Saddle Surface'))
df = make_surface(lambda x, y: x**2 - y**2)
(ggplot(df, aes(x='x', y='y', z='z'))
+ geom_surface(colorscale='RdBu')
+ labs(title='Saddle Surface'))
Out[6]:
Sinc Function (2D)¶
In [7]:
Copied!
def sinc_2d(x, y):
r = np.sqrt(x**2 + y**2)
return np.where(r == 0, 1, np.sin(r) / r)
df = make_surface(sinc_2d, x_range=(-10, 10), y_range=(-10, 10), resolution=80)
(ggplot(df, aes(x='x', y='y', z='z'))
+ geom_surface(colorscale='Plasma')
+ labs(title='2D Sinc Function'))
def sinc_2d(x, y):
r = np.sqrt(x**2 + y**2)
return np.where(r == 0, 1, np.sin(r) / r)
df = make_surface(sinc_2d, x_range=(-10, 10), y_range=(-10, 10), resolution=80)
(ggplot(df, aes(x='x', y='y', z='z'))
+ geom_surface(colorscale='Plasma')
+ labs(title='2D Sinc Function'))
Out[7]:
Trigonometric Surface¶
In [8]:
Copied!
df = make_surface(lambda x, y: np.sin(x) * np.cos(y))
(ggplot(df, aes(x='x', y='y', z='z'))
+ geom_surface(colorscale='Viridis')
+ labs(title='sin(x) * cos(y)'))
df = make_surface(lambda x, y: np.sin(x) * np.cos(y))
(ggplot(df, aes(x='x', y='y', z='z'))
+ geom_surface(colorscale='Viridis')
+ labs(title='sin(x) * cos(y)'))
Out[8]:
Surface Colorscales¶
Available colorscales for geom_surface:
- Sequential:
Viridis,Plasma,Inferno,Magma,Cividis,Blues,Greens,Reds,YlOrRd,YlGnBu - Diverging:
RdBu,RdYlBu,RdYlGn,BrBG,PiYG,PRGn,Spectral - Other:
Jet,Hot,Electric,Blackbody,Earth,Picnic,Portland
Wireframe Plots¶
Wireframes show the surface structure without solid fills:
In [9]:
Copied!
df = make_surface(lambda x, y: np.sin(x) * np.cos(y), resolution=30)
(ggplot(df, aes(x='x', y='y', z='z'))
+ geom_wireframe(color='steelblue', linewidth=1)
+ labs(title='Wireframe Plot'))
df = make_surface(lambda x, y: np.sin(x) * np.cos(y), resolution=30)
(ggplot(df, aes(x='x', y='y', z='z'))
+ geom_wireframe(color='steelblue', linewidth=1)
+ labs(title='Wireframe Plot'))
Out[9]:
In [10]:
Copied!
# Surface with scatter points
df_surface = make_surface(lambda x, y: np.sin(x) * np.cos(y))
# Sample points on the surface
sample_idx = np.random.choice(len(df_surface), 50, replace=False)
df_points = df_surface.iloc[sample_idx].copy()
df_points['z'] = df_points['z'] + 0.1 # Offset slightly above surface
(ggplot(df_surface, aes(x='x', y='y', z='z'))
+ geom_surface(colorscale='Viridis', opacity=0.7)
+ geom_point_3d(data=df_points, color='red', size=5))
# Surface with scatter points
df_surface = make_surface(lambda x, y: np.sin(x) * np.cos(y))
# Sample points on the surface
sample_idx = np.random.choice(len(df_surface), 50, replace=False)
df_points = df_surface.iloc[sample_idx].copy()
df_points['z'] = df_points['z'] + 0.1 # Offset slightly above surface
(ggplot(df_surface, aes(x='x', y='y', z='z'))
+ geom_surface(colorscale='Viridis', opacity=0.7)
+ geom_point_3d(data=df_points, color='red', size=5))
Out[10]:
In [11]:
Copied!
def gaussian_2d(x, y, sigma=1):
return np.exp(-(x**2 + y**2) / (2 * sigma**2))
df = make_surface(gaussian_2d, x_range=(-3, 3), y_range=(-3, 3), resolution=60)
(ggplot(df, aes(x='x', y='y', z='z'))
+ geom_surface(colorscale='Viridis')
+ labs(title='2D Gaussian Distribution'))
def gaussian_2d(x, y, sigma=1):
return np.exp(-(x**2 + y**2) / (2 * sigma**2))
df = make_surface(gaussian_2d, x_range=(-3, 3), y_range=(-3, 3), resolution=60)
(ggplot(df, aes(x='x', y='y', z='z'))
+ geom_surface(colorscale='Viridis')
+ labs(title='2D Gaussian Distribution'))
Out[11]:
Ripple Effect¶
In [12]:
Copied!
def ripple(x, y):
r = np.sqrt(x**2 + y**2)
return np.sin(3 * r) * np.exp(-0.3 * r)
df = make_surface(ripple, x_range=(-5, 5), y_range=(-5, 5), resolution=80)
(ggplot(df, aes(x='x', y='y', z='z'))
+ geom_surface(colorscale='RdBu')
+ labs(title='Ripple Effect'))
def ripple(x, y):
r = np.sqrt(x**2 + y**2)
return np.sin(3 * r) * np.exp(-0.3 * r)
df = make_surface(ripple, x_range=(-5, 5), y_range=(-5, 5), resolution=80)
(ggplot(df, aes(x='x', y='y', z='z'))
+ geom_surface(colorscale='RdBu')
+ labs(title='Ripple Effect'))
Out[12]:
Rosenbrock Function (Optimization Test)¶
In [13]:
Copied!
def rosenbrock(x, y, a=1, b=100):
return (a - x)**2 + b * (y - x**2)**2
df = make_surface(rosenbrock, x_range=(-2, 2), y_range=(-1, 3), resolution=60)
(ggplot(df, aes(x='x', y='y', z='z'))
+ geom_surface(colorscale='Hot')
+ labs(title='Rosenbrock Function'))
def rosenbrock(x, y, a=1, b=100):
return (a - x)**2 + b * (y - x**2)**2
df = make_surface(rosenbrock, x_range=(-2, 2), y_range=(-1, 3), resolution=60)
(ggplot(df, aes(x='x', y='y', z='z'))
+ geom_surface(colorscale='Hot')
+ labs(title='Rosenbrock Function'))
Out[13]:
Interactivity¶
All 3D plots support:
- Rotation: Click and drag to rotate
- Zoom: Scroll wheel or pinch
- Pan: Shift + drag
- Reset: Double-click
The 3D camera position is automatically saved when you interact, so subsequent renders maintain your viewpoint.