Python Tutorial

I. Plotting


1. Plotting Functions

Matplotlib is Python's primary plotting library. Let's explore how to create various types of plots:

Basic Function Plotting

import numpy as np
import matplotlib.pyplot as plt

# Define x values
x = np.linspace(-2*np.pi, 2*np.pi, 1000)

# Define multiple functions
y1 = np.sin(x)
y2 = np.cos(x)
y3 = np.sin(x) * np.exp(-0.1 * np.abs(x))  # Damped sine wave

# Create the plot
plt.figure(figsize=(10, 6))
plt.plot(x, y1, 'b-', label='sin(x)', linewidth=2)
plt.plot(x, y2, 'r--', label='cos(x)', linewidth=2)
plt.plot(x, y3, 'g-.', label='sin(x)·exp(-0.1|x|)', linewidth=2)

# Customize the plot
plt.xlabel('x', fontsize=12)
plt.ylabel('y', fontsize=12)
plt.title('Trigonometric Functions', fontsize=14)
plt.grid(True, alpha=0.3)
plt.legend(loc='upper right')
plt.axhline(y=0, color='k', linewidth=0.5)
plt.axvline(x=0, color='k', linewidth=0.5)

# Set axis limits
plt.xlim(-2*np.pi, 2*np.pi)
plt.ylim(-1.5, 1.5)

# Add pi markers on x-axis
pi_ticks = [-2*np.pi, -np.pi, 0, np.pi, 2*np.pi]
pi_labels = ['-2π', '-π', '0', 'π', '2π']
plt.xticks(pi_ticks, pi_labels)

plt.show()

Interactive Parameter Explorer

Explore how parameters affect function behavior:



2. Implicit Plot

Implicit plots are useful for visualizing equations of the form f(x,y) = 0. Here's how to create them:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle

# Create a grid of x and y values
x = np.linspace(-3, 3, 400)
y = np.linspace(-3, 3, 400)
X, Y = np.meshgrid(x, y)

# Define implicit functions
# Circle: x² + y² = 4
F1 = X**2 + Y**2 - 4

# Ellipse: x²/4 + y²/1 = 1
F2 = X**2/4 + Y**2/1 - 1

# Hyperbola: x²/4 - y²/1 = 1
F3 = X**2/4 - Y**2/1 - 1

# Lemniscate: (x² + y²)² = 2²(x² - y²)
F4 = (X**2 + Y**2)**2 - 4*(X**2 - Y**2)

# Create subplots
fig, axes = plt.subplots(2, 2, figsize=(12, 12))

# Plot 1: Circle
ax1 = axes[0, 0]
contour1 = ax1.contour(X, Y, F1, levels=[0], colors='blue', linewidths=2)
ax1.set_title('Circle: x² + y² = 4')
ax1.grid(True, alpha=0.3)
ax1.set_aspect('equal')
ax1.set_xlim(-3, 3)
ax1.set_ylim(-3, 3)

# Plot 2: Ellipse
ax2 = axes[0, 1]
contour2 = ax2.contour(X, Y, F2, levels=[0], colors='red', linewidths=2)
ax2.set_title('Ellipse: x²/4 + y² = 1')
ax2.grid(True, alpha=0.3)
ax2.set_aspect('equal')
ax2.set_xlim(-3, 3)
ax2.set_ylim(-3, 3)

# Plot 3: Hyperbola
ax3 = axes[1, 0]
contour3 = ax3.contour(X, Y, F3, levels=[0], colors='green', linewidths=2)
ax3.set_title('Hyperbola: x²/4 - y² = 1')
ax3.grid(True, alpha=0.3)
ax3.set_aspect('equal')
ax3.set_xlim(-3, 3)
ax3.set_ylim(-3, 3)

# Plot 4: Lemniscate
ax4 = axes[1, 1]
contour4 = ax4.contour(X, Y, F4, levels=[0], colors='purple', linewidths=2)
ax4.set_title('Lemniscate: (x² + y²)² = 4(x² - y²)')
ax4.grid(True, alpha=0.3)
ax4.set_aspect('equal')
ax4.set_xlim(-3, 3)
ax4.set_ylim(-3, 3)

plt.tight_layout()
plt.show()

# Advanced example: Multiple level curves
fig, ax = plt.subplots(figsize=(8, 8))

# Function: x²y - x²/2 - y²/2 = c
F = X**2 * Y - X**2/2 - Y**2/2

# Plot multiple level curves
levels = np.linspace(-2, 2, 15)
contour = ax.contour(X, Y, F, levels=levels, cmap='viridis')
ax.clabel(contour, inline=True, fontsize=8)

ax.set_title('Level Curves: x²y - x²/2 - y²/2 = c')
ax.grid(True, alpha=0.3)
ax.set_aspect('equal')
ax.set_xlabel('x')
ax.set_ylabel('y')

plt.show()

3. Vertical and Horizontal Lines

Adding reference lines helps highlight important features in plots:

import numpy as np
import matplotlib.pyplot as plt

# Create data
x = np.linspace(0, 10, 100)
y = 2 * np.sin(x) + 1

# Create the plot
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))

# Left plot: Basic vertical and horizontal lines
ax1.plot(x, y, 'b-', linewidth=2, label='2sin(x) + 1')

# Add horizontal lines
ax1.axhline(y=1, color='r', linestyle='--', label='y = 1 (mean)')
ax1.axhline(y=3, color='g', linestyle=':', label='y = 3 (max)')
ax1.axhline(y=-1, color='g', linestyle=':', label='y = -1 (min)')

# Add vertical lines at specific x values
ax1.axvline(x=np.pi/2, color='orange', linestyle='-', alpha=0.7, label='x = π/2')
ax1.axvline(x=3*np.pi/2, color='orange', linestyle='-', alpha=0.7, label='x = 3π/2')

# Customize
ax1.set_title('Vertical and Horizontal Reference Lines')
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.grid(True, alpha=0.3)
ax1.legend(loc='upper right')
ax1.set_xlim(0, 10)

# Right plot: Creating a coordinate system with asymptotes
ax2.set_title('Function with Asymptotes: y = 1/(x-2) + 1')

# Plot function with discontinuity
x1 = np.linspace(0, 1.98, 100)
x2 = np.linspace(2.02, 5, 100)
y1 = 1/(x1 - 2) + 1
y2 = 1/(x2 - 2) + 1

ax2.plot(x1, y1, 'b-', linewidth=2)
ax2.plot(x2, y2, 'b-', linewidth=2, label='y = 1/(x-2) + 1')

# Add asymptotes
ax2.axvline(x=2, color='red', linestyle='--', linewidth=2, label='Vertical asymptote: x = 2')
ax2.axhline(y=1, color='green', linestyle='--', linewidth=2, label='Horizontal asymptote: y = 1')

# Fill regions
ax2.axvspan(1.9, 2.1, alpha=0.2, color='red', label='Undefined region')

# Customize
ax2.set_xlabel('x')
ax2.set_ylabel('y')
ax2.grid(True, alpha=0.3)
ax2.legend(loc='upper right')
ax2.set_xlim(0, 5)
ax2.set_ylim(-10, 10)

plt.tight_layout()
plt.show()

# Interactive example: Click to add lines
print("""Interactive example:
Run this code and click on the plot to add vertical lines at clicked positions!""")

fig, ax = plt.subplots(figsize=(10, 6))
x = np.linspace(0, 10, 1000)
y = np.sin(2*x) * np.exp(-0.1*x)
ax.plot(x, y, 'b-', linewidth=2)
ax.set_title('Click to add vertical lines!')
ax.grid(True, alpha=0.3)

# Store line references
vertical_lines = []

def on_click(event):
    if event.inaxes == ax:
        # Add vertical line at click position
        line = ax.axvline(x=event.xdata, color='red', linestyle='--', alpha=0.7)
        vertical_lines.append(line)
        
        # Print coordinates
        y_val = np.sin(2*event.xdata) * np.exp(-0.1*event.xdata)
        ax.text(event.xdata, y_val, f'({event.xdata:.2f}, {y_val:.2f})', 
                ha='left', va='bottom', fontsize=8)
        
        fig.canvas.draw()

# Connect the click event
fig.canvas.mpl_connect('button_press_event', on_click)

plt.show()

4. Figures with Arrows

Arrows are essential for annotating plots and showing vectors or directions:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches

# Create figure with subplots
fig, axes = plt.subplots(2, 2, figsize=(14, 12))

# Subplot 1: Basic arrows for annotation
ax1 = axes[0, 0]
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)
ax1.plot(x, y, 'b-', linewidth=2)
ax1.set_title('Annotating with Arrows')

# Add arrows pointing to specific features
ax1.annotate('Maximum', xy=(np.pi/2, 1), xytext=(np.pi/2 + 1, 1.3),
             arrowprops=dict(arrowstyle='->', color='red', lw=2),
             fontsize=12, ha='center')

ax1.annotate('Zero crossing', xy=(np.pi, 0), xytext=(np.pi + 0.5, -0.5),
             arrowprops=dict(arrowstyle='->', color='green', lw=2),
             fontsize=12)

ax1.grid(True, alpha=0.3)
ax1.set_xlabel('x')
ax1.set_ylabel('sin(x)')

