Skip to content

stormsidali2001/plotlive

Repository files navigation

plotlive

PyPI Python License: MIT CI

matplotlib pyplot API with a live interactive window. Pan, zoom, hover over points for tooltips, and step through animations frame by frame.

Works with existing matplotlib tutorial code. No API to learn, no imports to change.

Softmax classifier training

Install

pip install plotlive

For animation export:

pip install plotlive[gif]     # GIF export  (Pillow)
pip install plotlive[video]   # MP4 export  (imageio + ffmpeg)
pip install plotlive[export]  # both

Quick start

import plotlive.pyplot as plt
import numpy as np

x = np.arange(50)
plt.plot(x, np.exp(-x/10), label='train loss')
plt.plot(x, np.exp(-x/12) + 0.05*np.random.randn(50), label='val loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Curve')
plt.legend()
plt.grid()
plt.show()

Jupyter support

plt.show() detects the Jupyter kernel and switches to inline display. No config, no different import. Static plots come out as PNG; animations export as GIF (requires plotlive[gif]) or MP4 if Pillow isn't installed.

import plotlive.pyplot as plt
import numpy as np

# Static — displays inline
plt.plot(np.arange(50), np.exp(-np.arange(50)/10), label='loss')
plt.legend(); plt.grid()
plt.show()
# Animation — exports GIF and displays inline
def update(frame):
    plt.cla()
    plt.plot(np.arange(frame), np.random.randn(frame).cumsum())
    plt.title(f'Step {frame}')

plt.animate(update, frames=30, interval=100)
plt.show()

Controls

Mouse

Action Result
Scroll up Zoom in (centered on cursor)
Scroll down Zoom out (centered on cursor)
Click and drag Pan the view
Double-click Reset zoom and pan

Keyboard

Key Action
? or H Show / hide the help panel
Space Play / pause animation
Right arrow Step forward one frame (while paused)
Left arrow Step back one frame (while paused)
R Reset zoom / pan + restart animation from frame 0 (paused)
S Save current frame as frame_NNNN.png
Esc Close the help panel

Animations start paused. Press Space to play, / to step one frame at a time.

Zoom and pan apply to whichever subplot the cursor is over. Each subplot is independent.


Supported plot types

Function Common use
plt.plot(x, y) Training curves, time series
plt.scatter(x, y, c=labels) Clusters, feature relationships
plt.hist(data, bins=20) Feature distributions
plt.bar(x, height) / plt.barh(y, width) Feature importance, class counts
plt.imshow(matrix, cmap='Blues') Confusion matrix, correlation heatmap
plt.boxplot(data) Distribution summary with outliers
plt.violinplot(data) Full distribution shape per group
plt.fill_between(x, y1, y2) Confidence bands
plt.errorbar(x, y, yerr=std) Mean with error bars
plt.stackplot(x, y1, y2, y3) Cumulative contributions
plt.pie(values, labels=...) Class proportions

API reference

# ── Figure / layout ──────────────────────────────────────────────────
fig = plt.figure(figsize=(10, 6))
fig, ax = plt.subplots()
fig, axs = plt.subplots(2, 3, figsize=(14, 8))   # returns 2-D array of Axes
fig.suptitle('Overall title')
plt.tight_layout()
plt.savefig('output.png')
plt.save_animation('output.gif')   # requires: pip install Pillow
plt.save_animation('output.mp4')   # requires: pip install imageio[ffmpeg]
plt.show()

# ── Plots ────────────────────────────────────────────────────────────
plt.plot(x, y, 'b--', label='data', linewidth=2)
plt.scatter(x, y, c=colors, cmap='viridis', s=50, alpha=0.7)
plt.hist(data, bins=30, color='steelblue', edgecolor='white')
plt.bar(categories, values, color='steelblue')
plt.barh(categories, values)
plt.imshow(matrix, cmap='coolwarm', vmin=-1, vmax=1)
plt.colorbar()

plt.boxplot([group_a, group_b, group_c])
plt.violinplot([group_a, group_b], positions=[1, 2], widths=0.6)
plt.fill_between(x, y_low, y_high, alpha=0.3, color='steelblue')
plt.errorbar(x, y, yerr=std, fmt='o', capsize=4)
plt.stackplot(x, y1, y2, y3, labels=['A', 'B', 'C'], alpha=0.8)
plt.pie(values, labels=['Cat A', 'Cat B', 'Cat C'], startangle=90)

