Last active
February 13, 2025 21:47
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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