# Subplot 2: Vector field
ax2 = axes[0, 1]
ax2.set_title('Vector Field: F(x,y) = (-y, x)')

# Create grid for vector field
x = np.linspace(-2, 2, 10)
y = np.linspace(-2, 2, 10)
X, Y = np.meshgrid(x, y)

# Define vector components
U = -Y  # dx/dt = -y
V = X   # dy/dt = x

# Normalize vectors for better visualization
M = np.sqrt(U**2 + V**2)
U_norm = U / M
V_norm = V / M

# Plot vector field
ax2.quiver(X, Y, U_norm, V_norm, M, cmap='viridis', scale=20)
ax2.set_aspect('equal')
ax2.grid(True, alpha=0.3)
ax2.set_xlabel('x')
ax2.set_ylabel('y')

# Add a sample trajectory
theta = np.linspace(0, 4*np.pi, 100)
r = 1.5
traj_x = r * np.cos(theta) * np.exp(-0.1*theta)
traj_y = r * np.sin(theta) * np.exp(-0.1*theta)
ax2.plot(traj_x, traj_y, 'r-', linewidth=2, label='Sample trajectory')
ax2.legend()

# Subplot 3: Custom arrow styles
ax3 = axes[1, 0]
ax3.set_title('Different Arrow Styles')
ax3.set_xlim(0, 10)
ax3.set_ylim(0, 10)

# Different arrow styles
arrow_styles = ['->', '-|>', '<->', '<|-|>', 'fancy', 'simple', 'wedge']
colors = ['red', 'blue', 'green', 'orange', 'purple', 'brown', 'pink']

for i, (style, color) in enumerate(zip(arrow_styles, colors)):
    y_pos = 9 - i * 1.2
    ax3.annotate('', xy=(8, y_pos), xytext=(2, y_pos),
                 arrowprops=dict(arrowstyle=style, color=color, lw=2))
    ax3.text(0.5, y_pos, f'Style: {style}', va='center', fontsize=10)

ax3.set_xticks([])
ax3.set_yticks([])

# Subplot 4: Phase portrait with arrows
ax4 = axes[1, 1]
ax4.set_title('Phase Portrait of a Dynamical System')

# Create a denser grid for phase portrait
x = np.linspace(-3, 3, 20)
y = np.linspace(-3, 3, 20)
X, Y = np.meshgrid(x, y)

# Nonlinear system: dx/dt = y, dy/dt = -x - 0.5*y
U = Y
V = -X - 0.5*Y

# Normalize
N = np.sqrt(U**2 + V**2)
U_norm = U / (N + 0.001)  # Avoid division by zero
V_norm = V / (N + 0.001)

# Plot phase portrait
ax4.quiver(X, Y, U_norm, V_norm, N, cmap='coolwarm', scale=25, alpha=0.8)

# Add some trajectories
for x0, y0 in [(1, 0), (-1, 0), (0, 2), (0, -2)]:
    t = np.linspace(0, 10, 1000)
    # Solve using simple Euler method
    x_traj = [x0]
    y_traj = [y0]
    dt = t[1] - t[0]
    for i in range(len(t)-1):
        x_new = x_traj[-1] + y_traj[-1] * dt
        y_new = y_traj[-1] + (-x_traj[-1] - 0.5*y_traj[-1]) * dt
        x_traj.append(x_new)
        y_traj.append(y_new)
    
    ax4.plot(x_traj, y_traj, linewidth=2, alpha=0.7)
    ax4.plot(x0, y0, 'o', markersize=8)  # Initial condition

ax4.set_aspect('equal')
ax4.grid(True, alpha=0.3)
ax4.set_xlabel('x')
ax4.set_ylabel('y')
ax4.set_xlim(-3, 3)
ax4.set_ylim(-3, 3)

plt.tight_layout()
plt.show()

5. Electronic Circuits

Visualizing electronic circuits and their behavior using Python:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.patches import Rectangle, Circle, FancyBboxPatch

# RC Circuit Analysis
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))

# Left plot: Draw RC circuit
ax1.set_title('RC Circuit Diagram')
ax1.set_xlim(0, 10)
ax1.set_ylim(0, 8)
ax1.axis('off')

# Draw circuit components
# Voltage source
ax1.add_patch(Circle((2, 4), 0.5, fill=False, linewidth=2))
ax1.text(2, 4, 'V', ha='center', va='center', fontsize=14, weight='bold')
ax1.plot([2, 2], [3.5, 2], 'k-', linewidth=2)  # Bottom connection
ax1.plot([2, 2], [4.5, 6], 'k-', linewidth=2)  # Top connection

# Top horizontal line
ax1.plot([2, 5], [6, 6], 'k-', linewidth=2)