# ── Axes decoration ──────────────────────────────────────────────────
plt.xlabel('x label')
plt.ylabel('y label')
plt.title('Axes title')
plt.legend()
plt.grid()
plt.xlim(0, 10)
plt.ylim(-1, 1)
plt.xscale('log')
plt.yscale('log')
plt.xticks([0, 1, 2], ['zero', 'one', 'two'])
plt.yticks([0, 0.5, 1])
plt.axhline(y=0, color='k', linewidth=0.8)
plt.axvline(x=0, color='k', linewidth=0.8)

# ── OOP API ─────────────────────────────────────────────────────────
fig, ax = plt.subplots(2, 2, figsize=(12, 8))
ax[0, 0].plot(x, y)
ax[0, 1].scatter(x, y, c=labels, cmap='tab10')
ax[1, 0].boxplot([a, b, c])
ax[1, 1].violinplot(data)
ax[0, 0].set_xlabel('x'); ax[0, 0].set_ylabel('y')
ax[0, 0].set_title('subplot title')
ax[0, 0].legend(); ax[0, 0].grid()

# ── Animation ────────────────────────────────────────────────────────
def update(frame):
    plt.cla()
    plt.plot(x[:frame], y[:frame])
    plt.title(f'Frame {frame}')

plt.animate(update, frames=100, interval=50, repeat=True)
plt.show()

# ── Animation export ─────────────────────────────────────────────────
anim = plt.animate(update, frames=100, interval=50)
plt.save_animation('output.gif')        # requires: pip install Pillow
plt.save_animation('output.mp4')        # requires: pip install imageio[ffmpeg]
plt.save_animation('output.gif', fps=24)  # override frame rate
anim.save('output.gif')                 # or call directly on the object
plt.show()                              # interactive window opens afterwards

Animation

FuncAnimation — matplotlib-compatible

Matches matplotlib.animation.FuncAnimation. Existing animation code works as-is:

from plotlive.animation import FuncAnimation

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
x = np.linspace(-3, 3, 200)

def update(frame):
    ax.cla()
    ax.plot(x, np.sin(x + frame * 0.1))
    ax.set_title(f'Frame {frame}')

anim = FuncAnimation(fig, update, frames=60, interval=50, repeat=True)
plt.show()

All constructor parameters are supported:

Parameter Default Description
fig Figure to animate
func Called as func(frame, *fargs) each step
frames None int, list, generator, or None (→ 100 frames)
init_func None Accepted, not used (no blit)
fargs None Extra positional args forwarded to func
save_count None Frame count when frames is None
interval 200 Milliseconds between frames
repeat True Loop when finished
blit False Accepted, not used (full redraw always)

frames as a list passes values directly to func, not the index:

# func receives 0.0, 0.5, 1.0, 1.5, … not the list index
anim = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 60))

plt.animate() — convenience shorthand

plt.animate(update, frames=60, interval=50, repeat=True)
plt.show()

Animation export

Export any animation to a file without opening a window. Useful for embedding in slides or sharing with people who don't have plotlive installed.

Install

pip install plotlive[gif]           # GIF support  (Pillow)
pip install plotlive[video]         # MP4/MOV/AVI  (imageio + ffmpeg)
pip install plotlive[export]        # both

Or install the optional dependency directly:

pip install Pillow                      # for GIF
pip install imageio[ffmpeg]             # for MP4 / MOV / AVI

Usage

import plotlive.pyplot as plt
import numpy as np

x = np.linspace(-3, 3, 200)
w = [2.5]

def update(frame):
    w[0] -= 0.15 * 2 * w[0]
    plt.cla()
    plt.plot(x, x**2, 'b-', linewidth=2, label='f(w) = w²')
    plt.scatter([w[0]], [w[0]**2], c='red', s=120, label=f'w = {w[0]:.3f}')
    plt.ylim(-0.2, 7)
    plt.legend()
    plt.title(f'Gradient Descent — step {frame + 1}')

plt.animate(update, frames=25, interval=200)
plt.save_animation('gradient_descent.gif')  # export first
plt.show()                                   # then open interactive window

save_animation renders all frames off-screen. After saving, the figure resets to frame 0 so a subsequent show() starts from the beginning.

Supported formats: .gif · .mp4 · .mov · .avi · .webm

API

Call Description
plt.save_animation(filename) Export current figure's animation
plt.save_animation(filename, fps=24) Override frame rate
anim.save(filename) Call on any FuncAnimation object
anim.save(filename, writer='pillow', fps=12) Explicit writer (matplotlib-compatible)
anim.save(filename, writer='ffmpeg', fps=30) ffmpeg writer
anim.save(filename, progress_callback=fn) fn(current, total) called each frame

Default fps is derived from interval: fps = 1000 / interval. Accepted writer values: 'pillow' (GIF), 'ffmpeg' / 'imageio' (video), or None (inferred from extension).

Quick test

Run this one-liner — no window opens, it just renders and saves:

python3 -c "
import sys; sys.path.insert(0, 'src')
import plotlive.pyplot as plt, numpy as np
x = np.linspace(-3, 3, 200); w = [2.5]
def update(frame):
    w[0] -= 0.15 * 2 * w[0]; plt.cla()
    plt.plot(x, x**2, 'b-', linewidth=2, label='f(w)=w²')
    plt.scatter([w[0]], [w[0]**2], c='red', s=120, label=f'w={w[0]:.3f}')
    plt.ylim(-0.2, 7); plt.legend(); plt.title(f'Gradient Descent — step {frame+1}')
plt.animate(update, frames=20, interval=200)
plt.save_animation('gradient_descent.gif')
"

Frame progress prints to the terminal and gradient_descent.gif appears in the current directory.


Examples

Run from the examples/ directory after activating the venv:

cd examples
source ../.venv/bin/activate

Static plots

Training curves

python3 -c "
import plotlive.pyplot as plt, numpy as np
x = np.arange(50)
plt.plot(x, np.exp(-x/10), label='train loss')
plt.plot(x, np.exp(-x/12) + 0.05*np.random.randn(50), label='val loss')
plt.fill_between(x, np.exp(-x/10)-0.05, np.exp(-x/10)+0.05, alpha=0.2, label='± 1σ')
plt.xlabel('Epoch'); plt.ylabel('Loss'); plt.title('Training Curve')
plt.legend(); plt.grid(); plt.show()
"

Confusion matrix

python3 -c "
import plotlive.pyplot as plt, numpy as np
cm = np.array([[50,2,1],[3,45,5],[2,4,48]])
fig, ax = plt.subplots()
im = ax.imshow(cm, cmap='Blues')
plt.colorbar(im, ax=ax); ax.set_title('Confusion Matrix'); plt.show()
"

Feature importance + error bars

python3 -c "
import plotlive.pyplot as plt, numpy as np
feats = ['age','income','tenure','score','region']
vals  = [0.40, 0.30, 0.18, 0.08, 0.04]
errs  = [0.04, 0.03, 0.02, 0.01, 0.005]
plt.barh(feats, vals)
plt.errorbar(vals, range(len(feats)), xerr=errs, fmt='none', color='black', capsize=4)
plt.xlabel('Importance'); plt.title('Feature Importance ± std'); plt.show()
"

Distribution comparison — box + violin

python3 -c "
import plotlive.pyplot as plt, numpy as np
np.random.seed(0)
data = [np.random.normal(m, s, 120) for m, s in [(0,1),(1,1.5),(3,0.5),(-1,2)]]
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 5))
ax1.boxplot(data); ax1.set_title('Box Plot')
ax2.violinplot(data); ax2.set_title('Violin Plot')
plt.show()
"

Correlation heatmap

python3 -c "
import plotlive.pyplot as plt, numpy as np
np.random.seed(0)
corr = np.corrcoef(np.random.randn(5, 100))
fig, ax = plt.subplots(figsize=(6,5))
im = ax.imshow(corr, cmap='coolwarm', vmin=-1, vmax=1)
plt.colorbar(im, ax=ax); ax.set_title('Correlation Matrix'); plt.show()
"

Stacked area: class proportions over time

