Skip to content

Instantly share code, notes, and snippets.

@dutta-alankar
Created February 12, 2025 11:35
Show Gist options
  • Save dutta-alankar/d3e8086545c32631bfa344c65aa8ae3e to your computer and use it in GitHub Desktop.
Save dutta-alankar/d3e8086545c32631bfa344c65aa8ae3e to your computer and use it in GitHub Desktop.
A simple html script solving and ODE with python using Pyodide
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ODE Solver (Enhanced)</title>
<script src="https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js"></script>
<style>
:root {
--bg-color: white;
--text-color: black;
--btn-bg: #ddd;
--btn-text: black;
}
[data-theme="dark"] {
--bg-color: #1e1e1e;
--text-color: white;
--btn-bg: #444;
--btn-text: white;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: Arial, sans-serif;
text-align: center;
transition: background 0.3s, color 0.3s;
}
button {
background-color: var(--btn-bg);
color: var(--btn-text);
border: none;
padding: 10px;
margin: 10px;
cursor: pointer;
}
img {
border: 1px solid var(--text-color);
opacity: 0;
transition: opacity 0.5s ease-in-out;
}
.loading {
font-size: 1.5em;
font-weight: bold;
animation: blink 1s infinite;
}
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
</style>
</head>
<body>
<h1>ODE Solver (Python in Browser)</h1>
<button onclick="toggleTheme()">Toggle Light/Dark Mode</button>
<br><br>
<label for="k-slider">Decay Rate (k): <span id="k-value">1.0</span></label>
<input type="range" id="k-slider" min="0.1" max="2.0" step="0.1" value="1.0" oninput="solveODE()">
<br><br>
<label>Initial Condition y(0):</label>
<select id="y0-select" onchange="solveODE()">
<option value="1">1</option>
<option value="2">2</option>
<option value="5">5</option>
</select>
<br><br>
<p id="loading" class="loading" style="display:none;">Solving ODE...</p>
<img id="plot" src="" alt="ODE Solution will appear here">
<script>
function detectSystemTheme() {
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
function applyTheme(theme) {
document.documentElement.setAttribute("data-theme", theme);
localStorage.setItem("theme", theme);
}
function toggleTheme() {
let currentTheme = document.documentElement.getAttribute("data-theme");
let newTheme = currentTheme === "dark" ? "light" : "dark";
applyTheme(newTheme);
solveODE();
}
function loadTheme() {
let savedTheme = localStorage.getItem("theme") || detectSystemTheme();
applyTheme(savedTheme);
}
async function loadPyodideAndPackages() {
window.pyodide = await loadPyodide();
await pyodide.loadPackage(["numpy", "scipy", "matplotlib"]);
solveODE(); // Solve once when page loads
}
async function solveODE() {
let k = parseFloat(document.getElementById("k-slider").value);
let y0 = parseFloat(document.getElementById("y0-select").value);
document.getElementById("k-value").innerText = k;
let theme = document.documentElement.getAttribute("data-theme");
// Show loading animation
document.getElementById("loading").style.display = "block";
document.getElementById("plot").style.opacity = "0";
let pythonCode = `
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
plt.style.use("dark_background" if "${theme}" == "dark" else "default")
## Plot Styling
matplotlib.rcParams["xtick.direction"] = "in"
matplotlib.rcParams["ytick.direction"] = "in"
matplotlib.rcParams["xtick.top"] = False
matplotlib.rcParams["ytick.right"] = True
matplotlib.rcParams["xtick.minor.visible"] = True
matplotlib.rcParams["ytick.minor.visible"] = True
matplotlib.rcParams["axes.grid"] = True
matplotlib.rcParams["grid.linestyle"] = ":"
matplotlib.rcParams["grid.linewidth"] = 0.8
matplotlib.rcParams["grid.color"] = "gray"
matplotlib.rcParams["grid.alpha"] = 0.3
matplotlib.rcParams["lines.dash_capstyle"] = "round"
matplotlib.rcParams["lines.solid_capstyle"] = "round"
matplotlib.rcParams["legend.handletextpad"] = 0.4
matplotlib.rcParams["axes.linewidth"] = 1.0
matplotlib.rcParams["lines.linewidth"] = 3.5
matplotlib.rcParams["ytick.major.width"] = 1.2
matplotlib.rcParams["xtick.major.width"] = 1.2
matplotlib.rcParams["ytick.minor.width"] = 1.0
matplotlib.rcParams["xtick.minor.width"] = 1.0
matplotlib.rcParams["ytick.major.size"] = 11.0
matplotlib.rcParams["xtick.major.size"] = 11.0
matplotlib.rcParams["ytick.minor.size"] = 5.0
matplotlib.rcParams["xtick.minor.size"] = 5.0
matplotlib.rcParams["xtick.major.pad"] = 10.0
matplotlib.rcParams["xtick.minor.pad"] = 10.0
matplotlib.rcParams["ytick.major.pad"] = 6.0
matplotlib.rcParams["ytick.minor.pad"] = 6.0
matplotlib.rcParams["xtick.labelsize"] = 26.0
matplotlib.rcParams["ytick.labelsize"] = 26.0
matplotlib.rcParams["axes.titlesize"] = 24.0
matplotlib.rcParams["axes.labelsize"] = 28.0
matplotlib.rcParams["axes.labelpad"] = 8.0
plt.rcParams["font.size"] = 28
matplotlib.rcParams["legend.handlelength"] = 2
# matplotlib.rcParams["figure.dpi"] = 200
matplotlib.rcParams["axes.axisbelow"] = True
matplotlib.rcParams["figure.figsize"] = (13,10)
def ode(t, y):
return -${k} * y
t_span = (0, 10)
t_eval = np.linspace(*t_span, 100)
sol = solve_ivp(ode, t_span, [${y0}], t_eval=t_eval, method="BDF")
plt.figure()
plt.plot(sol.t, sol.y[0], label="y(t)")
plt.xlabel("Time")
plt.ylabel("y")
plt.title("ODE Solution using BDF")
plt.legend()
import io
import base64
buf = io.BytesIO()
plt.savefig(buf, format="png", transparent=True)
buf.seek(0)
"data:image/png;base64," + base64.b64encode(buf.read()).decode()
`;
let result = await pyodide.runPythonAsync(pythonCode);
// Hide loading and fade in plot
document.getElementById("loading").style.display = "none";
document.getElementById("plot").src = result;
document.getElementById("plot").style.opacity = "1";
}
loadTheme();
loadPyodideAndPackages();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment