跳到主要内容Pyodide:在浏览器中运行 Python | 极客日志PythonNode.jsAI大前端
Pyodide:在浏览器中运行 Python
综述由AI生成Pyodide 是基于 WebAssembly 的 Python 运行时,支持在浏览器和 Node.js 中直接运行 Python 代码。它允许用户通过 micropip 安装纯 Python 包及编译扩展,完整支持 NumPy、pandas 等科学计算库,并提供 JavaScript 与 Python 的双向互操作性。 Pyodide 的核心特性,并通过一个包含 Matplotlib 绘图功能的 HTML 示例,演示了如何在无后端的情况下于浏览器端生成数据图表。
神经兮兮5.5K 浏览 Python 运行在浏览器
梦想成真
在过去的两年里,我们全身心投入到在各种操作系统上开发和部署 Python 应用的工作中,这一切源于其丰富的库资源、简洁直观的语法以及卓越的跨平台兼容性。我们的工作横跨多个领域,涵盖人工智能与数据科学项目、软件维护与测试、网页开发、数据库服务器管理、物联网监控以及安卓应用开发。
长久以来,我们始终认为 Python 能在网页浏览器中像 JavaScript 一样自由运行似乎只是一个遥不可及的梦想——虽然已有若干开源解决方案试图实现这一目标,但它们都存在不少缺陷。
Pyodide 简介
Pyodide 是一个基于 WebAssembly 的 Python 运行时,可以直接在 Web 浏览器和 Node.js 中运行。它支持用户通过 micropip 安装和运行纯 Python 包以及带有编译扩展的包。NumPy、pandas 和 scikit-learn 等关键科学计算库均获得完整支持。该平台具备 JavaScript 与 Python 之间无缝的双向互操作性,包括错误处理和 async/await 支持。在浏览器中运行时,Pyodide 可完全访问 Web API,实现全面的网络集成。
综合例子(调用 numpy 等科学计算库)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Pyodide 中文图表 - 修复字体问题</title>
<script src="https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js"></script>
<style>
*{margin: 0;padding: 0;box-sizing: border-box;}
body{font-family:'Microsoft YaHei','微软雅黑', sans-serif;background:linear-gradient(135deg, #667eea 0%, #764ba2 100%);min-height: 100vh;display: flex;justify-content: center;align-items: center;padding: 20px;}
.container{width: 100%;max-width: 1400px;background: white;border-radius: 20px;box-shadow: 0 20px 40px rgba(0,0,0,0.1);overflow: hidden;}
header{background:linear-gradient(90deg, #2c3e50 0%, #4a6491 100%);color: white;padding: 30px;text-align: center;}
h1{font-size: 2.8rem;margin-bottom: 10px;font-weight: bold;}
.subtitle{font-size: 1.3rem;opacity: 0.9;margin-bottom: 20px;}
.content{padding: 40px;}
.controls{display: grid;grid-template-columns:repeat(auto-fit,minmax(250px, 1fr));gap: 20px;margin-bottom: 40px;}
.btn{background:linear-gradient(90deg, #4776E6 0%, #8E54E9 100%);color: white;border: none;padding: 18px 25px;border-radius: 12px;font-size: 1.2rem;cursor: pointer;transition: all 0.3s ease;display: flex;align-items: center;justify-content: center;gap: 10px;font-weight: bold;}
.btn:hover{transform:translateY(-3px);box-shadow: 0 10px 20px rgba(0,0,0,0.2);}
.btn:active{transform:translateY(0);}
.btn:disabled{background: #cccccc;cursor: not-allowed;transform: none;box-shadow: none;opacity: 0.7;}
#status{padding: 20px;margin: 30px 0;border-radius: 12px;text-align: center;font-size: 1.1rem;font-weight: bold;}
.loading{background:linear-gradient(90deg, #e3f2fd, #bbdefb);color: #1565c0;border-left: 6px solid #2196f3;}
.success{background:linear-gradient(90deg, #e8f5e9, #c8e6c9);color: #2e7d32;border-left: 6px solid #4caf50;}
.error{background:linear-gradient(90deg, #ffebee, #ffcdd2);color: #c62828;border-left: 6px solid #f44336;}
#plotContainer{min-height: 700px;border: 3px dashed #e0e0e0;border-radius: 15px;background: #f8f9fa;display: flex;justify-content: center;align-items: center;padding: 30px;margin-bottom: 30px;position: relative;}
#plotContainer img{max-width: 100%;max-height: 650px;box-shadow: 0 10px 30px rgba(0,0,0,0.15);border-radius: 10px;border: 1px solid #ddd;}
.chart-info{background:linear-gradient(90deg, #f8f9fa, #e9ecef);padding: 25px;border-radius: 12px;margin-top: 30px;border-left: 5px solid #4b6cb7;}
.chart-info h3{color: #2c3e50;margin-bottom: 15px;font-size: 1.5rem;display: flex;align-items: center;gap: 10px;}
.chart-info p{margin: 8px 0;line-height: 1.8;font-size: 1.1rem;}
.info-grid{display: grid;grid-template-columns:repeat(auto-fit,minmax(250px, 1fr));gap: 15px;margin-top: 20px;}
.info-item{background: white;padding: 15px;border-radius: 8px;border-left: 4px solid #667eea;}
footer{text-align: center;padding: 25px;color: #666;border-top: 1px solid #eee;background: #f8f9fa;font-size: 1rem;}
.font-loading{font-size: 0.9rem;color: #666;margin-top: 10px;}
@media(max-width: 768px){.container{margin: 10px;border-radius: 15px;}header{padding: 20px;}h1{font-size: 2rem;}.content{padding: 20px;}.controls{grid-template-columns: 1fr;}.btn{padding: 15px;font-size: 1.1rem;}#plotContainer{min-height: 500px;padding: 15px;}}
🎨 Python + Matplotlib 中文图表
在浏览器中运行 Python,完美支持中文显示
🚀 初始化环境
📈 正弦函数图
🎯 散点分布图
📊 销售柱状图
🎨 多图对比
请点击'初始化环境'按钮开始
👆 点击上方按钮生成图表
图表将在这里显示
正在加载环境...
📋 图表信息
等待生成图表...
© Pyodide 学习项目
技术栈:HTML5 + JavaScript + Pyodide + Matplotlib
Pyodide 官网 | Matplotlib 文档
本页面完全在浏览器端运行
</style>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<header>
<h1>
</h1>
<div class="subtitle">
</div>
</header>
<div class="content">
<div class="controls">
<button class="btn" onclick="initPyodide()" id="btnInit">
<span>
</span>
</button>
<button class="btn" onclick="createChart1()" id="btnChart1" disabled>
<span>
</span>
</button>
<button class="btn" onclick="createChart2()" id="btnChart2" disabled>
<span>
</span>
</button>
<button class="btn" onclick="createChart3()" id="btnChart3" disabled>
<span>
</span>
</button>
<button class="btn" onclick="createChart4()" id="btnChart4" disabled>
<span>
</span>
</button>
</div>
<div id="status" class="loading">
</div>
<div id="plotContainer">
<div style="text-align: center;color: #666;">
<h3 style="margin-bottom: 15px;">
</h3>
<p>
</p>
<p class="font-loading">
</p>
</div>
</div>
<div id="chartInfo" class="chart-info">
<h3>
<span>
</span>
</h3>
<p>
</p>
</div>
</div>
<footer>
<div style="display: flex;justify-content: space-between;align-items: center;">
<div>
<p style="margin: 0;">
<script>
document.write(new Date().getFullYear())
</script>
</p>
<p style="margin: 5px 0 0 0;font-size: 0.9rem;color: #888;">
</p>
</div>
<div style="text-align: right;">
<p style="margin: 0;">
<a href="https://pyodide.org" target="_blank" style="color: #666;text-decoration: none;">
</a>
<a href="https://matplotlib.org" target="_blank" style="color: #666;text-decoration: none;">
</a>
</p>
<p style="margin: 5px 0 0 0;font-size: 0.9rem;color: #888;">
</p>
</div>
</div>
</footer>
</div>
<script>
let pyodide = null;
let isInitialized = false;
async function initPyodide(){
try{
updateStatus('第一步:正在加载 Pyodide 环境... (约 15-25 秒)','loading');
disableAllButtons(true);
pyodide = await loadPyodide({indexURL:"https://cdn.jsdelivr.net/pyodide/v0.25.0/full/"});
updateStatus('第二步:正在安装基础 Python 包...','loading');
await pyodide.loadPackage(["numpy","matplotlib","scipy"]);
updateStatus('✅ 环境初始化完成!现在可以生成图表了。','success');
enableChartButtons();
isInitialized = true;
setTimeout(()=>createChart1(),500);
}catch(error){
updateStatus(`❌ 初始化失败:${error.message}`,'error');
console.error("初始化错误:", error);
}
}
async function createChart1(){
if(!checkInitialized()) return;
try{
updateStatus('正在生成正弦函数图表...','loading');
disableChartButtons(true);
await pyodide.runPythonAsync(`
import matplotlib.pyplot as plt
import numpy as np
import io
import base64
from js import document
import matplotlib
plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'Arial Unicode MS', 'Arial']
plt.rcParams['axes.unicode_minus'] = False
use_chinese = False
x = np.linspace(0, 4 * np.pi, 200)
y = np.sin(x)
fig, ax = plt.subplots(figsize=(12, 7), dpi=100)
ax.plot(x, y, color='#FF6B6B', linewidth=3.5, label='Sine Function: y = sin(x)')
ax.fill_between(x, y, alpha=0.15, color='#FF6B6B')
if use_chinese:
ax.set_title('正弦函数图像演示', fontsize=18, fontweight='bold', pad=20)
ax.set_xlabel('角度 (弧度)', fontsize=14, labelpad=10)
ax.set_ylabel('函数值 y', fontsize=14, labelpad=10)
else:
ax.set_title('Sine Function Demonstration', fontsize=18, fontweight='bold', pad=20)
ax.set_xlabel('Angle (radians)', fontsize=14, labelpad=10)
ax.set_ylabel('Function Value y', fontsize=14, labelpad=10)
ax.grid(True, alpha=0.3, linestyle='--', linewidth=0.5)
ax.legend(loc='upper right', fontsize=12, frameon=True, shadow=True)
special_points = [0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi]
special_labels = ['0', 'π/2', 'π', '3π/2', '2π']
special_y = np.sin(special_points)
ax.scatter(special_points, special_y, color='#4ECDC4', s=150, zorder=5, edgecolors='black', linewidth=2, label='Key Points')
for i, (point, y_val, label) in enumerate(zip(special_points, special_y, special_labels)):
ax.annotate(f'{label}\\n({y_val:.2f})', xy=(point, y_val), xytext=(10, 30 if i % 2 == 0 else -40), textcoords='offset points', fontsize=11, bbox=dict(boxstyle="round,pad=0.3", facecolor="white", alpha=0.8), arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=0.2"))
ax.set_xlim(-0.5, 4*np.pi + 0.5)
ax.set_ylim(-1.2, 1.2)
ax.set_facecolor('#f8f9fa')
fig.patch.set_facecolor('white')
buf = io.BytesIO()
plt.savefig(buf, format='png', dpi=150, bbox_inches='tight', facecolor=fig.get_facecolor(), edgecolor='none', transparent=False)
buf.seek(0)
img_str = base64.b64encode(buf.read()).decode('utf-8')
plt.close(fig)
plot_div = document.getElementById("plotContainer")
plot_div.innerHTML = f'''<div><h3>✅ 正弦函数图表生成成功</h3><img src="data:image/png;base64,{img_str}" alt="正弦函数图表"></div>'''
info_div = document.getElementById("chartInfo")
info_div.innerHTML = f'''<h3><span>📋</span> 正弦函数图表信息</h3><div><div><strong>函数表达式:</strong><br>y = sin(x)</div><div><strong>定义域:</strong><br>x ∈ [0, 4π] ≈ [0, 12.57]</div><div><strong>值域:</strong><br>y ∈ [{y.min():.3f}, {y.max():.3f}]</div><div><strong>周期:</strong><br>2π ≈ 6.283</div><div><strong>数据点数:</strong><br>{len(x)} 个</div><div><strong>图表尺寸:</strong><br>12×7 英寸,150 DPI</div></div>'''
`);
updateStatus('✅ 正弦函数图表生成成功!','success');
disableChartButtons(false);
}catch(error){
updateStatus(`❌ 生成图表失败:${error.message}`,'error');
console.error("图表错误:", error);
disableChartButtons(false);
}
}
async function createChart2(){
if(!checkInitialized()) return;
try{
updateStatus('正在生成散点分布图...','loading');
disableChartButtons(true);
const seed = Math.floor(Math.random()*1000);
await pyodide.runPythonAsync(`
import matplotlib.pyplot as plt
import numpy as np
import io
import base64
from js import document
plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'Arial']
plt.rcParams['axes.unicode_minus'] = False
np.random.seed(${seed})
n_points = 200
x = np.random.randn(n_points) * 2.5
y = np.random.randn(n_points) * 2.5
colors = np.random.rand(n_points)
sizes = 30 + 250 * np.random.rand(n_points)
fig, ax = plt.subplots(figsize=(12, 7), dpi=100)
ax.set_title('Random Scatter Distribution', fontsize=18, fontweight='bold', pad=20)
ax.set_xlabel('X Coordinate', fontsize=14, labelpad=10)
ax.set_ylabel('Y Coordinate', fontsize=14, labelpad=10)
scatter = ax.scatter(x, y, c=colors, s=sizes, alpha=0.7, cmap='plasma', edgecolors='white', linewidth=0.8)
ax.grid(True, alpha=0.3, linestyle='--')
cbar = plt.colorbar(scatter)
cbar.set_label('Color Intensity', fontsize=12)
stats_text = f'''Statistics: • Data Points: {n_points} • X Mean: {x.mean():.3f} • Y Mean: {y.mean():.3f} • Correlation: {np.corrcoef(x, y)[0,1]:.3f}'''
ax.text(0.02, 0.98, stats_text, transform=ax.transAxes, fontsize=11, verticalalignment='top', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
ax.set_facecolor('#f5f5f5')
fig.patch.set_facecolor('white')
buf = io.BytesIO()
plt.savefig(buf, format='png', dpi=150, bbox_inches='tight')
buf.seek(0)
img_str = base64.b64encode(buf.read()).decode('utf-8')
plt.close(fig)
plot_div = document.getElementById("plotContainer")
plot_div.innerHTML = f'''<div><h3>✅ 散点图生成成功</h3><img src="data:image/png;base64,{img_str}" alt="散点分布图"></div>'''
info_div = document.getElementById("chartInfo")
info_div.innerHTML = f'''<h3><span>📋</span> 散点图统计信息</h3><div><div><strong>随机种子:</strong><br>${seed}</div><div><strong>数据规模:</strong><br>{n_points} 个点</div><div><strong>X 统计:</strong><br>均值={x.mean():.3f}, 标准差={x.std():.3f}</div><div><strong>Y 统计:</strong><br>均值={y.mean():.3f}, 标准差={y.std():.3f}</div><div><strong>数据范围:</strong><br>X:[{x.min():.2f}, {x.max():.2f}]<br>Y:[{y.min():.2f}, {y.max():.2f}]</div><div><strong>相关性:</strong><br>{np.corrcoef(x, y)[0,1]:.3f}</div></div>'''
`);
updateStatus('✅ 散点图生成成功!','success');
disableChartButtons(false);
}catch(error){
updateStatus(`❌ 生成图表失败:${error.message}`,'error');
console.error("图表错误:", error);
disableChartButtons(false);
}
}
async function createChart3(){
if(!checkInitialized()) return;
try{
updateStatus('正在生成销售柱状图...','loading');
disableChartButtons(true);
await pyodide.runPythonAsync(`
import matplotlib.pyplot as plt
import numpy as np
import io
import base64
from js import document
plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'Arial']
plt.rcParams['axes.unicode_minus'] = False
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
sales = np.array([85, 67, 92, 78, 88, 95, 110, 105, 98, 120, 115, 130])
fig, ax = plt.subplots(figsize=(14, 8), dpi=100)
colors = plt.cm.viridis(np.linspace(0.2, 0.8, len(months)))
bars = ax.bar(months, sales, color=colors, edgecolor='black', linewidth=1.5, alpha=0.8)
for bar in bars:
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height + 1.5, f'{height:.0f}', ha='center', va='bottom', fontsize=11, fontweight='bold')
ax.set_title('2024 Annual Sales Data Analysis', fontsize=20, fontweight='bold', pad=25)
ax.set_xlabel('Month', fontsize=16, labelpad=15)
ax.set_ylabel('Sales (10k yuan)', fontsize=16, labelpad=15)
avg_sales = sales.mean()
ax.axhline(y=avg_sales, color='red', linestyle='--', linewidth=3, alpha=0.7, label=f'Average: {avg_sales:.1f}')
plt.setp(ax.get_xticklabels(), rotation=45, ha='right', rotation_mode='anchor')
ax.grid(True, alpha=0.3, axis='y', linestyle='--')
ax.set_facecolor('#f8f9fa')
fig.patch.set_facecolor('white')
ax.set_ylim(0, max(sales) * 1.15)
ax.legend(loc='upper left', fontsize=12, frameon=True, shadow=True)
total_sales = sales.sum()
ax.text(0.02, 0.98, f'Total Sales: {total_sales:.0f}', transform=ax.transAxes, fontsize=14, fontweight='bold', verticalalignment='top', bbox=dict(boxstyle='round', facecolor='gold', alpha=0.8))
buf = io.BytesIO()
plt.savefig(buf, format='png', dpi=150, bbox_inches='tight', facecolor=fig.get_facecolor())
buf.seek(0)
img_str = base64.b64encode(buf.read()).decode('utf-8')
plt.close(fig)
plot_div = document.getElementById("plotContainer")
plot_div.innerHTML = f'''<div><h3>✅ 销售柱状图生成成功</h3><img src="data:image/png;base64,{img_str}" alt="销售柱状图"></div>'''
max_month = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'][sales.argmax()]
min_month = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'][sales.argmin()]
growth = ((sales[-1] - sales[0]) / sales[0]) * 100
info_div = document.getElementById("chartInfo")
info_div.innerHTML = f'''<h3><span>📋</span> 销售数据分析报告</h3><div><div><strong>年度总销售额:</strong><br>{total_sales:,.0f} 万元</div><div><strong>月平均销售额:</strong><br>{avg_sales:.1f} 万元</div><div><strong>最高销售额月份:</strong><br>{max_month} ({sales.max():.0f}万)</div><div><strong>最低销售额月份:</strong><br>{min_month} ({sales.min():.0f}万)</div><div><strong>年度增长率:</strong><br>{growth:.1f}%</div><div><strong>最佳季度:</strong><br>第四季度</div></div>'''
`);
updateStatus('✅ 销售柱状图生成成功!','success');
disableChartButtons(false);
}catch(error){
updateStatus(`❌ 生成图表失败:${error.message}`,'error');
console.error("图表错误:", error);
disableChartButtons(false);
}
}
async function createChart4(){
if(!checkInitialized()) return;
try{
updateStatus('正在生成多图对比展示...','loading');
disableChartButtons(true);
await pyodide.runPythonAsync(`
import matplotlib.pyplot as plt
import numpy as np
import io
import base64
from js import document
from scipy.stats import norm
plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'Arial']
plt.rcParams['axes.unicode_minus'] = False
fig = plt.figure(figsize=(16, 12), dpi=100)
fig.suptitle('Multi-Chart Comparison', fontsize=22, fontweight='bold', y=0.98)
ax1 = plt.subplot(2, 2, 1)
x = np.linspace(0, 2 * np.pi, 100)
ax1.plot(x, np.sin(x), 'b-', linewidth=3, label='Sine Function')
ax1.plot(x, np.cos(x), 'r--', linewidth=3, label='Cosine Function')
ax1.set_title('Trigonometric Functions', fontsize=16, fontweight='bold')
ax1.set_xlabel('Angle (radians)', fontsize=12)
ax1.set_ylabel('Function Value', fontsize=12)
ax1.grid(True, alpha=0.3)
ax1.legend(loc='best')
ax1.set_facecolor('#f0f8ff')
ax2 = plt.subplot(2, 2, 2)
categories = ['Product A', 'Product B', 'Product C', 'Product D', 'Product E']
values1 = [85, 92, 78, 88, 95]
values2 = [70, 85, 90, 82, 88]
x_pos = np.arange(len(categories))
width = 0.35
ax2.bar(x_pos - width/2, values1, width, label='First Half', color='skyblue', edgecolor='black')
ax2.bar(x_pos + width/2, values2, width, label='Second Half', color='lightcoral', edgecolor='black')
ax2.set_title('Product Sales Comparison', fontsize=16, fontweight='bold')
ax2.set_xlabel('Product Type', fontsize=12)
ax2.set_ylabel('Sales (thousand units)', fontsize=12)
ax2.set_xticks(x_pos)
ax2.set_xticklabels(categories)
ax2.legend()
ax2.grid(True, alpha=0.3, axis='y')
ax2.set_facecolor('#fff5ee')
ax3 = plt.subplot(2, 2, 3)
labels = ['R&D', 'Marketing', 'Design', 'Service', 'Admin']
sizes = [30, 25, 20, 15, 10]
colors = ['#ff9999', '#66b3ff', '#99ff99', '#ffcc99', '#c2c2f0']
explode = (0.05, 0, 0, 0, 0)
wedges, texts, autotexts = ax3.pie(sizes, explode=explode, labels=labels, colors=colors, autopct='%1.1f%%', shadow=True, startangle=90, textprops={'fontsize': 11})
ax3.set_title('Department Budget Distribution', fontsize=16, fontweight='bold')
for autotext in autotexts:
autotext.set_color('white')
autotext.set_fontweight('bold')
ax3.set_facecolor('#f8f8ff')
ax4 = plt.subplot(2, 2, 4)
data = np.random.randn(1000)
n, bins, patches = ax4.hist(data, bins=30, density=True, alpha=0.7, color='green', edgecolor='black')
x_range = np.linspace(-4, 4, 100)
pdf = norm.pdf(x_range, loc=data.mean(), scale=data.std())
ax4.plot(x_range, pdf, 'r-', linewidth=2, label='Normal Distribution Fit')
ax4.set_title('Normal Distribution Histogram', fontsize=16, fontweight='bold')
ax4.set_xlabel('Value', fontsize=12)
ax4.set_ylabel('Frequency', fontsize=12)
ax4.grid(True, alpha=0.3)
ax4.legend()
ax4.set_facecolor('#f0f0f0')
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
fig.patch.set_facecolor('white')
buf = io.BytesIO()
plt.savefig(buf, format='png', dpi=150, bbox_inches='tight', facecolor=fig.get_facecolor())
buf.seek(0)
img_str = base64.b64encode(buf.read()).decode('utf-8')
plt.close(fig)
plot_div = document.getElementById("plotContainer")
plot_div.innerHTML = f'''<div><h3>✅ 多图对比展示生成成功</h3><img src="data:image/png;base64,{img_str}" alt="多图对比展示"></div>'''
info_div = document.getElementById("chartInfo")
info_div.innerHTML = f'''<h3><span>📋</span> 多图表综合信息</h3><div><div><strong>图表总数:</strong><br>4 个不同类型图表</div><div><strong>图表类型:</strong><br>线图、柱状图、饼图、直方图</div><div><strong>数据总量:</strong><br>约 1500+ 个数据点</div><div><strong>图表尺寸:</strong><br>16×12 英寸,150 DPI</div><div><strong>技术特点:</strong><br>使用 scipy 进行统计分析</div><div><strong>生成技术:</strong><br>Pyodide + Matplotlib</div></div>'''
`);
updateStatus('✅ 多图对比展示生成成功!','success');
disableChartButtons(false);
}catch(error){
updateStatus(`❌ 生成图表失败:${error.message}`,'error');
console.error("图表错误:", error);
disableChartButtons(false);
}
}
function updateStatus(message, type){
const statusEl = document.getElementById('status');
statusEl.innerHTML = message;
statusEl.className = type;
}
function disableAllButtons(disabled){
['btnInit','btnChart1','btnChart2','btnChart3','btnChart4'].forEach(id=>{
const btn = document.getElementById(id);
if(btn){ btn.disabled = disabled;}
});
}
function disableChartButtons(disabled){
['btnChart1','btnChart2','btnChart3','btnChart4'].forEach(id=>{
const btn = document.getElementById(id);
if(btn){ btn.disabled = disabled;}
});
}
function enableChartButtons(){
['btnChart1','btnChart2','btnChart3','btnChart4'].forEach(id=>{
const btn = document.getElementById(id);
if(btn){ btn.disabled =false;}
});
}
function checkInitialized(){
if(!isInitialized){alert('请先点击'初始化环境'按钮!');return false;}
return true;
}
</script>
</body>
</html>
相关免费在线工具
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- 随机西班牙地址生成器
随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online