python3 -c "
import plotlive.pyplot as plt, numpy as np
x = np.arange(20)
a = np.random.dirichlet([3,2,1], 20).T
plt.stackplot(x, a[0], a[1], a[2], labels=['Class A','Class B','Class C'], alpha=0.85)
plt.xlabel('Time step'); plt.ylabel('Proportion'); plt.title('Class Distribution Over Time')
plt.legend(); plt.show()
"

Pie chart — class balance

python3 -c "
import plotlive.pyplot as plt
plt.pie([52, 31, 17], labels=['Negative','Neutral','Positive'], startangle=90)
plt.title('Sentiment Distribution'); plt.legend(); plt.show()
"

Animated examples

Animations start paused. Press Space to play, / to step frame by frame, S to save a frame.

Gradient descent

python3 -c "
import plotlive.pyplot as plt, numpy as np
x = np.linspace(-3, 3, 200); w = [2.5]
def update(frame):
    plt.cla(); w[0] -= 0.15 * 2 * w[0]
    plt.plot(x, x**2, 'b-', linewidth=2, label='f(w)=w²')
    plt.scatter([w[0]], [w[0]**2], c='red', s=120, zorder=5, label=f'w={w[0]:.3f}')
    plt.fill_between(x, 0, x**2, alpha=0.07)
    plt.ylim(-0.2, 7); plt.legend(); plt.title(f'Gradient Descent — step {frame+1}')
plt.animate(update, frames=25, interval=200); plt.show()
"

K-means clustering

python3 -c "
import plotlive.pyplot as plt, numpy as np
np.random.seed(7); K=3
data = np.vstack([np.random.randn(60,2)*0.7+c for c in [(-2,-2),(2,-2),(0,2)]])
centroids = data[np.random.choice(len(data),K,replace=False)].copy()
def update(frame):
    global centroids
    labels = np.array([[np.linalg.norm(p-c) for c in centroids] for p in data]).argmin(1).astype(float)
    centroids = np.array([data[labels==k].mean(0) if (labels==k).any() else centroids[k] for k in range(K)])
    plt.cla(); plt.scatter(data[:,0],data[:,1],c=labels,cmap='viridis',s=30,alpha=0.7)
    plt.scatter(centroids[:,0],centroids[:,1],c='red',s=200,marker='*',zorder=5,label='Centroids')
    plt.legend(); plt.title(f'K-Means — iteration {frame+1}')
plt.animate(update, frames=12, interval=500); plt.show()
"

Multi-class classification boundaries

Softmax classifier trained with gradient descent. Three decision boundaries, one per class pair. The lines rotate into place as accuracy climbs.

python3 -c "
import plotlive.pyplot as plt, numpy as np

np.random.seed(0)
K = 3
centers = [(-2, -1), (2, -1), (0, 2.5)]
X = np.vstack([np.random.randn(50, 2) * 0.8 + c for c in centers])
y = np.repeat(np.arange(K), 50)

W = np.zeros((2, K))
b = np.zeros(K)

MARKERS   = ['+',       'o',       '^'      ]
COLORS    = ['#e74c3c', '#3498db', '#2ecc71']
BD_COLORS = ['#8e44ad', '#e67e22', '#2c3e50']
x_edge = np.array([-5.5, 5.5])

def softmax(z):
    e = np.exp(z - z.max(axis=1, keepdims=True))
    return e / e.sum(axis=1, keepdims=True)

def plot_boundary(i, j, color):
    dw = W[:, i] - W[:, j]
    db = b[i] - b[j]
    if abs(dw[1]) < 1e-9:
        return
    plt.plot(x_edge, -(dw[0] * x_edge + db) / dw[1],
             color=color, linewidth=2, label=f'Boundary {i} vs {j}')

def update(frame):
    global W, b
    for _ in range(5):
        p = softmax(X @ W + b)
        oh = np.eye(K)[y]
        W -= 0.1 * (X.T @ (p - oh)) / len(X)
        b -= 0.1 * (p - oh).mean(axis=0)
    plt.cla()
    for k in range(K):
        m = y == k
        plt.scatter(X[m, 0], X[m, 1], c=COLORS[k], marker=MARKERS[k],
                    s=80, label=f'Class {k}')
    for (i, j), col in zip([(0, 1), (0, 2), (1, 2)], BD_COLORS):
        plot_boundary(i, j, col)
    acc = (np.argmax(X @ W + b, axis=1) == y).mean()
    plt.xlim(-5, 5); plt.ylim(-4, 5); plt.legend()
    plt.title(f'Softmax classifier — epoch {frame * 5} | acc {acc:.0%}')