# Resistor (zigzag)
resistor_x = np.linspace(5, 6.5, 7)
resistor_y = [6, 6.3, 5.7, 6.3, 5.7, 6.3, 6]
ax1.plot(resistor_x, resistor_y, 'k-', linewidth=2)
ax1.text(5.75, 6.7, 'R', ha='center', fontsize=12)

# Vertical line
ax1.plot([6.5, 8], [6, 6], 'k-', linewidth=2)
ax1.plot([8, 8], [6, 4], 'k-', linewidth=2)

# Capacitor
ax1.plot([7.7, 7.7], [4.3, 3.7], 'k-', linewidth=3)  # Top plate
ax1.plot([8.3, 8.3], [4.3, 3.7], 'k-', linewidth=3)  # Bottom plate
ax1.text(8.8, 4, 'C', ha='center', fontsize=12)

# Bottom line
ax1.plot([8, 8], [4, 2], 'k-', linewidth=2)
ax1.plot([8, 2], [2, 2], 'k-', linewidth=2)

# Add labels
ax1.text(1, 6.5, 'V₀ = 10V', fontsize=11)
ax1.text(4.5, 7, 'R = 1kΩ', fontsize=11)
ax1.text(9, 4, 'C = 1μF', fontsize=11)

# Right plot: Voltage across capacitor over time
ax2.set_title('Capacitor Charging: Voltage vs Time')

# Circuit parameters
V0 = 10  # Voltage source (V)
R = 1000  # Resistance (Ohms)
C = 1e-6  # Capacitance (F)
tau = R * C  # Time constant

# Time array
t = np.linspace(0, 5*tau, 1000)

# Voltage across capacitor: V_C(t) = V0(1 - e^(-t/tau))
V_C = V0 * (1 - np.exp(-t/tau))

# Current through circuit: I(t) = (V0/R) * e^(-t/tau)
I = (V0/R) * np.exp(-t/tau)

# Plot voltage
ax2.plot(t*1000, V_C, 'b-', linewidth=2, label='V_C(t)')
ax2.axhline(y=V0, color='r', linestyle='--', alpha=0.7, label=f'V₀ = {V0}V')
ax2.axhline(y=0.632*V0, color='g', linestyle=':', alpha=0.7)
ax2.axvline(x=tau*1000, color='g', linestyle=':', alpha=0.7)

# Mark time constant
ax2.plot(tau*1000, 0.632*V0, 'go', markersize=8)
ax2.annotate(f'τ = RC = {tau*1000:.1f}ms\nV = 0.632V₀', 
             xy=(tau*1000, 0.632*V0), xytext=(tau*1000 + 0.5, 0.632*V0 - 1),
             arrowprops=dict(arrowstyle='->', color='green'))

ax2.set_xlabel('Time (ms)')
ax2.set_ylabel('Voltage (V)')
ax2.grid(True, alpha=0.3)
ax2.legend()
ax2.set_xlim(0, 5*tau*1000)
ax2.set_ylim(0, 12)

# Add secondary y-axis for current
ax2_twin = ax2.twinx()
ax2_twin.plot(t*1000, I*1000, 'orange', linewidth=2, alpha=0.7, label='I(t)')
ax2_twin.set_ylabel('Current (mA)', color='orange')
ax2_twin.tick_params(axis='y', labelcolor='orange')
ax2_twin.legend(loc='upper right')

plt.tight_layout()
plt.show()

# Interactive RLC circuit response
print("\nRLC Circuit Analysis:")

# RLC parameters
L = 0.1  # Inductance (H)
R_rlc = 100  # Resistance (Ohms)
C_rlc = 1e-6  # Capacitance (F)

# Natural frequency and damping
omega_0 = 1/np.sqrt(L*C_rlc)
zeta = R_rlc/(2*np.sqrt(L/C_rlc))  # Damping ratio

print(f"Natural frequency: ω₀ = {omega_0:.1f} rad/s")
print(f"Damping ratio: ζ = {zeta:.3f}")

if zeta < 1:
    print("System is underdamped - will oscillate")
elif zeta == 1:
    print("System is critically damped")
else:
    print("System is overdamped")

# Plot RLC response
fig, ax = plt.subplots(figsize=(10, 6))
t = np.linspace(0, 0.001, 1000)

if zeta < 1:  # Underdamped
    omega_d = omega_0 * np.sqrt(1 - zeta**2)
    v_out = V0 * np.exp(-zeta*omega_0*t) * (np.cos(omega_d*t) + 
             (zeta*omega_0/omega_d)*np.sin(omega_d*t))
    ax.set_title(f'Underdamped RLC Response (ζ = {zeta:.3f})')
