Skip to content

Instantly share code, notes, and snippets.

@dutta-alankar
Last active February 13, 2025 21:47
Show Gist options
  • Save dutta-alankar/d1db23714b3553708e759b25fdedf3ff to your computer and use it in GitHub Desktop.
Save dutta-alankar/d1db23714b3553708e759b25fdedf3ff to your computer and use it in GitHub Desktop.
A fancy html code solving ODE using Python at the end user
<!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 ease-in-out, color 0.3s ease-in-out;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
max-width: 600px;
margin: auto;
}
.controls {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
width: 100%;
}
button {
background-color: var(--btn-bg);
color: var(--btn-text);
border: none;
padding: 10px 15px;
margin: 10px;
cursor: pointer;
transition: background 0.3s, transform 0.2s;
}
button:hover {
transform: scale(1.05);
}
img {
border: 0px 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>
<p id="pyodide-loading" class="loading">Loading Pyodide...</p>
<div class="container" id="main-container" style="display:none;">
<button onclick="toggleTheme()">Toggle Light/Dark Mode</button>
<div class="controls">
<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" disabled>
<label>Initial Condition y(0):</label>
<select id="y0-select" disabled>
<option value="1">1</option>
<option value="2">2</option>
<option value="5">5</option>
</select>
</div>
<button id="solve-btn" onclick="solveODEFirstTime()" disabled>Solve</button>
<p id="loading" class="loading" style="display:none;">Solving ODE...</p>
<img id="plot" src="" alt="ODE Solution will appear here">
<button id="export-btn" onclick="exportImage()" style="display:none;">Export Image</button>
</div>
<script>
let pyodideReady = false;
let firstSolve = false;
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() {
console.log("Loading Pyodide...");
window.pyodide = await loadPyodide();
await pyodide.loadPackage(["numpy", "scipy", "matplotlib"]);
console.log("Pyodide and required packages installed.");
pyodideReady = true;
document.getElementById("pyodide-loading").style.display = "none";
document.getElementById("main-container").style.display = "flex";
document.getElementById("k-slider").disabled = false;
document.getElementById("y0-select").disabled = false;
document.getElementById("solve-btn").disabled = false;
}
async function solveODEFirstTime() {
document.getElementById("solve-btn").style.display = "none";
firstSolve = true;
solveODE();
}
async function solveODE() {
if (!pyodideReady) {
console.log("Pyodide not yet loaded. Waiting...");
return;
}
if (!firstSolve) {
console.log("Hit the solve button for the first time ...");
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");
return;
}
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");
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
import io, base64
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(figsize=(13,10))
plt.plot(sol.t, sol.y[0], label="y(t)")
plt.xlabel("Time")
plt.ylabel("y")
plt.title("ODE Solution using BDF")
plt.legend()
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);
document.getElementById("loading").style.display = "none";
document.getElementById("plot").src = result;
document.getElementById("plot").style.opacity = "1";
document.getElementById("export-btn").style.display = "block";
}
function exportImage() {
let img = document.getElementById("plot");
let link = document.createElement("a");
link.href = img.src;
link.download = "ODE_Solution.png";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
document.getElementById("k-slider").addEventListener("input", solveODE);
document.getElementById("y0-select").addEventListener("change", solveODE);
loadTheme();
loadPyodideAndPackages();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment