{"id":51693361,"url":"https://github.com/stormsidali2001/plotlive","last_synced_at":"2026-07-16T03:33:45.618Z","repository":{"id":366193594,"uuid":"1275375319","full_name":"stormsidali2001/plotlive","owner":"stormsidali2001","description":" matplotlib pyplot code that runs in an interactive pygame window with pan, zoom and frame by frame animation","archived":false,"fork":false,"pushed_at":"2026-06-20T16:49:48.000Z","size":3853,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-20T18:21:38.051Z","etag":null,"topics":["animation","data-science","data-visualization","interactive-visualization","machine-learning","matplotlib","plotting","pygame-ce","python","visualization"],"latest_commit_sha":null,"homepage":"https://stormsidali2001.github.io/plotlive/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stormsidali2001.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-20T15:55:49.000Z","updated_at":"2026-06-20T16:49:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/stormsidali2001/plotlive","commit_stats":null,"previous_names":["stormsidali2001/plotlive"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/stormsidali2001/plotlive","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stormsidali2001%2Fplotlive","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stormsidali2001%2Fplotlive/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stormsidali2001%2Fplotlive/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stormsidali2001%2Fplotlive/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stormsidali2001","download_url":"https://codeload.github.com/stormsidali2001/plotlive/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stormsidali2001%2Fplotlive/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35529725,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-16T02:00:06.687Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animation","data-science","data-visualization","interactive-visualization","machine-learning","matplotlib","plotting","pygame-ce","python","visualization"],"created_at":"2026-07-16T03:33:44.796Z","updated_at":"2026-07-16T03:33:45.607Z","avatar_url":"https://github.com/stormsidali2001.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# plotlive\n\n[![PyPI](https://img.shields.io/pypi/v/plotlive)](https://pypi.org/project/plotlive/)\n[![Python](https://img.shields.io/pypi/pyversions/plotlive)](https://pypi.org/project/plotlive/)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![CI](https://github.com/assoulsidali/plotlive/actions/workflows/ci.yml/badge.svg)](https://github.com/assoulsidali/plotlive/actions)\n\nmatplotlib pyplot API with a live interactive window. Pan, zoom, hover over points for tooltips, and step through animations frame by frame.\n\nWorks with existing matplotlib tutorial code. No API to learn, no imports to change.\n\n![Softmax classifier training](docs/assets/demo.gif)\n\n## Install\n\n```bash\npip install plotlive\n```\n\nFor animation export:\n\n```bash\npip install plotlive[gif]     # GIF export  (Pillow)\npip install plotlive[video]   # MP4 export  (imageio + ffmpeg)\npip install plotlive[export]  # both\n```\n\n## Quick start\n\n```python\nimport plotlive.pyplot as plt\nimport numpy as np\n\nx = np.arange(50)\nplt.plot(x, np.exp(-x/10), label='train loss')\nplt.plot(x, np.exp(-x/12) + 0.05*np.random.randn(50), label='val loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('Training Curve')\nplt.legend()\nplt.grid()\nplt.show()\n```\n\n---\n\n## Jupyter support\n\n`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.\n\n```python\nimport plotlive.pyplot as plt\nimport numpy as np\n\n# Static — displays inline\nplt.plot(np.arange(50), np.exp(-np.arange(50)/10), label='loss')\nplt.legend(); plt.grid()\nplt.show()\n```\n\n```python\n# Animation — exports GIF and displays inline\ndef update(frame):\n    plt.cla()\n    plt.plot(np.arange(frame), np.random.randn(frame).cumsum())\n    plt.title(f'Step {frame}')\n\nplt.animate(update, frames=30, interval=100)\nplt.show()\n```\n\n---\n\n## Controls\n\n### Mouse\n\n| Action | Result |\n|--------|--------|\n| Scroll up | Zoom in (centered on cursor) |\n| Scroll down | Zoom out (centered on cursor) |\n| Click and drag | Pan the view |\n| Double-click | Reset zoom and pan |\n\n### Keyboard\n\n| Key | Action |\n|-----|--------|\n| `?` or `H` | Show / hide the help panel |\n| `Space` | Play / pause animation |\n| `→` Right arrow | Step forward one frame (while paused) |\n| `←` Left arrow | Step back one frame (while paused) |\n| `R` | Reset zoom / pan + restart animation from frame 0 (paused) |\n| `S` | Save current frame as `frame_NNNN.png` |\n| `Esc` | Close the help panel |\n\nAnimations start paused. Press `Space` to play, `←` / `→` to step one frame at a time.\n\nZoom and pan apply to whichever subplot the cursor is over. Each subplot is independent.\n\n---\n\n## Supported plot types\n\n| Function | Common use |\n|----------|------------|\n| `plt.plot(x, y)` | Training curves, time series |\n| `plt.scatter(x, y, c=labels)` | Clusters, feature relationships |\n| `plt.hist(data, bins=20)` | Feature distributions |\n| `plt.bar(x, height)` / `plt.barh(y, width)` | Feature importance, class counts |\n| `plt.imshow(matrix, cmap='Blues')` | Confusion matrix, correlation heatmap |\n| `plt.boxplot(data)` | Distribution summary with outliers |\n| `plt.violinplot(data)` | Full distribution shape per group |\n| `plt.fill_between(x, y1, y2)` | Confidence bands |\n| `plt.errorbar(x, y, yerr=std)` | Mean with error bars |\n| `plt.stackplot(x, y1, y2, y3)` | Cumulative contributions |\n| `plt.pie(values, labels=...)` | Class proportions |\n\n---\n\n## API reference\n\n```python\n# ── Figure / layout ──────────────────────────────────────────────────\nfig = plt.figure(figsize=(10, 6))\nfig, ax = plt.subplots()\nfig, axs = plt.subplots(2, 3, figsize=(14, 8))   # returns 2-D array of Axes\nfig.suptitle('Overall title')\nplt.tight_layout()\nplt.savefig('output.png')\nplt.save_animation('output.gif')   # requires: pip install Pillow\nplt.save_animation('output.mp4')   # requires: pip install imageio[ffmpeg]\nplt.show()\n\n# ── Plots ────────────────────────────────────────────────────────────\nplt.plot(x, y, 'b--', label='data', linewidth=2)\nplt.scatter(x, y, c=colors, cmap='viridis', s=50, alpha=0.7)\nplt.hist(data, bins=30, color='steelblue', edgecolor='white')\nplt.bar(categories, values, color='steelblue')\nplt.barh(categories, values)\nplt.imshow(matrix, cmap='coolwarm', vmin=-1, vmax=1)\nplt.colorbar()\n\nplt.boxplot([group_a, group_b, group_c])\nplt.violinplot([group_a, group_b], positions=[1, 2], widths=0.6)\nplt.fill_between(x, y_low, y_high, alpha=0.3, color='steelblue')\nplt.errorbar(x, y, yerr=std, fmt='o', capsize=4)\nplt.stackplot(x, y1, y2, y3, labels=['A', 'B', 'C'], alpha=0.8)\nplt.pie(values, labels=['Cat A', 'Cat B', 'Cat C'], startangle=90)\n\n# ── Axes decoration ──────────────────────────────────────────────────\nplt.xlabel('x label')\nplt.ylabel('y label')\nplt.title('Axes title')\nplt.legend()\nplt.grid()\nplt.xlim(0, 10)\nplt.ylim(-1, 1)\nplt.xscale('log')\nplt.yscale('log')\nplt.xticks([0, 1, 2], ['zero', 'one', 'two'])\nplt.yticks([0, 0.5, 1])\nplt.axhline(y=0, color='k', linewidth=0.8)\nplt.axvline(x=0, color='k', linewidth=0.8)\n\n# ── OOP API ─────────────────────────────────────────────────────────\nfig, ax = plt.subplots(2, 2, figsize=(12, 8))\nax[0, 0].plot(x, y)\nax[0, 1].scatter(x, y, c=labels, cmap='tab10')\nax[1, 0].boxplot([a, b, c])\nax[1, 1].violinplot(data)\nax[0, 0].set_xlabel('x'); ax[0, 0].set_ylabel('y')\nax[0, 0].set_title('subplot title')\nax[0, 0].legend(); ax[0, 0].grid()\n\n# ── Animation ────────────────────────────────────────────────────────\ndef update(frame):\n    plt.cla()\n    plt.plot(x[:frame], y[:frame])\n    plt.title(f'Frame {frame}')\n\nplt.animate(update, frames=100, interval=50, repeat=True)\nplt.show()\n\n# ── Animation export ─────────────────────────────────────────────────\nanim = plt.animate(update, frames=100, interval=50)\nplt.save_animation('output.gif')        # requires: pip install Pillow\nplt.save_animation('output.mp4')        # requires: pip install imageio[ffmpeg]\nplt.save_animation('output.gif', fps=24)  # override frame rate\nanim.save('output.gif')                 # or call directly on the object\nplt.show()                              # interactive window opens afterwards\n```\n\n---\n\n## Animation\n\n### FuncAnimation — matplotlib-compatible\n\nMatches `matplotlib.animation.FuncAnimation`. Existing animation code works as-is:\n\n```python\nfrom plotlive.animation import FuncAnimation\n\nfig = plt.figure()\nax = fig.add_subplot(1, 1, 1)\nx = np.linspace(-3, 3, 200)\n\ndef update(frame):\n    ax.cla()\n    ax.plot(x, np.sin(x + frame * 0.1))\n    ax.set_title(f'Frame {frame}')\n\nanim = FuncAnimation(fig, update, frames=60, interval=50, repeat=True)\nplt.show()\n```\n\nAll constructor parameters are supported:\n\n| Parameter | Default | Description |\n|-----------|---------|-------------|\n| `fig` | — | Figure to animate |\n| `func` | — | Called as `func(frame, *fargs)` each step |\n| `frames` | `None` | int, list, generator, or None (→ 100 frames) |\n| `init_func` | `None` | Accepted, not used (no blit) |\n| `fargs` | `None` | Extra positional args forwarded to `func` |\n| `save_count` | `None` | Frame count when `frames` is None |\n| `interval` | `200` | Milliseconds between frames |\n| `repeat` | `True` | Loop when finished |\n| `blit` | `False` | Accepted, not used (full redraw always) |\n\n`frames` as a list passes values directly to `func`, not the index:\n\n```python\n# func receives 0.0, 0.5, 1.0, 1.5, … not the list index\nanim = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 60))\n```\n\n### plt.animate() — convenience shorthand\n\n```python\nplt.animate(update, frames=60, interval=50, repeat=True)\nplt.show()\n```\n\n---\n\n## Animation export\n\nExport any animation to a file without opening a window. Useful for embedding in slides or sharing with people who don't have plotlive installed.\n\n### Install\n\n```bash\npip install plotlive[gif]           # GIF support  (Pillow)\npip install plotlive[video]         # MP4/MOV/AVI  (imageio + ffmpeg)\npip install plotlive[export]        # both\n```\n\nOr install the optional dependency directly:\n\n```bash\npip install Pillow                      # for GIF\npip install imageio[ffmpeg]             # for MP4 / MOV / AVI\n```\n\n### Usage\n\n```python\nimport plotlive.pyplot as plt\nimport numpy as np\n\nx = np.linspace(-3, 3, 200)\nw = [2.5]\n\ndef update(frame):\n    w[0] -= 0.15 * 2 * w[0]\n    plt.cla()\n    plt.plot(x, x**2, 'b-', linewidth=2, label='f(w) = w²')\n    plt.scatter([w[0]], [w[0]**2], c='red', s=120, label=f'w = {w[0]:.3f}')\n    plt.ylim(-0.2, 7)\n    plt.legend()\n    plt.title(f'Gradient Descent — step {frame + 1}')\n\nplt.animate(update, frames=25, interval=200)\nplt.save_animation('gradient_descent.gif')  # export first\nplt.show()                                   # then open interactive window\n```\n\n`save_animation` renders all frames off-screen. After saving, the figure resets to frame 0 so a subsequent `show()` starts from the beginning.\n\nSupported formats: `.gif` · `.mp4` · `.mov` · `.avi` · `.webm`\n\n### API\n\n| Call | Description |\n|------|-------------|\n| `plt.save_animation(filename)` | Export current figure's animation |\n| `plt.save_animation(filename, fps=24)` | Override frame rate |\n| `anim.save(filename)` | Call on any `FuncAnimation` object |\n| `anim.save(filename, writer='pillow', fps=12)` | Explicit writer (matplotlib-compatible) |\n| `anim.save(filename, writer='ffmpeg', fps=30)` | ffmpeg writer |\n| `anim.save(filename, progress_callback=fn)` | `fn(current, total)` called each frame |\n\nDefault `fps` is derived from `interval`: `fps = 1000 / interval`.\nAccepted `writer` values: `'pillow'` (GIF), `'ffmpeg'` / `'imageio'` (video), or `None` (inferred from extension).\n\n### Quick test\n\nRun this one-liner — no window opens, it just renders and saves:\n\n```bash\npython3 -c \"\nimport sys; sys.path.insert(0, 'src')\nimport plotlive.pyplot as plt, numpy as np\nx = np.linspace(-3, 3, 200); w = [2.5]\ndef update(frame):\n    w[0] -= 0.15 * 2 * w[0]; plt.cla()\n    plt.plot(x, x**2, 'b-', linewidth=2, label='f(w)=w²')\n    plt.scatter([w[0]], [w[0]**2], c='red', s=120, label=f'w={w[0]:.3f}')\n    plt.ylim(-0.2, 7); plt.legend(); plt.title(f'Gradient Descent — step {frame+1}')\nplt.animate(update, frames=20, interval=200)\nplt.save_animation('gradient_descent.gif')\n\"\n```\n\nFrame progress prints to the terminal and `gradient_descent.gif` appears in the current directory.\n\n---\n\n## Examples\n\nRun from the `examples/` directory after activating the venv:\n\n```bash\ncd examples\nsource ../.venv/bin/activate\n```\n\n---\n\n### Static plots\n\n#### Training curves\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\nx = np.arange(50)\nplt.plot(x, np.exp(-x/10), label='train loss')\nplt.plot(x, np.exp(-x/12) + 0.05*np.random.randn(50), label='val loss')\nplt.fill_between(x, np.exp(-x/10)-0.05, np.exp(-x/10)+0.05, alpha=0.2, label='± 1σ')\nplt.xlabel('Epoch'); plt.ylabel('Loss'); plt.title('Training Curve')\nplt.legend(); plt.grid(); plt.show()\n\"\n```\n\n#### Confusion matrix\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\ncm = np.array([[50,2,1],[3,45,5],[2,4,48]])\nfig, ax = plt.subplots()\nim = ax.imshow(cm, cmap='Blues')\nplt.colorbar(im, ax=ax); ax.set_title('Confusion Matrix'); plt.show()\n\"\n```\n\n#### Feature importance + error bars\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\nfeats = ['age','income','tenure','score','region']\nvals  = [0.40, 0.30, 0.18, 0.08, 0.04]\nerrs  = [0.04, 0.03, 0.02, 0.01, 0.005]\nplt.barh(feats, vals)\nplt.errorbar(vals, range(len(feats)), xerr=errs, fmt='none', color='black', capsize=4)\nplt.xlabel('Importance'); plt.title('Feature Importance ± std'); plt.show()\n\"\n```\n\n#### Distribution comparison — box + violin\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\nnp.random.seed(0)\ndata = [np.random.normal(m, s, 120) for m, s in [(0,1),(1,1.5),(3,0.5),(-1,2)]]\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 5))\nax1.boxplot(data); ax1.set_title('Box Plot')\nax2.violinplot(data); ax2.set_title('Violin Plot')\nplt.show()\n\"\n```\n\n#### Correlation heatmap\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\nnp.random.seed(0)\ncorr = np.corrcoef(np.random.randn(5, 100))\nfig, ax = plt.subplots(figsize=(6,5))\nim = ax.imshow(corr, cmap='coolwarm', vmin=-1, vmax=1)\nplt.colorbar(im, ax=ax); ax.set_title('Correlation Matrix'); plt.show()\n\"\n```\n\n#### Stacked area: class proportions over time\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\nx = np.arange(20)\na = np.random.dirichlet([3,2,1], 20).T\nplt.stackplot(x, a[0], a[1], a[2], labels=['Class A','Class B','Class C'], alpha=0.85)\nplt.xlabel('Time step'); plt.ylabel('Proportion'); plt.title('Class Distribution Over Time')\nplt.legend(); plt.show()\n\"\n```\n\n#### Pie chart — class balance\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt\nplt.pie([52, 31, 17], labels=['Negative','Neutral','Positive'], startangle=90)\nplt.title('Sentiment Distribution'); plt.legend(); plt.show()\n\"\n```\n\n---\n\n### Animated examples\n\nAnimations start paused. Press `Space` to play, `←` / `→` to step frame by frame, `S` to save a frame.\n\n#### Gradient descent\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\nx = np.linspace(-3, 3, 200); w = [2.5]\ndef update(frame):\n    plt.cla(); w[0] -= 0.15 * 2 * w[0]\n    plt.plot(x, x**2, 'b-', linewidth=2, label='f(w)=w²')\n    plt.scatter([w[0]], [w[0]**2], c='red', s=120, zorder=5, label=f'w={w[0]:.3f}')\n    plt.fill_between(x, 0, x**2, alpha=0.07)\n    plt.ylim(-0.2, 7); plt.legend(); plt.title(f'Gradient Descent — step {frame+1}')\nplt.animate(update, frames=25, interval=200); plt.show()\n\"\n```\n\n#### K-means clustering\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\nnp.random.seed(7); K=3\ndata = np.vstack([np.random.randn(60,2)*0.7+c for c in [(-2,-2),(2,-2),(0,2)]])\ncentroids = data[np.random.choice(len(data),K,replace=False)].copy()\ndef update(frame):\n    global centroids\n    labels = np.array([[np.linalg.norm(p-c) for c in centroids] for p in data]).argmin(1).astype(float)\n    centroids = np.array([data[labels==k].mean(0) if (labels==k).any() else centroids[k] for k in range(K)])\n    plt.cla(); plt.scatter(data[:,0],data[:,1],c=labels,cmap='viridis',s=30,alpha=0.7)\n    plt.scatter(centroids[:,0],centroids[:,1],c='red',s=200,marker='*',zorder=5,label='Centroids')\n    plt.legend(); plt.title(f'K-Means — iteration {frame+1}')\nplt.animate(update, frames=12, interval=500); plt.show()\n\"\n```\n\n#### Multi-class classification boundaries\n\nSoftmax classifier trained with gradient descent. Three decision boundaries, one per class pair. The lines rotate into place as accuracy climbs.\n\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\n\nnp.random.seed(0)\nK = 3\ncenters = [(-2, -1), (2, -1), (0, 2.5)]\nX = np.vstack([np.random.randn(50, 2) * 0.8 + c for c in centers])\ny = np.repeat(np.arange(K), 50)\n\nW = np.zeros((2, K))\nb = np.zeros(K)\n\nMARKERS   = ['+',       'o',       '^'      ]\nCOLORS    = ['#e74c3c', '#3498db', '#2ecc71']\nBD_COLORS = ['#8e44ad', '#e67e22', '#2c3e50']\nx_edge = np.array([-5.5, 5.5])\n\ndef softmax(z):\n    e = np.exp(z - z.max(axis=1, keepdims=True))\n    return e / e.sum(axis=1, keepdims=True)\n\ndef plot_boundary(i, j, color):\n    dw = W[:, i] - W[:, j]\n    db = b[i] - b[j]\n    if abs(dw[1]) \u003c 1e-9:\n        return\n    plt.plot(x_edge, -(dw[0] * x_edge + db) / dw[1],\n             color=color, linewidth=2, label=f'Boundary {i} vs {j}')\n\ndef update(frame):\n    global W, b\n    for _ in range(5):\n        p = softmax(X @ W + b)\n        oh = np.eye(K)[y]\n        W -= 0.1 * (X.T @ (p - oh)) / len(X)\n        b -= 0.1 * (p - oh).mean(axis=0)\n    plt.cla()\n    for k in range(K):\n        m = y == k\n        plt.scatter(X[m, 0], X[m, 1], c=COLORS[k], marker=MARKERS[k],\n                    s=80, label=f'Class {k}')\n    for (i, j), col in zip([(0, 1), (0, 2), (1, 2)], BD_COLORS):\n        plot_boundary(i, j, col)\n    acc = (np.argmax(X @ W + b, axis=1) == y).mean()\n    plt.xlim(-5, 5); plt.ylim(-4, 5); plt.legend()\n    plt.title(f'Softmax classifier — epoch {frame * 5} | acc {acc:.0%}')\n\nplt.animate(update, frames=80, interval=100)\nplt.show()\n\"\n```\n\nExport to GIF:\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\nnp.random.seed(0); K=3\nX=np.vstack([np.random.randn(50,2)*0.8+c for c in [(-2,-1),(2,-1),(0,2.5)]])\ny=np.repeat(np.arange(K),50); W=np.zeros((2,K)); b=np.zeros(K)\nMARKERS=['+','o','^']; COLORS=['#e74c3c','#3498db','#2ecc71']\nBD_COLORS=['#8e44ad','#e67e22','#2c3e50']; x_edge=np.array([-5.5,5.5])\ndef softmax(z):\n    e=np.exp(z-z.max(axis=1,keepdims=True)); return e/e.sum(axis=1,keepdims=True)\ndef plot_boundary(i,j,color):\n    dw=W[:,i]-W[:,j]; db=b[i]-b[j]\n    if abs(dw[1])\u003c1e-9: return\n    plt.plot(x_edge,-(dw[0]*x_edge+db)/dw[1],color=color,linewidth=2,label=f'Boundary {i} vs {j}')\ndef update(frame):\n    global W,b\n    for _ in range(5):\n        p=softmax(X@W+b); oh=np.eye(K)[y]\n        W-=0.1*(X.T@(p-oh))/len(X); b-=0.1*(p-oh).mean(axis=0)\n    plt.cla()\n    for k in range(K):\n        m=y==k; plt.scatter(X[m,0],X[m,1],c=COLORS[k],marker=MARKERS[k],s=80,label=f'Class {k}')\n    for (i,j),col in zip([(0,1),(0,2),(1,2)],BD_COLORS): plot_boundary(i,j,col)\n    acc=(np.argmax(X@W+b,axis=1)==y).mean()\n    plt.xlim(-5,5); plt.ylim(-4,5); plt.legend()\n    plt.title(f'Softmax classifier — epoch {frame*5} | acc {acc:.0%}')\nplt.animate(update, frames=60, interval=100)\nplt.save_animation('classification.gif')\n\"\n```\n\n#### Neural network — hidden unit boundaries (ReLU)\n\nTrains 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.\n\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\n\nnp.random.seed(0)\nn_h = 8\n\n# Two-moon dataset\ntheta = np.linspace(0, np.pi, 60)\nX0 = np.c_[np.cos(theta),   np.sin(theta)     ] + np.random.randn(60,2)*0.15\nX1 = np.c_[1-np.cos(theta), 0.5-np.sin(theta) ] + np.random.randn(60,2)*0.15\nX  = np.vstack([X0, X1]); X = (X - X.mean(0)) / X.std(0)\ny  = np.repeat([0, 1], 60)\n\n# Network weights\nW1 = np.random.randn(2, n_h) * np.sqrt(2/2)\nb1 = np.zeros(n_h)\nW2 = np.random.randn(n_h, 1) * np.sqrt(2/n_h)\nb2 = np.zeros(1)\n\n# Decision-boundary mesh\ng = 28\ngx, gy  = np.linspace(-3, 3, g), np.linspace(-3, 3, g)\nxx, yy  = np.meshgrid(gx, gy)\ngrid    = np.c_[xx.ravel(), yy.ravel()]\ngx_flat = xx.ravel(); gy_flat = yy.ravel()\nx_edge  = np.array([-3.5, 3.5])\nCOLORS  = ['#e74c3c', '#3498db']; MARKERS = ['o', '^']\n\ndef relu(z):    return np.maximum(0, z)\ndef sigmoid(z): return 1 / (1 + np.exp(-np.clip(z, -50, 50)))\n\ndef forward(Xb):\n    z1 = Xb @ W1 + b1\n    return sigmoid(relu(z1) @ W2 + b2).ravel(), z1\n\nfig, axs = plt.subplots(2, 4, figsize=(14, 7))\n\ndef update(frame):\n    global W1, b1, W2, b2\n    lr = 0.05\n    for _ in range(10):\n        p, z1 = forward(X); a1 = relu(z1); N = len(X)\n        dz2 = (p - y).reshape(-1, 1) / N\n        dW2 = a1.T @ dz2;  db2 = dz2.sum(0)\n        dz1 = (dz2 @ W2.T) * (z1 \u003e 0)\n        W1 -= lr * X.T @ dz1;  b1 -= lr * dz1.sum(0)\n        W2 -= lr * dW2;         b2 -= lr * db2\n    p_tr, _ = forward(X)\n    acc = ((p_tr \u003e 0.5).astype(int) == y).mean()\n    _, z1_g = forward(grid)\n    for i, ax in enumerate(axs.flat):\n        ax.cla()\n        active = z1_g[:, i] \u003e 0\n        ax.scatter(gx_flat[~active], gy_flat[~active], c='#eeeeee', s=55)\n        ax.scatter(gx_flat[ active], gy_flat[ active], c='#c6e2f5', s=55)\n        w, b = W1[:, i], b1[i]\n        if abs(w[1]) \u003e 1e-9:\n            ax.plot(x_edge, -(w[0]*x_edge + b)/w[1], 'k-', linewidth=1.5)\n        for k in range(2):\n            m = y == k\n            ax.scatter(X[m,0], X[m,1], c=COLORS[k], marker=MARKERS[k],\n                       s=45, edgecolors='k', linewidths=0.5)\n        ax.set_xlim(-3, 3); ax.set_ylim(-3, 3)\n        ax.set_title(f'Unit {i+1}  w={W2[i,0]:+.2f}')\n    plt.suptitle(f'1-hidden-layer ReLU — epoch {frame*10} | acc {acc:.0%}'\n                 '   [double-click any panel to focus]')\n\nplt.animate(update, frames=100, interval=100)\nplt.show()\n\"\n```\n\n#### Neural network training curves\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\nnp.random.seed(1); losses, accs = [], []\ndef update(frame):\n    t = frame/80\n    losses.append(2.3*np.exp(-3*t)+0.08+0.03*np.random.randn())\n    accs.append(min(0.99,1-np.exp(-4*t)*0.9+0.01*np.random.randn()))\n    axs = plt.gcf().axes\n    axs[0].cla(); axs[1].cla()\n    axs[0].plot(losses,'b-',linewidth=2); axs[0].set_title('Loss'); axs[0].grid()\n    axs[1].plot(accs,'g-',linewidth=2); axs[1].set_title('Accuracy'); axs[1].set_ylim(0,1); axs[1].grid()\nplt.subplots(1,2,figsize=(10,4))\nplt.animate(update, frames=80, interval=80); plt.show()\n\"\n```\n\n#### fill_between: confidence band widening under distribution shift\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\nnp.random.seed(0)\nx = np.linspace(0, 10, 80)\nmean = np.sin(x) * np.exp(-x/8)\nnoise_levels = np.linspace(0.05, 0.6, 30)\ndef update(frame):\n    sigma = noise_levels[frame]\n    plt.cla()\n    plt.plot(x, mean, 'steelblue', linewidth=2, label='prediction')\n    plt.fill_between(x, mean - sigma, mean + sigma, alpha=0.35, color='steelblue', label=f'± {sigma:.2f}')\n    plt.fill_between(x, mean - 2*sigma, mean + 2*sigma, alpha=0.15, color='steelblue', label='± 2σ')\n    plt.ylim(-1.8, 1.8); plt.legend(); plt.grid()\n    plt.title(f'Uncertainty grows under distribution shift — σ={sigma:.2f}')\nplt.animate(update, frames=30, interval=150); plt.show()\n\"\n```\n\n#### errorbar: learning curve\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\nnp.random.seed(0)\nsizes = np.array([10, 25, 50, 100, 200, 400, 800])\nmeans = 1 - 0.88*np.exp(-sizes/120) + 0.015*np.random.randn(len(sizes))\nstds  = 0.32*np.exp(-sizes/80) + 0.01\ndef update(frame):\n    n = frame + 1\n    plt.cla()\n    plt.errorbar(sizes[:n], means[:n], yerr=stds[:n], fmt='o-', capsize=5,\n                 color='steelblue', label='accuracy ± std')\n    plt.xlim(-30, 850); plt.ylim(0, 1.1)\n    plt.xlabel('Training set size'); plt.ylabel('Accuracy')\n    plt.title('Learning Curve — more data, less variance'); plt.legend(); plt.grid()\nplt.animate(update, frames=len(sizes), interval=600); plt.show()\n\"\n```\n\n#### boxplot: prediction distribution per epoch\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\nnp.random.seed(1)\nepochs = [5, 10, 20, 40, 80, 160]\ndata = [np.random.normal(0.35 + 0.55*(i/len(epochs)), max(0.28 - i*0.04, 0.04), 80)\n        for i in range(len(epochs))]\ndef update(frame):\n    n = frame + 1\n    plt.cla()\n    plt.boxplot(data[:n], labels=[str(e) for e in epochs[:n]])\n    plt.ylim(-0.1, 1.1)\n    plt.xlabel('Epoch'); plt.ylabel('Predicted probability')\n    plt.title(f'Prediction Distribution — epoch {epochs[frame]}')\nplt.animate(update, frames=len(epochs), interval=700); plt.show()\n\"\n```\n\n#### violinplot: activation distribution per training step\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\nnp.random.seed(2)\nsteps = 6\ndata = [np.random.normal(i*0.5, max(1.1 - i*0.16, 0.15), 120) for i in range(steps)]\ndef update(frame):\n    n = frame + 1\n    plt.cla()\n    plt.violinplot(data[:n], positions=list(range(1, n+1)), widths=0.7)\n    plt.xlim(0, steps+1); plt.ylim(-3.5, 5.5)\n    plt.xlabel('Training step'); plt.ylabel('Activation value')\n    plt.title(f'Activation Distribution — step {frame+1} of {steps}')\nplt.animate(update, frames=steps, interval=700); plt.show()\n\"\n```\n\n#### pie: class balance as dataset is rebalanced\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt\nlabels = ['Negative', 'Neutral', 'Positive']\nstages = [[70,20,10],[60,25,15],[50,30,20],[45,32,23],[40,35,25],[33,34,33]]\ncaptions = ['raw','oversample pos','oversample more','near balance','balanced','uniform']\ndef update(frame):\n    plt.cla()\n    vals = stages[frame]\n    plt.pie(vals, labels=labels, startangle=90)\n    pcts = ' | '.join(f'{l}: {v}%' for l,v in zip(labels,vals))\n    plt.title(f'Class Balance — {captions[frame]}\\n{pcts}')\nplt.animate(update, frames=len(stages), interval=900); plt.show()\n\"\n```\n\n#### stackplot: feature contributions per complexity level\n```bash\npython3 -c \"\nimport plotlive.pyplot as plt, numpy as np\nnp.random.seed(3)\nx = np.arange(20)\nfeats = ['linear','interactions','polynomials','residuals']\ncomponents = [np.abs(np.random.randn(20))*(i+1)*0.4 for i in range(len(feats))]\ndef update(frame):\n    n = frame + 1\n    plt.cla()\n    plt.stackplot(x, *components[:n], labels=feats[:n], alpha=0.85)\n    plt.xlim(0, 19); plt.ylim(0, sum(c.max() for c in components)*1.05)\n    plt.xlabel('Sample'); plt.ylabel('Explained variance')\n    plt.title(f'Model Complexity — adding {feats[frame]}')\n    plt.legend()\nplt.animate(update, frames=len(feats), interval=900); plt.show()\n\"\n```\n\n---\n\n### Sorting algorithm visualizations\n\nEach opens its own window with 7 elements, a color legend, and step descriptions.\n\n```bash\ncd examples\npython3 bubble_sort.py\npython3 insertion_sort.py\npython3 selection_sort.py\npython3 heap_sort.py\npython3 merge_sort.py\npython3 quick_sort.py\n```\n\n| Color | Meaning |\n|-------|---------|\n| Blue | Unsorted |\n| Orange | Being compared |\n| Red | Being swapped |\n| Green | Confirmed sorted |\n\nUse `←` / `→` to step frame by frame. The value of each element is shown below its bar.\n\n#### Runtime benchmark — all 6 algorithms\n```bash\npython3 sort_benchmark.py\n```\n\nBenchmarks 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.\n\n---\n\n## Run tests\n\n```bash\npytest tests/\n```\n\n## Dependencies\n\n- `pygame-ce \u003e= 2.4.0`\n- `numpy \u003e= 1.24.0`\n- Python 3.10+\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstormsidali2001%2Fplotlive","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstormsidali2001%2Fplotlive","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstormsidali2001%2Fplotlive/lists"}