else:  # Overdamped
    r1 = -omega_0 * (zeta + np.sqrt(zeta**2 - 1))
    r2 = -omega_0 * (zeta - np.sqrt(zeta**2 - 1))
    A = V0 * r2 / (r2 - r1)
    B = -V0 * r1 / (r2 - r1)
    v_out = A * np.exp(r1*t) + B * np.exp(r2*t)
    ax.set_title(f'Overdamped RLC Response (ζ = {zeta:.3f})')

ax.plot(t*1000, v_out, 'b-', linewidth=2)
ax.axhline(y=0, color='k', linewidth=0.5)
ax.set_xlabel('Time (ms)')
ax.set_ylabel('Voltage (V)')
ax.grid(True, alpha=0.3)

plt.show()

6. Labeling Figures

Proper labeling makes plots professional and informative:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import matplotlib.patches as mpatches

# Create a comprehensive labeled figure
fig = plt.figure(figsize=(14, 10))

# Create a grid of subplots
gs = fig.add_gridspec(3, 2, height_ratios=[2, 2, 1], hspace=0.3, wspace=0.3)

# Subplot 1: Multiple curves with legend
ax1 = fig.add_subplot(gs[0, 0])
x = np.linspace(0, 2*np.pi, 100)

# Plot multiple functions
ax1.plot(x, np.sin(x), 'b-', linewidth=2, label='sin(x)')
ax1.plot(x, np.cos(x), 'r--', linewidth=2, label='cos(x)')
ax1.plot(x, 0.5*np.sin(2*x), 'g-.', linewidth=2, label='0.5·sin(2x)')

# Comprehensive labeling
ax1.set_title('Trigonometric Functions', fontsize=14, fontweight='bold', pad=10)
ax1.set_xlabel('Angle (radians)', fontsize=12)
ax1.set_ylabel('Amplitude', fontsize=12)

# Custom legend
ax1.legend(loc='upper right', frameon=True, fancybox=True, shadow=True)

# Grid
ax1.grid(True, linestyle=':', alpha=0.6)

# Custom tick labels
ax1.set_xticks([0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi])
ax1.set_xticklabels(['0', 'π/2', 'π', '3π/2', '2π'])

# Add text annotations
ax1.text(np.pi/2, 1, 'Peak', ha='center', va='bottom', 
         bbox=dict(boxstyle='round,pad=0.3', facecolor='yellow', alpha=0.7))

# Subplot 2: Bar chart with value labels
ax2 = fig.add_subplot(gs[0, 1])
categories = ['A', 'B', 'C', 'D', 'E']
values = [23, 45, 56, 78, 32]
errors = [5, 7, 3, 9, 4]

bars = ax2.bar(categories, values, yerr=errors, capsize=5, 
                color=['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8'],
                edgecolor='black', linewidth=1.5)

# Add value labels on bars
for bar, value, error in zip(bars, values, errors):
    height = bar.get_height()
    ax2.text(bar.get_x() + bar.get_width()/2., height + error + 1,
             f'{value}±{error}', ha='center', va='bottom', fontsize=10)

ax2.set_title('Measurements with Error Bars', fontsize=14, fontweight='bold')
ax2.set_xlabel('Categories', fontsize=12)
ax2.set_ylabel('Values', fontsize=12)
ax2.set_ylim(0, 100)

# Add horizontal reference line
ax2.axhline(y=50, color='red', linestyle='--', alpha=0.7, label='Threshold')
ax2.legend()

# Subplot 3: Scatter plot with colorbar
ax3 = fig.add_subplot(gs[1, :])

# Generate data
np.random.seed(42)
x_scatter = np.random.randn(200)
y_scatter = 2*x_scatter + np.random.randn(200)
colors = x_scatter + y_scatter

scatter = ax3.scatter(x_scatter, y_scatter, c=colors, cmap='viridis', 
                      s=50, alpha=0.6, edgecolors='black', linewidth=0.5)

# Labels and title
ax3.set_title('Scatter Plot with Color Mapping', fontsize=14, fontweight='bold')
ax3.set_xlabel('X Variable', fontsize=12)
ax3.set_ylabel('Y Variable', fontsize=12)

# Colorbar with label
cbar = plt.colorbar(scatter, ax=ax3)
cbar.set_label('X + Y Value', fontsize=11)

# Add regression line
z = np.polyfit(x_scatter, y_scatter, 1)
p = np.poly1d(z)
x_line = np.linspace(-3, 3, 100)
ax3.plot(x_line, p(x_line), 'r--', linewidth=2, 
         label=f'y = {z[0]:.2f}x + {z[1]:.2f}')
ax3.legend(loc='upper left')

# Subplot 4: Text and annotations example
ax4 = fig.add_subplot(gs[2, :])
ax4.set_xlim(0, 10)
ax4.set_ylim(0, 5)
ax4.axis('off')

# Title
ax4.text(5, 4.5, 'Text Styling and Positioning Examples', 
         fontsize=16, fontweight='bold', ha='center')

# Different text styles
styles = [
    {'text': 'Normal text', 'props': {}},
    {'text': 'Bold text', 'props': {'fontweight': 'bold'}},
    {'text': 'Italic text', 'props': {'fontstyle': 'italic'}},
    {'text': 'Colored text', 'props': {'color': 'red'}},
    {'text': 'Large text', 'props': {'fontsize': 14}},
    {'text': 'Boxed text', 'props': {'bbox': dict(boxstyle='round', 
                                                   facecolor='lightblue')}},
]

y_pos = 3.5
for i, style in enumerate(styles):
    x_pos = 1.5 + (i % 3) * 3
    if i == 3:
        y_pos = 2
    ax4.text(x_pos, y_pos, style['text'], **style['props'], ha='center')

# Mathematical expressions
ax4.text(5, 1, r'Mathematical: $\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}$', 
         fontsize=12, ha='center',
         bbox=dict(boxstyle='round', facecolor='lightyellow'))

# Add figure label
fig.text(0.02, 0.98, '(a)', fontsize=16, fontweight='bold', 
         transform=fig.transFigure, va='top')

# Overall figure title
fig.suptitle('Comprehensive Figure Labeling Examples', 
             fontsize=18, fontweight='bold', y=0.98)

plt.tight_layout()
plt.show()

# Interactive labeling example
print("\nClick on the plot below to add labels at clicked positions!")

fig, ax = plt.subplots(figsize=(10, 8))
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.set_title('Interactive Labeling - Click to Add Labels', fontsize=14)
ax.grid(True, alpha=0.3)

label_count = 0

def on_click(event):
    global label_count
    if event.inaxes == ax:
        label_count += 1
        ax.plot(event.xdata, event.ydata, 'ro', markersize=8)
        ax.text(event.xdata, event.ydata, f'Point {label_count}\n({event.xdata:.1f}, {event.ydata:.1f})', 
                ha='left', va='bottom', fontsize=10,
                bbox=dict(boxstyle='round,pad=0.3', facecolor='yellow', alpha=0.7))
        fig.canvas.draw()

fig.canvas.mpl_connect('button_press_event', on_click)
plt.show()

7. Polar Plot

Polar plots are perfect for displaying data with angular components:

import numpy as np
import matplotlib.pyplot as plt

# Create figure with polar subplots
fig = plt.figure(figsize=(15, 10))

# Subplot 1: Basic polar plot
ax1 = plt.subplot(2, 3, 1, projection='polar')
theta = np.linspace(0, 2*np.pi, 100)
r = 1 + np.cos(theta)
ax1.plot(theta, r, 'b-', linewidth=2)
ax1.set_title('Cardioid: r = 1 + cos(θ)', pad=20)
ax1.grid(True)

# Subplot 2: Rose curve
ax2 = plt.subplot(2, 3, 2, projection='polar')
theta = np.linspace(0, 2*np.pi, 1000)
n = 5  # Number of petals
r = np.cos(n*theta)
ax2.plot(theta, r, 'r-', linewidth=2)
ax2.fill(theta, r, alpha=0.3, color='red')
ax2.set_title(f'Rose Curve: r = cos({n}θ)', pad=20)

# Subplot 3: Spiral
ax3 = plt.subplot(2, 3, 3, projection='polar')
theta = np.linspace(0, 4*np.pi, 1000)
r = theta/10
ax3.plot(theta, r, 'g-', linewidth=2)
ax3.set_title('Archimedean Spiral: r = θ/10', pad=20)

# Subplot 4: Butterfly curve
ax4 = plt.subplot(2, 3, 4, projection='polar')
theta = np.linspace(0, 12*np.pi, 10000)
r = np.exp(np.sin(theta)) - 2*np.cos(4*theta) + np.sin((2*theta - np.pi)/24)**5
ax4.plot(theta, r, 'purple', linewidth=1, alpha=0.8)
ax4.set_title('Butterfly Curve', pad=20)
ax4.set_ylim(-2, 4)

# Subplot 5: Polar bar chart
ax5 = plt.subplot(2, 3, 5, projection='polar')
# Data for wind direction
directions = np.array([0, 45, 90, 135, 180, 225, 270, 315]) * np.pi/180
wind_speed = [5, 10, 8, 12, 3, 6, 9, 7]
width = np.pi/8

bars = ax5.bar(directions, wind_speed, width=width, bottom=0.0)
# Color bars by speed
for bar, speed in zip(bars, wind_speed):
    bar.set_facecolor(plt.cm.viridis(speed/max(wind_speed)))
ax5.set_title('Wind Speed by Direction', pad=20)
ax5.set_theta_zero_location('N')
ax5.set_theta_direction(-1)

# Subplot 6: Interactive parametric curve
ax6 = plt.subplot(2, 3, 6, projection='polar')

# Lissajous-like curve in polar
t = np.linspace(0, 2*np.pi, 1000)
a, b = 3, 4
delta = np.pi/2
x = np.sin(a*t + delta)
y = np.sin(b*t)
r = np.sqrt(x**2 + y**2)
theta = np.arctan2(y, x)

ax6.plot(theta, r, 'orange', linewidth=2)
ax6.set_title('Lissajous in Polar', pad=20)
ax6.grid(True)

plt.tight_layout()
plt.show()

# Advanced example: Animated polar plot
print("\nCreating animated polar plot...")

fig, ax = plt.subplots(subplot_kw=dict(projection='polar'), figsize=(8, 8))
ax.set_title('Animated Rose Curve\n(Different petal patterns)', pad=20)

# Function to create rose curves with different parameters
def rose_curve(n):
    theta = np.linspace(0, 2*np.pi, 1000)
    if n % 2 == 0:
        # Even n: 2n petals
        r = np.cos(n*theta/2)
    else:
        # Odd n: n petals
        r = np.cos(n*theta)
    return theta, r

# Create multiple rose curves
for n in range(2, 8):
    theta, r = rose_curve(n)
    ax.plot(theta, r, linewidth=2, alpha=0.7, label=f'n={n}')

ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.1))
ax.grid(True)

plt.show()

# Polar scatter plot with color mapping
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'), figsize=(10, 10))

# Generate random data
np.random.seed(42)
n_points = 200
theta_scatter = np.random.uniform(0, 2*np.pi, n_points)
r_scatter = np.random.uniform(0, 5, n_points)
colors = theta_scatter

scatter = ax.scatter(theta_scatter, r_scatter, c=colors, cmap='hsv', 
                     s=100, alpha=0.8, edgecolors='black', linewidth=0.5)

ax.set_title('Polar Scatter Plot with Angular Color Mapping', pad=20)
cbar = plt.colorbar(scatter, ax=ax, pad=0.1)
cbar.set_label('Angle (radians)', fontsize=11)

plt.show()

8. Some Famous Curves

Explore mathematical curves that have fascinated mathematicians for centuries:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle

# Create a figure with famous curves
fig = plt.figure(figsize=(16, 12))

# 1. Lemniscate of Bernoulli
ax1 = plt.subplot(3, 3, 1)
t = np.linspace(0, 2*np.pi, 1000)
a = 2
x = a * np.sqrt(2) * np.cos(t) / (np.sin(t)**2 + 1)
y = a * np.sqrt(2) * np.cos(t) * np.sin(t) / (np.sin(t)**2 + 1)
ax1.plot(x, y, 'b-', linewidth=2)
ax1.set_title('Lemniscate of Bernoulli\n(x² + y²)² = a²(x² - y²)')
ax1.grid(True, alpha=0.3)
ax1.set_aspect('equal')
ax1.set_xlim(-3, 3)
ax1.set_ylim(-2, 2)

# 2. Cycloid
ax2 = plt.subplot(3, 3, 2)
t = np.linspace(0, 4*np.pi, 1000)
r = 1
x = r * (t - np.sin(t))
y = r * (1 - np.cos(t))
ax2.plot(x, y, 'r-', linewidth=2)
ax2.set_title('Cycloid\nx = r(t - sin(t)), y = r(1 - cos(t))')
ax2.grid(True, alpha=0.3)
ax2.set_aspect('equal')
ax2.set_ylim(-0.5, 2.5)

# 3. Astroid
ax3 = plt.subplot(3, 3, 3)
t = np.linspace(0, 2*np.pi, 1000)
a = 3
x = a * np.cos(t)**3
y = a * np.sin(t)**3
ax3.plot(x, y, 'g-', linewidth=2)
ax3.fill(x, y, alpha=0.3, color='green')
ax3.set_title('Astroid\nx = a·cos³(t), y = a·sin³(t)')
ax3.grid(True, alpha=0.3)
ax3.set_aspect('equal')