plt.animate(update, frames=80, interval=100)
plt.show()
"

Export to GIF:

python3 -c "
import plotlive.pyplot as plt, numpy as np
np.random.seed(0); K=3
X=np.vstack([np.random.randn(50,2)*0.8+c for c in [(-2,-1),(2,-1),(0,2.5)]])
y=np.repeat(np.arange(K),50); W=np.zeros((2,K)); b=np.zeros(K)
MARKERS=['+','o','^']; COLORS=['#e74c3c','#3498db','#2ecc71']
BD_COLORS=['#8e44ad','#e67e22','#2c3e50']; x_edge=np.array([-5.5,5.5])
def softmax(z):
    e=np.exp(z-z.max(axis=1,keepdims=True)); return e/e.sum(axis=1,keepdims=True)
def plot_boundary(i,j,color):
    dw=W[:,i]-W[:,j]; db=b[i]-b[j]
    if abs(dw[1])<1e-9: return
    plt.plot(x_edge,-(dw[0]*x_edge+db)/dw[1],color=color,linewidth=2,label=f'Boundary {i} vs {j}')
def update(frame):
    global W,b
    for _ in range(5):
        p=softmax(X@W+b); oh=np.eye(K)[y]
        W-=0.1*(X.T@(p-oh))/len(X); b-=0.1*(p-oh).mean(axis=0)
    plt.cla()
    for k in range(K):
        m=y==k; plt.scatter(X[m,0],X[m,1],c=COLORS[k],marker=MARKERS[k],s=80,label=f'Class {k}')
    for (i,j),col in zip([(0,1),(0,2),(1,2)],BD_COLORS): plot_boundary(i,j,col)
    acc=(np.argmax(X@W+b,axis=1)==y).mean()
    plt.xlim(-5,5); plt.ylim(-4,5); plt.legend()
    plt.title(f'Softmax classifier — epoch {frame*5} | acc {acc:.0%}')
plt.animate(update, frames=60, interval=100)
plt.save_animation('classification.gif')
"

Neural network — hidden unit boundaries (ReLU)

Trains a 1-hidden-layer ReLU network on the two-moon dataset. Each subplot is one hidden unit. The shaded region is where it fires, the black line is its decision boundary, and w= is its output weight. Eight straight cuts combine into the curve that separates the moons. Double-click any subplot to expand it.

python3 -c "
import plotlive.pyplot as plt, numpy as np

np.random.seed(0)
n_h = 8

# Two-moon dataset
theta = np.linspace(0, np.pi, 60)
X0 = np.c_[np.cos(theta),   np.sin(theta)     ] + np.random.randn(60,2)*0.15
X1 = np.c_[1-np.cos(theta), 0.5-np.sin(theta) ] + np.random.randn(60,2)*0.15
X  = np.vstack([X0, X1]); X = (X - X.mean(0)) / X.std(0)
y  = np.repeat([0, 1], 60)

# Network weights
W1 = np.random.randn(2, n_h) * np.sqrt(2/2)
b1 = np.zeros(n_h)
W2 = np.random.randn(n_h, 1) * np.sqrt(2/n_h)
b2 = np.zeros(1)

# Decision-boundary mesh
g = 28
gx, gy  = np.linspace(-3, 3, g), np.linspace(-3, 3, g)
xx, yy  = np.meshgrid(gx, gy)
grid    = np.c_[xx.ravel(), yy.ravel()]
gx_flat = xx.ravel(); gy_flat = yy.ravel()
x_edge  = np.array([-3.5, 3.5])
COLORS  = ['#e74c3c', '#3498db']; MARKERS = ['o', '^']

def relu(z):    return np.maximum(0, z)
def sigmoid(z): return 1 / (1 + np.exp(-np.clip(z, -50, 50)))

def forward(Xb):
    z1 = Xb @ W1 + b1
    return sigmoid(relu(z1) @ W2 + b2).ravel(), z1

fig, axs = plt.subplots(2, 4, figsize=(14, 7))

