Python 数学可视化:显函数、隐函数及复杂曲线交互绘图
基于 Python Tkinter 和 Matplotlib 构建的函数可视化工具,支持显函数、隐函数、心形线及电势分布的交互式绘图。通过正则表达式和白名单机制实现安全表达式解析,提供预设函数选择、动态控件更新、图像保存及 LaTeX 公式渲染功能。适用于数学教学、工程仿真及科学研究领域,帮助用户直观理解数学表达式与图形关系。

基于 Python Tkinter 和 Matplotlib 构建的函数可视化工具,支持显函数、隐函数、心形线及电势分布的交互式绘图。通过正则表达式和白名单机制实现安全表达式解析,提供预设函数选择、动态控件更新、图像保存及 LaTeX 公式渲染功能。适用于数学教学、工程仿真及科学研究领域,帮助用户直观理解数学表达式与图形关系。

在科学计算和数据分析中,函数与方程的可视化是理解数学关系和物理现象的重要工具。本文基于 Python 的 Tkinter 和 Matplotlib 库,实现一个功能完善的函数与方程可视化工具,支持显函数、隐函数、特殊曲线(如心形线)及物理场分布(如电势)的交互式绘图,并提供安全的表达式解析、图像保存等功能。
np.linspace 生成采样点,安全计算函数值contour 绘制等值线def is_valid_expression(expr):
"""验证表达式安全性"""
allowed_chars = set("0123456789.+-*/()xy^np_sin_cos_tan_exp_sqrt_log_pi_ ")
invalid_chars = set(expr.replace('.', '').replace('_', '')) - allowed_chars
if invalid_chars:
raise ValueError(f"非法字符:{''.join(invalid_chars)}")
# 括号匹配检查
stack = []
for char in expr:
if char == '(':
stack.append(char)
elif char == ')':
if not stack:
raise ValueError("括号不匹配")
stack.pop()
if stack:
raise ValueError("括号不匹配")
return True
def safe_eval(expr, namespace):
"""安全执行表达式"""
expr = expr.replace('^', '**') # 替换幂运算符
allowed_funcs = {
'np': np, 'sin': np.sin, 'cos': np.cos, 'tan': np.tan,
'exp': np.exp, 'sqrt': np.sqrt, 'log': np.log, 'pi': np.pi
}
safe_globals = {"__builtins__": None}
safe_locals = {**allowed_funcs, **namespace}
compiled_code = compile(expr, '<string>', 'eval')
return eval(compiled_code, safe_globals, safe_locals)
def plot_explicit_function(self, f, x_range, title):
"""绘制显函数"""
self.fig.clear()
ax = self.fig.add_subplot(111)
ax.set_facecolor('white')
x = np.linspace(x_range[0], x_range[1], 1000)
y = np.array([f(xi) for xi in x])
# 逐点计算防止数组错误
ax.plot(x, y, 'b-', linewidth=2.5)
ax.set_title(title)
ax.grid(True, linestyle='--', alpha=0.6)
self.optimize_ticks(ax, x_range, (y.min(), y.max()))
# 预设函数定义
self.explicit_presets = {
"三次函数": {
"func": lambda x: x**3 - 3*x,
"expr": "x**3 - 3*x",
"x_range": (-2.5, 2.5),
"title": "三次函数:$y = x^3 - 3x$",
}
}
plot_explicit("1/x", x_range=(-5, 5)) # 输入表达式直接绘制
def plot_implicit_equation(self, eq, x_range, y_range):
"""绘制隐函数 F(x,y)=0"""
x = np.linspace(x_range[0], x_range[1], 500)
y = np.linspace(y_range[0], y_range[1], 500)
X, Y = np.meshgrid(x, y)
Z = eq(X, Y)
self.fig.contour(X, Y, Z, levels=[0], colors='red', linewidths=2.5)
self.fig.contourf(X, Y, Z, alpha=0.6) # 填充色显示数值分布
self.fig.colorbar(label='F(x,y)')
# 预设隐函数
self.implicit_presets["圆"] = {
"eq": lambda x, y: x**2 + y**2 - 4,
"title": "圆:$x^2 + y^2 = 4$",
}
plot_implicit("x**3 + y**3 - 3*x*y", x_range=(-3, 3), y_range=(-3, 3))
def plot_heart_curve(self):
"""笛卡尔心形线"""
eq = lambda x, y: (x**2 + y**2 - 1)**3 - x**2 * y**3
self.plot_implicit_equation(eq, x_range=(-1.5, 1.5), y_range=(-1.5, 1.5))
self.fig.contourf(..., colors='pink', alpha=0.4) # 填充爱心区域
def plot_electric_potential(self):
"""点电荷电势分布"""
charges = [
{"x": -1, "y": 0, "q": 1},
{"x": 1, "y": 0, "q": -1}
]
x = np.linspace(-2.5, 2.5, 500)
y = np.linspace(-2, 2, 500)
X, Y = np.meshgrid(x, y)
V = sum(
charge['q'] / np.sqrt((X - c['x'])**2 + (Y - c['y'])**2)
for c in charges
)
self.fig.contourf(X, Y, V, cmap='coolwarm') # 温度映射显示电势
self.fig.scatter([c['x']], [c['y']], s=300, c=['red', 'blue'], marker='+_')
def __init__(self, root):
self.root = root
self.root.geometry("1200x800")
# 左侧控制面板
left_frame = ttk.LabelFrame(root, text="可视化选项")
ttk.Radiobutton(left_frame, text="显函数", variable=self.viz_type, value="explicit")
ttk.Radiobutton(left_frame, text="隐函数", variable=self.viz_type, value="implicit")
ttk.Radiobutton(left_frame, text="心形线", variable=self.viz_type, value="heart")
# 右侧绘图区域
self.canvas = FigureCanvasTkAgg(self.fig, master=right_frame)
self.toolbar = NavigationToolbar2Tk(self.canvas, toolbar_frame)
# 集成缩放工具
def update_controls(self):
"""根据选择类型显示对应控件"""
if self.viz_type.get() == "explicit":
self.explicit_frame.pack()
self.update_preset_options(self.explicit_presets.keys())
elif self.viz_type.get() == "implicit":
self.implicit_frame.pack()
self.update_preset_options(self.implicit_presets.keys())
# 隐藏其他面板
def save_image(self):
filename = simpledialog.askstring("保存", "文件名")
if filename:
self.fig.savefig(f"{filename}.png", dpi=150, bbox_inches="tight")
messagebox.showinfo("成功", f"保存至:{os.path.abspath(filename)}")
def get_function_label(self, expr):
"""生成 LaTeX 公式"""
expr = expr.replace('np.sin', '\\sin').replace('**', '^')
expr = re.sub(r'(\d)/(\d)', r'\\frac{\1}{\2}', expr) # 自动转换分数
return f"${expr}$"
本文实现的函数可视化工具具备以下特点:
该工具可广泛应用于数学教学、工程仿真、科学研究等领域,帮助用户快速建立数学表达式与图形之间的直观联系。
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.patches as patches
from matplotlib import ticker
from matplotlib.colors import ListedColormap
import re
import os
# 设置 matplotlib 支持中文显示
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC", "Arial Unicode MS"]
plt.rcParams["axes.unicode_minus"] = False # 解决负号显示问题
class FunctionVisualizer:
def __init__(self, root):
self.root = root
self.root.title("函数与方程可视化工具")
self.root.geometry("1200x800")
self.root.minsize(1000, 700)
# 设置主题颜色
self.bg_color = "#f5f5f5"
self.frame_color = "#ffffff"
self.button_color = "#3b82f6"
self.button_text_color = "#ffffff"
# 预设函数分组(显函数/隐函数)
self.explicit_presets = {
"三次函数": {
"func": lambda x: x**3 - 3*x,
"expr": "x**3 - 3*x",
"x_range": (-2.5, 2.5),
"title": "三次函数:$y = x^3 - 3x$",
},
"双曲线": {
"func": lambda x: 1/x,
"expr": "1/x",
"x_range": (-5, 5),
"title": "双曲线:$y = \\frac{1}{x}$",
},
"指数函数": {
"func": lambda x: np.exp(x),
"expr": "np.exp(x)",
"x_range": (-3, 3),
"title": "指数函数:$y = e^x$",
},
}
self.implicit_presets = {
"圆": {
"eq": lambda x, y: x**2 + y**2 - 4,
"expr": "x**2 + y**2 - 4",
"x_range": (-3, 3),
"y_range": (-3, 3),
"title": "圆:$x^2 + y^2 = 4$",
},
"椭圆": {
"eq": lambda x, y: x**2/4 + y**2/9 - 1,
"expr": "x**2/4 + y**2/9 - 1",
"x_range": (-3, 3),
"y_range": (-4, 4),
"title": "椭圆:$\\frac{x^2}{4} + \\frac{y^2}{9} = 1$",
},
"双曲线 (隐式)": {
"eq": lambda x, y: x**2 - y**2 - 1,
"expr": "x**2 - y**2 - 1",
"x_range": (-3, 3),
"y_range": (-3, 3),
"title": "双曲线:$x^2 - y^2 = 1$",
},
"笛卡尔叶形线": {
"eq": lambda x, y: x**3 + y**3 - 3*x*y,
"expr": "x**3 + y**3 - 3*x*y",
"x_range": (-3, 3),
"y_range": (-3, 3),
"title": "笛卡尔叶形线:$x^3 + y^3 = 3xy$",
},
}
# 创建主框架
main_frame = ttk.Frame(self.root, padding=10)
main_frame.pack(fill=tk.BOTH, expand=True)
# 创建左侧控制面板
left_frame = ttk.LabelFrame(main_frame, text="函数与方程可视化选项", padding=10, width=375)
left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
left_frame.pack_propagate(False) # 固定宽度
# 创建右侧绘图区域
right_frame = ttk.Frame(main_frame)
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
# 创建绘图区域和工具栏容器
self.plot_frame = ttk.Frame(right_frame)
self.plot_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 初始化绘图区域
self.fig = Figure(figsize=(8, 6), dpi=100)
self.canvas = FigureCanvasTkAgg(self.fig, master=self.plot_frame)
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# 添加工具栏
self.toolbar_frame = ttk.Frame(right_frame, height=40)
self.toolbar_frame.pack(fill=tk.X, padx=5, pady=(0, 5))
self.toolbar = NavigationToolbar2Tk(self.canvas, self.toolbar_frame)
self.toolbar.update()
# 添加控制选项
self.create_controls(left_frame)
# 初始显示
self.plot_predefined_function()
def create_controls(self, parent):
"""创建控制选项"""
ttk.Label(parent, text="选择可视化类型:", font=("SimHei", 10, "bold")).pack(anchor=tk.W, pady=(0, 10))
# 可视化类型选择
self.viz_type = tk.StringVar(value="explicit")
types = [("显函数", "explicit"), ("隐函数", "implicit"), ("心形线", "heart"), ("电势分布", "potential")]
for text, value in types:
ttk.Radiobutton(parent, text=text, variable=self.viz_type, value=value, command=self.update_controls).pack(anchor=tk.W, padx=5, pady=2)
# 预设函数下拉菜单(动态更新选项)
self.preset_frame = ttk.LabelFrame(parent, text="预设函数", padding=10)
self.preset_frame.pack(fill=tk.X, pady=10)
# 动态选项变量
self.preset_functions = tk.StringVar()
self.preset_combobox = ttk.Combobox(self.preset_frame, textvariable=self.preset_functions, width=30)
self.preset_combobox.pack(fill=tk.X, pady=5)
ttk.Button(self.preset_frame, text="绘制预设函数", command=self.plot_predefined_function).pack(fill=tk.X, pady=5)
# 显函数输入
self.explicit_frame = ttk.LabelFrame(parent, text="显函数输入", padding=10)
self.explicit_frame.pack(fill=tk.X, pady=10)
ttk.Label(self.explicit_frame, text="函数表达式 (例如 x**2):").pack(anchor=tk.W)
self.explicit_entry = ttk.Entry(self.explicit_frame, width=30)
self.explicit_entry.insert(0, "x**3 - 3*x")
self.explicit_entry.pack(fill=tk.X, pady=5)
ttk.Label(self.explicit_frame, text="X 范围 (min,max):").pack(anchor=tk.W)
self.x_range_entry = ttk.Entry(self.explicit_frame, width=30)
self.x_range_entry.insert(0, "-2.5,2.5")
self.x_range_entry.pack(fill=tk.X, pady=5)
ttk.Button(self.explicit_frame, text="绘制显函数", command=self.plot_explicit).pack(fill=tk.X, pady=5)
# 隐函数输入
self.implicit_frame = ttk.LabelFrame(parent, text="隐函数输入", padding=10)
self.implicit_frame.pack(fill=tk.X, pady=10)
ttk.Label(self.implicit_frame, text="方程表达式 (例如 x**2 + y**2 - 4):").pack(anchor=tk.W)
self.implicit_entry = ttk.Entry(self.implicit_frame, width=30)
self.implicit_entry.insert(0, "x**3 + y**3 - 3*x*y")
self.implicit_entry.pack(fill=tk.X, pady=5)
ttk.Label(self.implicit_frame, text="X 范围 (min,max):").pack(anchor=tk.W)
self.implicit_x_range_entry = ttk.Entry(self.implicit_frame, width=30)
self.implicit_x_range_entry.insert(0, "-3,3")
self.implicit_x_range_entry.pack(fill=tk.X, pady=5)
ttk.Label(self.implicit_frame, text="Y 范围 (min,max):").pack(anchor=tk.W)
self.implicit_y_range_entry = ttk.Entry(self.implicit_frame, width=30)
self.implicit_y_range_entry.insert(0, "-3,3")
self.implicit_y_range_entry.pack(fill=tk.X, pady=5)
ttk.Button(self.implicit_frame, text="绘制隐函数", command=self.plot_implicit).pack(fill=tk.X, pady=5)
# 保存图像按钮
ttk.Button(parent, text="保存图像", command=self.save_image).pack(side=tk.BOTTOM, pady=10)
# 初始更新控件状态
self.update_controls()
def update_controls(self):
"""更新控件状态"""
viz_type = self.viz_type.get()
# 隐藏所有输入面板
self.preset_frame.pack_forget()
self.explicit_frame.pack_forget()
self.implicit_frame.pack_forget()
# 显示对应面板
if viz_type == "explicit":
self.explicit_frame.pack(fill=tk.X, pady=10)
self.update_preset_options(self.explicit_presets.keys()) # 显函数预设
elif viz_type == "implicit":
self.implicit_frame.pack(fill=tk.X, pady=10)
self.update_preset_options(self.implicit_presets.keys()) # 隐函数预设
elif viz_type == "heart":
self.plot_heart_curve()
elif viz_type == "potential":
self.plot_electric_potential()
# 显示预设框架
self.preset_frame.pack(fill=tk.X, pady=10)
def update_preset_options(self, options=None):
"""动态更新预设函数选项"""
if options is None:
options = []
self.preset_combobox["values"] = list(options)
if options:
self.preset_functions.set(list(options)[0]) # 默认选择第一个
def plot_predefined_function(self):
"""绘制预设函数"""
viz_type = self.viz_type.get()
selected = self.preset_functions.get()
self.fig.clear()
ax = self.fig.add_subplot(111)
ax.set_facecolor("white")
self.fig.set_facecolor("white")
if viz_type == "explicit" and selected in self.explicit_presets:
data = self.explicit_presets[selected]
self.plot_explicit_function(f=data["func"], x_range=data["x_range"], title=data["title"])
# 更新显函数输入框
self.explicit_entry.delete(0, tk.END)
self.explicit_entry.insert(0, data["expr"])
self.x_range_entry.delete(0, tk.END)
self.x_range_entry.insert(0, f"{data['x_range'][0]},{data['x_range'][1]}")
elif viz_type == "implicit" and selected in self.implicit_presets:
data = self.implicit_presets[selected]
self.plot_implicit_equation(eq=data["eq"], x_range=data["x_range"], y_range=data["y_range"], title=data["title"])
# 更新隐函数输入框
self.implicit_entry.delete(0, tk.END)
self.implicit_entry.insert(0, data["expr"])
self.implicit_x_range_entry.delete(0, tk.END)
self.implicit_x_range_entry.insert(0, f"{data['x_range'][0]},{data['x_range'][1]}")
self.implicit_y_range_entry.delete(0, tk.END)
self.implicit_y_range_entry.insert(0, f"{data['y_range'][0]},{data['y_range'][1]}")
self.canvas.draw()
def is_valid_expression(self, expr):
"""验证表达式是否为有效的数学表达式"""
# 允许的字符:数字、运算符、函数名、xy 变量、小数点、括号、空格
allowed_chars = set("0123456789.+-*/()xy^np_sin_cos_tan_exp_sqrt_log_pi_ ")
# 移除所有允许的字符,检查是否还有剩余
cleaned = expr.replace('.', '').replace('_', '')
invalid_chars = set(cleaned) - allowed_chars
if invalid_chars:
raise ValueError(f"非法字符:{''.join(invalid_chars)}")
# 检查括号匹配
stack = []
for char in expr:
if char == '(':
stack.append(char)
elif char == ')':
if not stack:
raise ValueError("括号不匹配:缺少左括号")
stack.pop()
if stack:
raise ValueError("括号不匹配:缺少右括号")
return True
def safe_eval(self, expr, namespace):
"""安全地执行表达式计算"""
try:
self.is_valid_expression(expr)
# 替换常见函数别名
expr = expr.replace('^', '**') # 替换^为**
# 白名单函数和变量
allowed_funcs = {
'np': np, 'sin': np.sin, 'cos': np.cos, 'tan': np.tan,
'exp': np.exp, 'sqrt': np.sqrt, 'log': np.log, 'pi': np.pi, 'arctan2': np.arctan2
}
# 创建安全命名空间
safe_globals = {"__builtins__": None}
safe_locals = {**allowed_funcs, **namespace}
# 使用编译后的代码提高安全性
compiled_code = compile(expr, '<string>', 'eval')
return eval(compiled_code, safe_globals, safe_locals)
except Exception as e:
raise ValueError(f"表达式错误:{str(e)}")
def plot_explicit(self):
"""绘制用户输入的显函数"""
try:
func_str = self.explicit_entry.get().strip()
x_range_str = self.x_range_entry.get().strip()
if not func_str or not x_range_str:
raise ValueError("请输入函数表达式和 X 范围")
# 解析 x 范围
x_min, x_max = map(float, x_range_str.split(","))
if x_min >= x_max:
raise ValueError("X 范围的最小值必须小于最大值")
# 生成 x 值
x_vals = np.linspace(x_min, x_max, 1000)
# 安全计算 y 值(逐个点计算,避免数组错误)
y_vals = np.zeros_like(x_vals)
for i, x in enumerate(x_vals):
y_vals[i] = self.safe_eval(func_str, {'x': x})
# 绘制函数
self.plot_explicit_function(
f=lambda x: y_vals, x_range=(x_min, x_max),
title=f"显函数:$y = {self.get_function_label(func_str)}$",
)
self.canvas.draw()
except Exception as e:
messagebox.showerror("错误", f"绘制显函数时出错:{str(e)}")
def plot_implicit(self):
"""绘制用户输入的隐函数(修复网格点数不匹配问题)"""
try:
eq_str = self.implicit_entry.get().strip()
x_range_str = self.implicit_x_range_entry.get().strip()
y_range_str = self.implicit_y_range_entry.get().strip()
if not eq_str or not x_range_str or not y_range_str:
raise ValueError("请输入完整的方程表达式和范围")
# 解析范围
x_min, x_max = map(float, x_range_str.split(","))
y_min, y_max = map(float, y_range_str.split(","))
if x_min >= x_max or y_min >= y_max:
raise ValueError("范围的最小值必须小于最大值")
# 创建向量化的方程函数(直接处理数组输入)
eq = lambda X, Y: self.safe_eval(eq_str, {'x': X, 'y': Y})
# 调用隐函数绘图函数,使用默认分辨率 500(与函数内部一致)
self.plot_implicit_equation(
eq=eq, x_range=(x_min, x_max), y_range=(y_min, y_max),
title=f"隐函数:${self.get_function_label(eq_str)} = 0$",
)
self.canvas.draw()
except Exception as e:
messagebox.showerror("错误", f"绘制隐函数时出错:{str(e)}")
def plot_explicit_function(self, f, x_range=(-5, 5), title="显函数图像"):
"""绘制显函数 y = f(x) 的图像"""
self.fig.clear()
ax = self.fig.add_subplot(111)
# 设置背景为白色
ax.set_facecolor("white")
self.fig.set_facecolor("white")
# 创建网格和样式
ax.grid(True, linestyle="--", alpha=0.6)
ax.spines["left"].set_position("zero")
ax.spines["bottom"].set_position("zero")
ax.spines["right"].set_visible(False)
ax.spines["top"].set_visible(False)
# 生成数据
x = np.linspace(x_range[0], x_range[1], 1000)
try:
y = f(x)
except Exception as e:
messagebox.showerror("函数错误", f"计算函数值时出错:{str(e)}")
return
# 绘制函数曲线
ax.plot(x, y, "b-", linewidth=2.5)
# 设置标题和标签
ax.set_title(title, fontsize=16, pad=20)
ax.set_xlabel("x", fontsize=12, labelpad=-10, x=1.02)
ax.set_ylabel("y", fontsize=12, labelpad=-20, y=1.02, rotation=0)
# 优化坐标轴刻度
self.optimize_ticks(ax, x_range, (np.min(y), np.max(y)))
self.fig.tight_layout()
def plot_implicit_equation(self, eq, x_range=(-3, 3), y_range=(-3, 3), resolution=500, levels=[0], cmap="viridis", title="隐函数图像"):
"""绘制隐函数 F(x, y) = 0 的图像"""
self.fig.clear()
ax = self.fig.add_subplot(111)
# 设置背景为白色
ax.set_facecolor("white")
self.fig.set_facecolor("white")
# 创建网格
x = np.linspace(x_range[0], x_range[1], resolution)
y = np.linspace(y_range[0], y_range[1], resolution)
X, Y = np.meshgrid(x, y)
# 计算方程值
try:
Z = eq(X, Y)
except Exception as e:
messagebox.showerror("方程错误", f"计算方程值时出错:{str(e)}")
return
# 绘制等高线 (隐函数曲线)
contour = ax.contour(X, Y, Z, levels=levels, colors="red", linewidths=2.5)
# 添加填充色显示方程值的变化 (只在需要时)
if len(levels) > 1:
ax.contourf(X, Y, Z, levels=np.linspace(Z.min(), Z.max(), 100), cmap=cmap, alpha=0.6)
# 添加颜色条
cbar = self.fig.colorbar(contour)
cbar.set_label("F(x, y)", rotation=270, labelpad=20)
# 设置网格和样式
ax.grid(True, linestyle="--", alpha=0.4)
ax.set_aspect("equal")
# 设置标题和标签
ax.set_title(title, fontsize=16, pad=20)
ax.set_xlabel("x", fontsize=12)
ax.set_ylabel("y", fontsize=12)
# 添加零线
ax.axhline(0, color="black", linewidth=0.8, alpha=0.7)
ax.axvline(0, color="black", linewidth=0.8, alpha=0.7)
# 优化坐标轴刻度
self.optimize_ticks(ax, x_range, y_range)
self.fig.tight_layout()
def optimize_ticks(self, ax, x_range, y_range):
"""优化坐标轴刻度,避免刻度过于密集"""
x_min, x_max = x_range
y_min, y_max = y_range
# 根据数据范围自动设置刻度
x_span = x_max - x_min
y_span = y_max - y_min
# 设置合理的刻度间隔
x_major_locator = ticker.MaxNLocator(nbins=7)
y_major_locator = ticker.MaxNLocator(nbins=7)
ax.xaxis.set_major_locator(x_major_locator)
ax.yaxis.set_major_locator(y_major_locator)
def plot_heart_curve(self):
"""绘制心形线"""
self.fig.clear()
# 创建图像和子图
ax1 = self.fig.add_subplot(111)
ax1.set_aspect("equal")
ax1.set_title("心形线:$(x^2+y^2-1)^3 - x^2y^3 = 0$", fontsize=14)
# 设置背景为白色
ax1.set_facecolor("white")
self.fig.set_facecolor("white")
# 定义心形线方程
def heart_eq(x, y):
return (x**2 + y**2 - 1)**3 - x**2 * y**3
# 生成网格
x = np.linspace(-1.5, 1.5, 500)
y = np.linspace(-1.5, 1.5, 500)
X, Y = np.meshgrid(x, y)
Z = heart_eq(X, Y)
# 绘制心形线
contour = ax1.contour(X, Y, Z, levels=[0], colors="red", linewidths=3)
# 填充颜色
ax1.contourf(X, Y, Z, levels=[-1000, 0], colors=["pink"], alpha=0.4)
# 添加网格和样式
ax1.grid(True, linestyle="--", alpha=0.3)
ax1.set_xlim(-1.5, 1.5)
ax1.set_ylim(-1.5, 1.5)
# 优化坐标轴刻度
self.optimize_ticks(ax1, (-1.5, 1.5), (-1.5, 1.5))
self.fig.tight_layout()
self.canvas.draw()
def plot_electric_potential(self):
"""可视化点电荷系统的电势分布"""
self.fig.clear()
ax = self.fig.add_subplot(111)
# 设置背景为白色
ax.set_facecolor("white")
self.fig.set_facecolor("white")
# 定义两个点电荷的位置和电荷量
charges = [
{"x": -1, "y": 0, "q": 1}, # 正电荷
{"x": 1, "y": 0, "q": -1}, # 负电荷
]
# 创建网格
x = np.linspace(-2.5, 2.5, 500)
y = np.linspace(-2, 2, 500)
X, Y = np.meshgrid(x, y)
# 计算电势 (k=1)
V = np.zeros_like(X)
for charge in charges:
r = np.sqrt((X - charge["x"])**2 + (Y - charge["y"])**2)
V += charge["q"] / r # 避免除以零
V = np.nan_to_num(V, posinf=10, neginf=-10)
# 绘制电势等高线 (使用 contourf 创建填充等高线)
levels = np.linspace(-10, 10, 21)
contourf = ax.contourf(X, Y, V, levels=levels, cmap="coolwarm", alpha=0.8)
contour = ax.contour(X, Y, V, levels=levels, colors="k", linewidths=0.5)
ax.clabel(contour, inline=True, fontsize=8)
# 绘制电荷位置
for charge in charges:
color = "red" if charge["q"] > 0 else "blue"
marker = "+" if charge["q"] > 0 else "_"
ax.scatter(charge["x"], charge["y"], s=300, c=color, marker=marker, linewidths=2)
ax.text(charge["x"], charge["y"]+0.2, f"{charge['q']}q", ha="center", fontsize=12, weight="bold")
# 设置标题和标签
ax.set_title("两个点电荷的电势分布", fontsize=16, pad=20)
ax.set_xlabel("x (m)", fontsize=12)
ax.set_ylabel("y (m)", fontsize=12)
# 添加网格和样式
ax.set_aspect("equal")
ax.grid(True, linestyle="--", alpha=0.4)
# 添加坐标轴
ax.axhline(0, color="k", linewidth=0.8, alpha=0.7)
ax.axvline(0, color="k", linewidth=0.8, alpha=0.7)
# 添加物理公式
ax.text(1.5, 1.8, r"$V = \sum \frac{kq_i}{r_i}$", fontsize=14, bbox=dict(facecolor="white", alpha=0.8))
# 添加颜色条
cbar = self.fig.colorbar(contourf, label="电势 (V)")
# 优化坐标轴刻度
self.optimize_ticks(ax, (-2.5, 2.5), (-2, 2))
self.fig.tight_layout()
self.canvas.draw()
def get_function_label(self, func_str):
"""生成函数的 LaTeX 标签"""
# 安全检查,防止恶意代码
if any(word in func_str.lower() for word in ["import", "os", "sys", "subprocess"]):
raise ValueError("检测到不安全的代码")
# 直接使用原始字符串,不再进行转义
safe_str = func_str
# 替换常见的数学函数
replacements = {
r'np\.sin\(([^)]+)\)': r'\sin(\1)',
r'np\.cos\(([^)]+)\)': r'\cos(\1)',
r'np\.tan\(([^)]+)\)': r'\tan(\1)',
r'np\.exp\(([^)]+)\)': r'\exp(\1)',
r'np\.sqrt\(([^)]+)\)': r'\sqrt{\1}',
r'np\.log\(([^)]+)\)': r'\ln(\1)',
r'np\.pi': r'\pi',
r'\*\*': r'^',
r'\*': r'\cdot',
}
# 应用所有替换,捕获可能的正则表达式错误
for pattern, replacement in replacements.items():
try:
safe_str = re.sub(pattern, replacement, safe_str)
except re.error as e:
continue # 跳过有问题的替换
# 处理分数 - 更稳健的方法
if '/' in safe_str:
# 只替换不包含字母的分数表达式
if re.search(r'\d+\.?\d*/\d+\.?\d*', safe_str):
parts = safe_str.split('/')
if len(parts) == 2:
numerator = parts[0].strip()
denominator = parts[1].strip()
safe_str = r'\frac{' + numerator + '}' + r'{'+ denominator +'}'
return safe_str
def save_image(self):
"""保存当前图像"""
try:
filename = simpledialog.askstring("保存图像", "请输入文件名:", initialvalue="function_plot.png")
if filename:
if not filename.endswith(".png"):
filename += ".png"
self.fig.savefig(filename, dpi=150, bbox_inches="tight")
messagebox.showinfo("成功", f"图像已保存至:{os.path.abspath(filename)}")
except Exception as e:
messagebox.showerror("保存错误", f"保存图像时出错:{e}")
def main():
root = tk.Tk()
# 设置样式
style = ttk.Style()
style.configure("TFrame", background="#f5f5f5")
style.configure("TLabelframe", background="#ffffff", relief="sunken")
style.configure("TLabelframe.Label", background="#ffffff", font=("SimHei", 10, "bold"))
style.configure("TButton", padding=5)
# 尝试设置中文字体
try:
plt.rcParams["font.family"] = ["SimHei"]
except:
try:
plt.rcParams["font.family"] = ["WenQuanYi Micro Hei"]
except:
try:
plt.rcParams["font.family"] = ["Heiti TC"]
except:
try:
plt.rcParams["font.family"] = ["Arial Unicode MS"]
except:
plt.rcParams["font.family"] = ["DejaVu Sans", "sans-serif"]
print("警告:未找到中文字体,图表文字可能无法正确显示")
app = FunctionVisualizer(root)
root.mainloop()
if __name__ == "__main__":
main()

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online