# 4. Cardioid (Heart shape)
ax4 = plt.subplot(3, 3, 4)
t = np.linspace(0, 2*np.pi, 1000)
a = 1
x = a * (2*np.cos(t) - np.cos(2*t))
y = a * (2*np.sin(t) - np.sin(2*t))
ax4.plot(x, y, 'red', linewidth=3)
ax4.fill(x, y, alpha=0.3, color='pink')
ax4.set_title('Cardioid (Heart Curve)\nx = a(2cos(t) - cos(2t))')
ax4.grid(True, alpha=0.3)
ax4.set_aspect('equal')

# 5. Lissajous Curves
ax5 = plt.subplot(3, 3, 5)
t = np.linspace(0, 2*np.pi, 1000)
# Multiple Lissajous curves
for a, b, delta in [(3, 2, 0), (3, 4, np.pi/2), (5, 4, np.pi/4)]:
    x = np.sin(a*t + delta)
    y = np.sin(b*t)
    ax5.plot(x, y, linewidth=2, alpha=0.7, 
             label=f'a={a}, b={b}, δ={delta:.2f}')
ax5.set_title('Lissajous Curves\nx = sin(at + δ), y = sin(bt)')
ax5.grid(True, alpha=0.3)
ax5.set_aspect('equal')
ax5.legend(fontsize=8)

# 6. Logarithmic Spiral
ax6 = plt.subplot(3, 3, 6, projection='polar')
theta = np.linspace(0, 6*np.pi, 1000)
a = 0.1
b = 0.2
r = a * np.exp(b * theta)
ax6.plot(theta, r, 'purple', linewidth=2)
ax6.set_title('Logarithmic Spiral\nr = ae^(bθ)', pad=20)
ax6.grid(True)

# 7. Folium of Descartes
ax7 = plt.subplot(3, 3, 7)
t = np.linspace(-10, 10, 1000)
t = t[np.abs(t**3 + 1) > 0.01]  # Avoid singularity
a = 3
x = 3*a*t / (1 + t**3)
y = 3*a*t**2 / (1 + t**3)
ax7.plot(x, y, 'm-', linewidth=2)
ax7.set_title('Folium of Descartes\nx³ + y³ = 3axy')
ax7.grid(True, alpha=0.3)
ax7.set_xlim(-5, 5)
ax7.set_ylim(-5, 5)
ax7.set_aspect('equal')

# 8. Witch of Agnesi
ax8 = plt.subplot(3, 3, 8)
t = np.linspace(-3, 3, 1000)
a = 2
x = t
y = 8*a**3 / (x**2 + 4*a**2)
ax8.plot(x, y, 'brown', linewidth=2)
ax8.fill_between(x, 0, y, alpha=0.3, color='brown')
ax8.set_title('Witch of Agnesi\ny = 8a³/(x² + 4a²)')
ax8.grid(True, alpha=0.3)
ax8.set_xlim(-4, 4)
ax8.set_ylim(0, 3)

# 9. Catenary (Shape of hanging chain)
ax9 = plt.subplot(3, 3, 9)
x = np.linspace(-3, 3, 1000)
a = 1
y = a * np.cosh(x/a)
ax9.plot(x, y, 'navy', linewidth=3)
# Add comparison with parabola
y_parabola = 1 + x**2/(2*a)
ax9.plot(x, y_parabola, 'gray', linewidth=2, linestyle='--', 
         label='Parabola (comparison)')
ax9.set_title('Catenary\ny = a·cosh(x/a)')
ax9.grid(True, alpha=0.3)
ax9.set_ylim(0, 5)
ax9.legend()

plt.tight_layout()
plt.show()

# Interactive curve explorer
print("\nInteractive Curve Explorer:")
print("Adjust parameters to see how curves change!")

fig, ax = plt.subplots(figsize=(10, 8))

# Example: Interactive epicycloid
def plot_epicycloid(R=3, r=1):
    ax.clear()
    t = np.linspace(0, 2*np.pi*r, 1000)
    x = (R + r) * np.cos(t) - r * np.cos((R + r) * t / r)
    y = (R + r) * np.sin(t) - r * np.sin((R + r) * t / r)
    
    ax.plot(x, y, 'b-', linewidth=2)
    ax.set_title(f'Epicycloid: R={R}, r={r}\n' + 
                 f'(Rolling circle of radius {r} around circle of radius {R})')
    ax.grid(True, alpha=0.3)
    ax.set_aspect('equal')
    
    # Draw the base circle
    circle = Circle((0, 0), R, fill=False, color='gray', linestyle='--')
    ax.add_patch(circle)
    
    # Set limits
    lim = R + 2*r + 1
    ax.set_xlim(-lim, lim)
    ax.set_ylim(-lim, lim)

# Initial plot
plot_epicycloid()

plt.show()
Which curve is formed by a point on a circle rolling along a straight line?
  • Cardioid
  • Cycloid
  • Astroid
  • Lemniscate

9. References