def update(frame):
    global W1, b1, W2, b2
    lr = 0.05
    for _ in range(10):
        p, z1 = forward(X); a1 = relu(z1); N = len(X)
        dz2 = (p - y).reshape(-1, 1) / N
        dW2 = a1.T @ dz2;  db2 = dz2.sum(0)
        dz1 = (dz2 @ W2.T) * (z1 > 0)
        W1 -= lr * X.T @ dz1;  b1 -= lr * dz1.sum(0)
        W2 -= lr * dW2;         b2 -= lr * db2
    p_tr, _ = forward(X)
    acc = ((p_tr > 0.5).astype(int) == y).mean()
    _, z1_g = forward(grid)
    for i, ax in enumerate(axs.flat):
        ax.cla()
        active = z1_g[:, i] > 0
        ax.scatter(gx_flat[~active], gy_flat[~active], c='#eeeeee', s=55)
        ax.scatter(gx_flat[ active], gy_flat[ active], c='#c6e2f5', s=55)
        w, b = W1[:, i], b1[i]
        if abs(w[1]) > 1e-9:
            ax.plot(x_edge, -(w[0]*x_edge + b)/w[1], 'k-', linewidth=1.5)
        for k in range(2):
            m = y == k
            ax.scatter(X[m,0], X[m,1], c=COLORS[k], marker=MARKERS[k],
                       s=45, edgecolors='k', linewidths=0.5)
        ax.set_xlim(-3, 3); ax.set_ylim(-3, 3)
        ax.set_title(f'Unit {i+1}  w={W2[i,0]:+.2f}')
    plt.suptitle(f'1-hidden-layer ReLU — epoch {frame*10} | acc {acc:.0%}'
                 '   [double-click any panel to focus]')

plt.animate(update, frames=100, interval=100)
plt.show()
"

Neural network training curves

python3 -c "
import plotlive.pyplot as plt, numpy as np
np.random.seed(1); losses, accs = [], []
def update(frame):
    t = frame/80
    losses.append(2.3*np.exp(-3*t)+0.08+0.03*np.random.randn())
    accs.append(min(0.99,1-np.exp(-4*t)*0.9+0.01*np.random.randn()))
    axs = plt.gcf().axes
    axs[0].cla(); axs[1].cla()
    axs[0].plot(losses,'b-',linewidth=2); axs[0].set_title('Loss'); axs[0].grid()
    axs[1].plot(accs,'g-',linewidth=2); axs[1].set_title('Accuracy'); axs[1].set_ylim(0,1); axs[1].grid()
plt.subplots(1,2,figsize=(10,4))
plt.animate(update, frames=80, interval=80); plt.show()
"

fill_between: confidence band widening under distribution shift

python3 -c "
import plotlive.pyplot as plt, numpy as np
np.random.seed(0)
x = np.linspace(0, 10, 80)
mean = np.sin(x) * np.exp(-x/8)
noise_levels = np.linspace(0.05, 0.6, 30)
def update(frame):
    sigma = noise_levels[frame]
    plt.cla()
    plt.plot(x, mean, 'steelblue', linewidth=2, label='prediction')
    plt.fill_between(x, mean - sigma, mean + sigma, alpha=0.35, color='steelblue', label=f'± {sigma:.2f}')
    plt.fill_between(x, mean - 2*sigma, mean + 2*sigma, alpha=0.15, color='steelblue', label='± 2σ')
    plt.ylim(-1.8, 1.8); plt.legend(); plt.grid()
    plt.title(f'Uncertainty grows under distribution shift — σ={sigma:.2f}')
plt.animate(update, frames=30, interval=150); plt.show()
"

errorbar: learning curve

python3 -c "
import plotlive.pyplot as plt, numpy as np
np.random.seed(0)
sizes = np.array([10, 25, 50, 100, 200, 400, 800])
means = 1 - 0.88*np.exp(-sizes/120) + 0.015*np.random.randn(len(sizes))
stds  = 0.32*np.exp(-sizes/80) + 0.01
def update(frame):
    n = frame + 1
    plt.cla()
    plt.errorbar(sizes[:n], means[:n], yerr=stds[:n], fmt='o-', capsize=5,
                 color='steelblue', label='accuracy ± std')
    plt.xlim(-30, 850); plt.ylim(0, 1.1)
    plt.xlabel('Training set size'); plt.ylabel('Accuracy')
    plt.title('Learning Curve — more data, less variance'); plt.legend(); plt.grid()
plt.animate(update, frames=len(sizes), interval=600); plt.show()
"

boxplot: prediction distribution per epoch

python3 -c "
import plotlive.pyplot as plt, numpy as np
np.random.seed(1)
epochs = [5, 10, 20, 40, 80, 160]
data = [np.random.normal(0.35 + 0.55*(i/len(epochs)), max(0.28 - i*0.04, 0.04), 80)
        for i in range(len(epochs))]
def update(frame):
    n = frame + 1
    plt.cla()
    plt.boxplot(data[:n], labels=[str(e) for e in epochs[:n]])
    plt.ylim(-0.1, 1.1)
    plt.xlabel('Epoch'); plt.ylabel('Predicted probability')
    plt.title(f'Prediction Distribution — epoch {epochs[frame]}')
plt.animate(update, frames=len(epochs), interval=700); plt.show()
"

violinplot: activation distribution per training step

python3 -c "
import plotlive.pyplot as plt, numpy as np
np.random.seed(2)
steps = 6
data = [np.random.normal(i*0.5, max(1.1 - i*0.16, 0.15), 120) for i in range(steps)]
def update(frame):
    n = frame + 1
    plt.cla()
    plt.violinplot(data[:n], positions=list(range(1, n+1)), widths=0.7)
    plt.xlim(0, steps+1); plt.ylim(-3.5, 5.5)
    plt.xlabel('Training step'); plt.ylabel('Activation value')
    plt.title(f'Activation Distribution — step {frame+1} of {steps}')
plt.animate(update, frames=steps, interval=700); plt.show()
"

pie: class balance as dataset is rebalanced

python3 -c "
import plotlive.pyplot as plt
labels = ['Negative', 'Neutral', 'Positive']
stages = [[70,20,10],[60,25,15],[50,30,20],[45,32,23],[40,35,25],[33,34,33]]
captions = ['raw','oversample pos','oversample more','near balance','balanced','uniform']
def update(frame):
    plt.cla()
    vals = stages[frame]
    plt.pie(vals, labels=labels, startangle=90)
    pcts = ' | '.join(f'{l}: {v}%' for l,v in zip(labels,vals))
    plt.title(f'Class Balance — {captions[frame]}\n{pcts}')
plt.animate(update, frames=len(stages), interval=900); plt.show()
"

stackplot: feature contributions per complexity level

python3 -c "
import plotlive.pyplot as plt, numpy as np
np.random.seed(3)
x = np.arange(20)
feats = ['linear','interactions','polynomials','residuals']
components = [np.abs(np.random.randn(20))*(i+1)*0.4 for i in range(len(feats))]
def update(frame):
    n = frame + 1
    plt.cla()
    plt.stackplot(x, *components[:n], labels=feats[:n], alpha=0.85)
    plt.xlim(0, 19); plt.ylim(0, sum(c.max() for c in components)*1.05)
    plt.xlabel('Sample'); plt.ylabel('Explained variance')
    plt.title(f'Model Complexity — adding {feats[frame]}')
    plt.legend()
plt.animate(update, frames=len(feats), interval=900); plt.show()
"

Sorting algorithm visualizations

Each opens its own window with 7 elements, a color legend, and step descriptions.

cd examples
python3 bubble_sort.py
python3 insertion_sort.py
python3 selection_sort.py
python3 heap_sort.py
python3 merge_sort.py
python3 quick_sort.py
Color Meaning
Blue Unsorted
Orange Being compared
Red Being swapped
Green Confirmed sorted

Use / to step frame by frame. The value of each element is shown below its bar.

Runtime benchmark — all 6 algorithms

python3 sort_benchmark.py

Benchmarks all 6 in the terminal, then opens an animated log-scale chart that adds one input size per frame. O(n²) and O(n log n) curves diverge visibly as N grows.


Run tests

pytest tests/

Dependencies

  • pygame-ce >= 2.4.0
  • numpy >= 1.24.0
  • Python 3.10+

About

matplotlib pyplot code that runs in an interactive pygame window with pan, zoom and frame by frame animation

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages