Python数学可视化——坐标系与变换

在这里插入图片描述


一、引言

数学可视化是理解抽象概念的强大工具,特别是在学习坐标系、坐标转换和几何变换等基础概念时。本文将介绍一个基于Python的数学可视化工具,它能够直观地展示直角坐标系与极坐标系的对比、坐标转换过程、几何变换效果以及行星轨道模拟。这个工具不仅有助于学生理解数学概念,也为教师提供了一个实用的教学辅助工具。

二、工具功能概览

这个可视化工具主要包含四个核心功能模块:

  • 坐标系对比:展示直角坐标系和极坐标系的基本结构
  • 坐标转换:演示点在直角坐标和极坐标之间的转换关系
  • 几何变换:展示平移、旋转和缩放等基本几何变换
  • 行星轨道:模拟椭圆轨道的形状及其参数影响

下面我们将深入探讨每个功能模块的实现和参数设置。

三、代码实现解析

1. 环境准备与基本设置

首先需要导入必要的库,包括Tkinter用于创建图形界面,Matplotlib用于绘图,以及NumPy用于数值计算:

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 math
import os
from matplotlib.projections import PolarAxes
import matplotlib.transforms as transforms
from matplotlib.patches import Circle, Ellipse

# 设置matplotlib支持中文显示
plt.rcParams["font.family"] = [
    "SimHei",
    "WenQuanYi Micro Hei",
    "Heiti TC",
    "Arial Unicode MS",
]
plt.rcParams["axes.unicode_minus"] = False  # 解决负号显示问题

2. 坐标系对比

这部分功能展示了直角坐标系和极坐标系的基本结构,帮助用户理解两种坐标系的差异:

def plot_coordinate_systems(self):
    """可视化直角坐标系与极坐标系对比"""
    self.fig.clear()
    self.fig.suptitle("直角坐标系与极坐标系对比", fontsize=14)

    # 直角坐标系
    ax1 = self.fig.add_subplot(121)
    ax1.set_aspect("equal")
    ax1.grid(True, linestyle="--", alpha=0.7)
    ax1.set_xlim(-5, 5)
    ax1.set_ylim(-5, 5)
    ax1.set_title("直角坐标系", fontsize=12)
    ax1.set_xlabel("x", fontsize=10)
    ax1.set_ylabel("y", fontsize=10, rotation=0)

    # 添加网格点
    for x in range(-4, 5):
        for y in range(-4, 5):
            ax1.plot(x, y, "bo", markersize=4, alpha=0.5)

    # 极坐标系
    ax2 = self.fig.add_subplot(122, projection="polar")
    ax2.set_theta_zero_location("E")  # 0度方向朝右
    ax2.set_theta_direction(-1)  # 顺时针方向
    ax2.set_rlim(0, 5)
    ax2.set_title("极坐标系", fontsize=12)
    ax2.grid(True, linestyle="--", alpha=0.7)

    # 添加网格点
    for r in np.linspace(1, 4, 4):
        for theta in np.linspace(0, 2 * np.pi, 16, endpoint=False):
            ax2.plot(theta, r, "ro", markersize=4, alpha=0.5)

    self.fig.tight_layout(rect=[0, 0, 1, 0.96])
    self.canvas.draw()

在这个函数中,我们创建了两个子图:左侧是直角坐标系,右侧是极坐标系。每个坐标系上都添加了网格点,便于用户理解坐标系统的结构。

3. 坐标转换

坐标转换功能允许用户输入一个点的直角坐标(x, y),并实时显示其对应的极坐标(r, θ):

def plot_coordinate_conversion(self, point):
    """展示单个点的坐标转换过程"""
    self.fig.clear()
    # 使用完整的LaTeX表达式替代可能缺失的上标字符
    self.fig.suptitle(f"坐标转换: ({point[0]:.1f}, {point[1]:.1f})", fontsize=14)

    # 直角坐标系中的点
    ax1 = self.fig.add_subplot(121)
    ax1.set_aspect("equal")
    ax1.grid(True, linestyle="--", alpha=0.7)
    ax1.set_xlim(-6, 6)
    ax1.set_ylim(-6, 6)
    # 使用完整的LaTeX表达式替代可能缺失的上标字符
    ax1.set_title(f"直角坐标: $({point[0]:.1f}, {point[1]:.1f})$", fontsize=10)

    # 绘制点和坐标线
    ax1.plot([0, point[0]], [0, 0], "b--", alpha=0.5)
    ax1.plot([point[0], point[0]], [0, point[1]], "b--", alpha=0.5)
    ax1.plot(point[0], point[1], "ro", markersize=8)

    # 极坐标系中的点
    r = np.sqrt(point[0] ** 2 + point[1] ** 2)
    theta = np.arctan2(point[1], point[0])

    ax2 = self.fig.add_subplot(122, projection="polar")
    ax2.set_theta_zero_location("E")
    ax2.set_theta_direction(-1)
    ax2.set_rlim(0, 6)
    # 使用完整的LaTeX表达式替代可能缺失的上标字符
    ax2.set_title(f"极坐标: $(r={r:.2f}, \\theta={np.degrees(theta):.1f}^\\circ)$", fontsize=10)
    ax2.grid(True, linestyle="--", alpha=0.7)

    # 绘制点和坐标线
    ax2.plot([0, theta], [0, r], "r-", linewidth=2)
    ax2.plot(theta, r, "ro", markersize=8)

    # 使用完整的LaTeX表达式替代可能缺失的上标字符
    self.fig.text(
        0.5, 0.02, 
        f"转换公式: $r = \\sqrt{{x^2 + y^2}} = {r:.2f}, \\theta = \\arctan\\left(\\frac{{y}}{{x}}\\right) = {np.degrees(theta):.1f}^\\circ$", 
        ha="center", fontsize=11
    )

    self.fig.tight_layout(rect=[0, 0.05, 1, 0.96])
    self.canvas.draw()

这里使用了两个子图分别展示同一个点在直角坐标系和极坐标系中的表示。图中显示了点到原点的距离r和角度θ,并在底部给出了转换公式。

4. 几何变换

几何变换功能展示了平移、旋转和缩放三种基本变换对图形的影响:

def plot_geometric_transformations(self):
    """展示几何变换:平移、旋转、缩放"""
    self.fig.clear()
    self.fig.suptitle("几何变换可视化", fontsize=14)

    # 创建三角形
    triangle = np.array([[0, 0], [1, 0], [0.5, 1], [0, 0]])
    
    # 获取变换参数
    try:
        tx = float(self.tx_entry.get())
        ty = float(self.ty_entry.get())
        angle = float(self.rotation_entry.get())
        sx = float(self.sx_entry.get())
        sy = float(self.sy_entry.get())
    except ValueError:
        messagebox.showerror("输入错误", "请输入有效的变换参数")
        return

    # 原始形状
    ax1 = self.fig.add_subplot(221)
    ax1.set_title("原始形状", fontsize=10)
    ax1.plot(triangle[:, 0], triangle[:, 1], "bo-")
    ax1.set_xlim(-3, 5)
    ax1.set_ylim(-3, 5)
    ax1.grid(True, linestyle="--", alpha=0.7)
    ax1.set_aspect("equal")

    # 平移变换
    translated = triangle + [tx, ty]
    ax2 = self.fig.add_subplot(222)
    ax2.set_title(f"平移: $({tx}, {ty})$", fontsize=10)
    ax2.plot(triangle[:, 0], triangle[:, 1], "bo-", alpha=0.3)
    ax2.plot(translated[:, 0], translated[:, 1], "ro-")
    ax2.set_xlim(-3, 5)
    ax2.set_ylim(-3, 5)
    ax2.grid(True, linestyle="--", alpha=0.7)
    ax2.set_aspect("equal")

    # 旋转变换
    angle_rad = np.radians(angle)
    rotation_matrix = np.array(
        [[np.cos(angle_rad), -np.sin(angle_rad)], 
        [np.sin(angle_rad), np.cos(angle_rad)]]
    )
    rotated = np.dot(triangle, rotation_matrix.T)
    ax3 = self.fig.add_subplot(223)
    # 使用完整的LaTeX表达式替代可能缺失的上标字符
    ax3.set_title(f"旋转: ${angle}^\\circ$", fontsize=10)
    ax3.plot(triangle[:, 0], triangle[:, 1], "bo-", alpha=0.3)
    ax3.plot(rotated[:, 0], rotated[:, 1], "go-")
    ax3.set_xlim(-3, 5)
    ax3.set_ylim(-3, 5)
    ax3.grid(True, linestyle="--", alpha=0.7)
    ax3.set_aspect("equal")

    # 缩放变换
    scaled = triangle * [sx, sy]
    ax4 = self.fig.add_subplot(224)
    ax4.set_title(f"缩放: $({sx}, {sy})$", fontsize=10)
    ax4.plot(triangle[:, 0], triangle[:, 1], "bo-", alpha=0.3)
    ax4.plot(scaled[:, 0], scaled[:, 1], "mo-")
    ax4.set_xlim(-3, 5)
    ax4.set_ylim(-3, 5)
    ax4.grid(True, linestyle="--", alpha=0.7)
    ax4.set_aspect("equal")

    self.fig.tight_layout(rect=[0, 0, 1, 0.96])
    self.canvas.draw()

这个函数创建了一个2×2的子图布局,分别展示原始形状和三种变换后的形状。用户可以通过界面输入平移量(tx, ty)、旋转角度(angle)和缩放因子(sx, sy),实时观察变换效果。

5. 行星轨道模拟

行星轨道模拟功能基于开普勒定律,展示了椭圆轨道的特性:

def plot_planetary_orbit(self, a, e):
    """
    绘制行星轨道(椭圆)在极坐标系中的表示
    参数:
    a: 半长轴 (天文单位)
    e: 离心率 (0 < e < 1)
    """
    self.fig.clear()
    self.fig.suptitle(f"行星轨道 $(a={a}, e={e})$", fontsize=14)

    # 计算轨道参数
    b = a * np.sqrt(1 - e**2)  # 半短轴
    focal_distance = e * a  # 焦点间距

    # 直角坐标系视图
    ax1 = self.fig.add_subplot(121)
    ax1.set_aspect("equal")
    ax1.set_title("直角坐标系中的椭圆轨道", fontsize=10)
    ax1.set_xlabel("x (AU)")
    ax1.set_ylabel("y (AU)")

    # 绘制椭圆轨道
    theta = np.linspace(0, 2 * np.pi, 300)
    r = a * (1 - e**2) / (1 + e * np.cos(theta))
    x = r * np.cos(theta)
    y = r * np.sin(theta)

    ax1.plot(x, y, "b-", linewidth=1.8)

    # 标记焦点(太阳位置)
    ax1.plot(-focal_distance, 0, "yo", markersize=12)
    ax1.text(-focal_distance - 0.2, 0.2, "太阳", fontsize=9)

    # 添加网格和标记
    ax1.grid(True, linestyle="--", alpha=0.6)
    ax1.set_xlim(-a * 1.2, a * 1.2)
    ax1.set_ylim(-b * 1.2, b * 1.2)

    # 极坐标系视图
    ax2 = self.fig.add_subplot(122, projection="polar")
    ax2.set_theta_zero_location("E")
    ax2.set_theta_direction(-1)
    ax2.set_title("极坐标系中的椭圆轨道", fontsize=10, pad=20)

    # 绘制轨道
    ax2.plot(theta, r, "b-", linewidth=1.8)

    # 标记近日点和远日点
    perihelion_theta = 0
    aphelion_theta = np.pi
    perihelion_r = a * (1 - e)
    aphelion_r = a * (1 + e)

    ax2.plot(perihelion_theta, perihelion_r, "ro", markersize=8)
    ax2.plot(aphelion_theta, aphelion_r, "go", markersize=8)

    # 添加标签
    ax2.text(
        perihelion_theta,
        perihelion_r,
        " 近日点",
        verticalalignment="bottom",
        fontsize=9,
    )
    ax2.text(
        aphelion_theta, aphelion_r, " 远日点", verticalalignment="bottom", fontsize=9
    )

    # 添加轨道参数标注
    param_text = f"半长轴: ${a}$ AU\n离心率: ${e}$\n半短轴: ${b:.2f}$ AU"
    ax2.annotate(
        param_text,
        xy=(0.5, 0.95),
        xycoords="axes fraction",
        ha="center",
        va="top",
        fontsize=10,
        bbox=dict(boxstyle="round,pad=0.3", fc="white", alpha=0.8),
    )

    ax2.grid(True, linestyle="--", alpha=0.6)

    # 使用完整的LaTeX表达式替代可能缺失的上标字符
    self.fig.text(
        0.5, 0.02, 
        f"椭圆轨道方程: $r = \\frac{{a(1-e^2)}}{{1+e\\cdot\\cos\\theta}} = \\frac{{{a:.2f}\\times(1-{e:.3f}^2)}}{{1+{e:.3f}\\cdot\\cos\\theta}}$", 
        ha="center", fontsize=11
    )

    self.fig.tight_layout(rect=[0, 0.05, 1, 0.96])
    self.canvas.draw()

这个函数在两个子图中分别展示了椭圆轨道在直角坐标系和极坐标系中的表示。用户可以调整半长轴(a)和离心率(e)两个参数,观察轨道形状的变化。同时,图中标记了太阳的位置(椭圆的一个焦点)以及近日点和远日点。

四、使用方法

运行程序后,你会看到一个包含左侧控制面板和右侧绘图区域的窗口。使用左侧面板可以:

  1. 选择可视化类型(坐标系对比、坐标转换、几何变换或行星轨道)
    在这里插入图片描述

  2. 对于坐标转换,输入X和Y坐标值,点击"刷新图形"按钮查看转换结果
    在这里插入图片描述

  3. 对于几何变换,调整平移、旋转和缩放参数,点击"刷新图形"按钮查看变换效果
    在这里插入图片描述

  4. 对于行星轨道,调整半长轴和离心率参数,或使用预设按钮(地球轨道、哈雷彗星、水星轨道),点击"刷新图形"按钮查看轨道形状
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

五、参数说明

坐标转换参数

  • X坐标:直角坐标系中的x值
  • Y坐标:直角坐标系中的y值

几何变换参数

  • 平移(Δx, Δy):图形在x和y方向上的平移量
  • 旋转角度(°):图形绕原点旋转的角度(逆时针为正)
  • 缩放(sₓ, sᵧ):图形在x和y方向上的缩放因子

行星轨道参数

  • 半长轴(a):椭圆轨道的半长轴长度,单位为天文单位(AU)
  • 离心率(e):描述椭圆形状的参数,范围从0(圆形)到1(抛物线)

六、总结

通过这个数学可视化工具,我们可以直观地理解不同坐标系之间的关系、几何变换的效果以及行星轨道的特性。这不仅有助于学习和教学,也为数学研究和工程应用提供了一个实用的辅助工具。

这个程序的代码结构清晰,界面友好,易于扩展。你可以根据需要添加更多的数学可视化功能,如三维坐标系、向量场、函数图像等,使其成为一个更加全面的数学可视化平台。

七、完整代码

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 math
import os
from matplotlib.projections import PolarAxes
import matplotlib.transforms as transforms
from matplotlib.patches import Circle, Ellipse

# 设置matplotlib支持中文显示
plt.rcParams["font.family"] = [
    "SimHei",
    "WenQuanYi Micro Hei",
    "Heiti TC",
    "Arial Unicode MS",
]
plt.rcParams["axes.unicode_minus"] = False  # 解决负号显示问题


class CoordinateVisualizer:
    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"

        # 创建主框架
        main_frame = ttk.Frame(self.root, padding=10)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 创建左侧控制面板 - 宽度增加1.5倍(从250到375)
        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_coordinate_systems()

    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="systems")
        types = [
            ("坐标系对比", "systems"),
            ("坐标转换", "conversion"),
            ("几何变换", "transformations"),
            ("行星轨道", "orbits"),
        ]

        for text, value in types:
            ttk.Radiobutton(
                parent,
                text=text,
                variable=self.viz_type,
                value=value,
                command=self.update_visualization,
            ).pack(anchor=tk.W, padx=5, pady=2)

        # 坐标转换参数
        self.conversion_frame = ttk.LabelFrame(parent, text="坐标转换参数", padding=10)
        self.conversion_frame.pack(fill=tk.X, pady=10)

        ttk.Label(self.conversion_frame, text="X坐标 (x):").grid(
            row=0, column=0, padx=8, pady=5, sticky=tk.W
        )
        self.x_entry = ttk.Entry(self.conversion_frame, width=12)
        self.x_entry.insert(0, "3")
        self.x_entry.grid(row=0, column=1, padx=5, pady=5)

        ttk.Label(self.conversion_frame, text="Y坐标 (y):").grid(
            row=0, column=2, padx=8, pady=5, sticky=tk.W
        )
        self.y_entry = ttk.Entry(self.conversion_frame, width=12)
        self.y_entry.insert(0, "4")
        self.y_entry.grid(row=0, column=3, padx=5, pady=5)

        # 添加坐标转换面板的刷新按钮
        refresh_conversion_btn = ttk.Button(
            self.conversion_frame, text="刷新图形", command=self.refresh_conversion
        )
        refresh_conversion_btn.grid(row=1, column=0, columnspan=4, pady=8, sticky=tk.EW)

        # 几何变换参数
        self.transform_frame = ttk.LabelFrame(parent, text="几何变换参数", padding=10)
        self.transform_frame.pack(fill=tk.X, pady=10)

        # 平移参数
        ttk.Label(self.transform_frame, text="平移 (Δx, Δy):").grid(
            row=0, column=0, padx=5, pady=2, sticky=tk.W
        )
        self.tx_entry = ttk.Entry(self.transform_frame, width=8)
        self.tx_entry.insert(0, "2")
        self.tx_entry.grid(row=0, column=1, padx=5, pady=2)

        self.ty_entry = ttk.Entry(self.transform_frame, width=8)
        self.ty_entry.insert(0, "1.5")
        self.ty_entry.grid(row=0, column=2, padx=5, pady=2)

        # 旋转参数
        ttk.Label(self.transform_frame, text="旋转角度 (°):").grid(
            row=1, column=0, padx=5, pady=2, sticky=tk.W
        )
        self.rotation_entry = ttk.Entry(self.transform_frame, width=8)
        self.rotation_entry.insert(0, "30")
        self.rotation_entry.grid(
            row=1, column=1, padx=5, pady=2, columnspan=2, sticky=tk.W
        )

        # 缩放参数
        ttk.Label(self.transform_frame, text="缩放 (sₓ, sᵧ):").grid(
            row=2, column=0, padx=5, pady=2, sticky=tk.W
        )
        self.sx_entry = ttk.Entry(self.transform_frame, width=8)
        self.sx_entry.insert(0, "1.5")
        self.sx_entry.grid(row=2, column=1, padx=5, pady=2)

        self.sy_entry = ttk.Entry(self.transform_frame, width=8)
        self.sy_entry.insert(0, "0.8")
        self.sy_entry.grid(row=2, column=2, padx=5, pady=2)

        # 添加几何变换面板的刷新按钮
        refresh_transform_btn = ttk.Button(
            self.transform_frame, text="刷新图形", command=self.refresh_transformations
        )
        refresh_transform_btn.grid(row=3, column=0, columnspan=3, pady=8, sticky=tk.EW)

        # 行星轨道参数
        self.orbit_frame = ttk.LabelFrame(parent, text="行星轨道参数", padding=10)
        self.orbit_frame.pack(fill=tk.X, pady=10)

        ttk.Label(self.orbit_frame, text="半长轴 (a):").grid(
            row=0, column=0, padx=5, pady=2, sticky=tk.W
        )
        self.a_entry = ttk.Entry(self.orbit_frame, width=8)
        self.a_entry.insert(0, "1")
        self.a_entry.grid(row=0, column=1, padx=5, pady=2)

        ttk.Label(self.orbit_frame, text="离心率 (e):").grid(
            row=0, column=2, padx=5, pady=2, sticky=tk.W
        )
        self.e_entry = ttk.Entry(self.orbit_frame, width=8)
        self.e_entry.insert(0, "0.017")
        self.e_entry.grid(row=0, column=3, padx=5, pady=2)

        # 预设轨道按钮
        preset_frame = ttk.Frame(self.orbit_frame)
        preset_frame.grid(row=1, column=0, columnspan=5, pady=(10, 0))

        ttk.Button(
            preset_frame,
            text="地球轨道",
            width=10,
            command=lambda: self.set_orbit_params(1, 0.017),
        ).pack(side=tk.LEFT, padx=5)

        ttk.Button(
            preset_frame,
            text="哈雷彗星",
            width=10,
            command=lambda: self.set_orbit_params(17.8, 0.967),
        ).pack(side=tk.LEFT, padx=5)

        ttk.Button(
            preset_frame,
            text="水星轨道",
            width=10,
            command=lambda: self.set_orbit_params(0.39, 0.206),
        ).pack(side=tk.LEFT, padx=5)

        # 添加行星轨道面板的刷新按钮
        refresh_orbit_btn = ttk.Button(
            self.orbit_frame, text="刷新图形", command=self.refresh_orbit
        )
        refresh_orbit_btn.grid(row=2, column=0, columnspan=5, pady=8, sticky=tk.EW)

        # 添加保存按钮
        ttk.Button(parent, text="保存图像", command=self.save_image).pack(
            side=tk.BOTTOM, pady=10
        )

    def update_visualization(self):
        """更新可视化类型"""
        viz_type = self.viz_type.get()

        # 隐藏所有参数面板
        self.conversion_frame.pack_forget()
        self.transform_frame.pack_forget()
        self.orbit_frame.pack_forget()

        # 显示对应面板
        if viz_type == "systems":
            self.plot_coordinate_systems()
        elif viz_type == "conversion":
            self.conversion_frame.pack(fill=tk.X, pady=10)
        elif viz_type == "transformations":
            self.transform_frame.pack(fill=tk.X, pady=10)
        elif viz_type == "orbits":
            self.orbit_frame.pack(fill=tk.X, pady=10)

    def refresh_conversion(self):
        """刷新坐标转换图形"""
        try:
            x = float(self.x_entry.get())
            y = float(self.y_entry.get())
            self.plot_coordinate_conversion([x, y])
        except ValueError:
            messagebox.showerror("输入错误", "请输入有效的数值坐标")

    def refresh_transformations(self):
        """刷新几何变换图形"""
        try:
            tx = float(self.tx_entry.get())
            ty = float(self.ty_entry.get())
            angle = float(self.rotation_entry.get())
            sx = float(self.sx_entry.get())
            sy = float(self.sy_entry.get())
            self.plot_geometric_transformations()
        except ValueError:
            messagebox.showerror("输入错误", "请输入有效的变换参数")

    def refresh_orbit(self):
        """刷新行星轨道图形"""
        try:
            a = float(self.a_entry.get())
            e = float(self.e_entry.get())
            if e < 0 or e >= 1:
                raise ValueError("离心率必须在0到1之间")
            self.plot_planetary_orbit(a, e)
        except ValueError as err:
            messagebox.showerror("输入错误", str(err))

    def plot_coordinate_systems(self):
        """可视化直角坐标系与极坐标系对比"""
        self.fig.clear()
        self.fig.suptitle("直角坐标系与极坐标系对比", fontsize=14)

        # 直角坐标系
        ax1 = self.fig.add_subplot(121)
        ax1.set_aspect("equal")
        ax1.grid(True, linestyle="--", alpha=0.7)
        ax1.set_xlim(-5, 5)
        ax1.set_ylim(-5, 5)
        ax1.set_title("直角坐标系", fontsize=12)
        ax1.set_xlabel("x", fontsize=10)
        ax1.set_ylabel("y", fontsize=10, rotation=0)

        # 添加网格点
        for x in range(-4, 5):
            for y in range(-4, 5):
                ax1.plot(x, y, "bo", markersize=4, alpha=0.5)

        # 极坐标系
        ax2 = self.fig.add_subplot(122, projection="polar")
        ax2.set_theta_zero_location("E")  # 0度方向朝右
        ax2.set_theta_direction(-1)  # 顺时针方向
        ax2.set_rlim(0, 5)
        ax2.set_title("极坐标系", fontsize=12)
        ax2.grid(True, linestyle="--", alpha=0.7)

        # 添加网格点
        for r in np.linspace(1, 4, 4):
            for theta in np.linspace(0, 2 * np.pi, 16, endpoint=False):
                ax2.plot(theta, r, "ro", markersize=4, alpha=0.5)

        self.fig.tight_layout(rect=[0, 0, 1, 0.96])
        self.canvas.draw()

    def plot_coordinate_conversion(self, point):
        """展示单个点的坐标转换过程"""
        self.fig.clear()
        # 使用完整的LaTeX表达式替代可能缺失的上标字符
        self.fig.suptitle(f"坐标转换: ({point[0]:.1f}, {point[1]:.1f})", fontsize=14)

        # 直角坐标系中的点
        ax1 = self.fig.add_subplot(121)
        ax1.set_aspect("equal")
        ax1.grid(True, linestyle="--", alpha=0.7)
        ax1.set_xlim(-6, 6)
        ax1.set_ylim(-6, 6)
        # 使用完整的LaTeX表达式替代可能缺失的上标字符
        ax1.set_title(f"直角坐标: $({point[0]:.1f}, {point[1]:.1f})$", fontsize=10)

        # 绘制点和坐标线
        ax1.plot([0, point[0]], [0, 0], "b--", alpha=0.5)
        ax1.plot([point[0], point[0]], [0, point[1]], "b--", alpha=0.5)
        ax1.plot(point[0], point[1], "ro", markersize=8)

        # 极坐标系中的点
        r = np.sqrt(point[0] ** 2 + point[1] ** 2)
        theta = np.arctan2(point[1], point[0])

        ax2 = self.fig.add_subplot(122, projection="polar")
        ax2.set_theta_zero_location("E")
        ax2.set_theta_direction(-1)
        ax2.set_rlim(0, 6)
        # 使用完整的LaTeX表达式替代可能缺失的上标字符
        ax2.set_title(
            f"极坐标: $(r={r:.2f}, \\theta={np.degrees(theta):.1f}^\\circ)$",
            fontsize=10,
        )
        ax2.grid(True, linestyle="--", alpha=0.7)

        # 绘制点和坐标线
        ax2.plot([0, theta], [0, r], "r-", linewidth=2)
        ax2.plot(theta, r, "ro", markersize=8)

        # 使用完整的LaTeX表达式替代可能缺失的上标字符
        self.fig.text(
            0.5,
            0.02,
            f"转换公式: $r = \\sqrt{{x^2 + y^2}} = {r:.2f}, \\theta = \\arctan\\left(\\frac{{y}}{{x}}\\right) = {np.degrees(theta):.1f}^\\circ$",
            ha="center",
            fontsize=11,
        )

        self.fig.tight_layout(rect=[0, 0.05, 1, 0.96])
        self.canvas.draw()

    def plot_geometric_transformations(self):
        """展示几何变换:平移、旋转、缩放"""
        self.fig.clear()
        self.fig.suptitle("几何变换可视化", fontsize=14)

        # 创建三角形
        triangle = np.array([[0, 0], [1, 0], [0.5, 1], [0, 0]])

        # 获取变换参数
        try:
            tx = float(self.tx_entry.get())
            ty = float(self.ty_entry.get())
            angle = float(self.rotation_entry.get())
            sx = float(self.sx_entry.get())
            sy = float(self.sy_entry.get())
        except ValueError:
            messagebox.showerror("输入错误", "请输入有效的变换参数")
            return

        # 原始形状
        ax1 = self.fig.add_subplot(221)
        ax1.set_title("原始形状", fontsize=10)
        ax1.plot(triangle[:, 0], triangle[:, 1], "bo-")
        ax1.set_xlim(-3, 5)
        ax1.set_ylim(-3, 5)
        ax1.grid(True, linestyle="--", alpha=0.7)
        ax1.set_aspect("equal")

        # 平移变换
        translated = triangle + [tx, ty]
        ax2 = self.fig.add_subplot(222)
        ax2.set_title(f"平移: $({tx}, {ty})$", fontsize=10)
        ax2.plot(triangle[:, 0], triangle[:, 1], "bo-", alpha=0.3)
        ax2.plot(translated[:, 0], translated[:, 1], "ro-")
        ax2.set_xlim(-3, 5)
        ax2.set_ylim(-3, 5)
        ax2.grid(True, linestyle="--", alpha=0.7)
        ax2.set_aspect("equal")

        # 旋转变换
        angle_rad = np.radians(angle)
        rotation_matrix = np.array(
            [
                [np.cos(angle_rad), -np.sin(angle_rad)],
                [np.sin(angle_rad), np.cos(angle_rad)],
            ]
        )
        rotated = np.dot(triangle, rotation_matrix.T)
        ax3 = self.fig.add_subplot(223)
        # 使用完整的LaTeX表达式替代可能缺失的上标字符
        ax3.set_title(f"旋转: ${angle}^\\circ$", fontsize=10)
        ax3.plot(triangle[:, 0], triangle[:, 1], "bo-", alpha=0.3)
        ax3.plot(rotated[:, 0], rotated[:, 1], "go-")
        ax3.set_xlim(-3, 5)
        ax3.set_ylim(-3, 5)
        ax3.grid(True, linestyle="--", alpha=0.7)
        ax3.set_aspect("equal")

        # 缩放变换
        scaled = triangle * [sx, sy]
        ax4 = self.fig.add_subplot(224)
        ax4.set_title(f"缩放: $({sx}, {sy})$", fontsize=10)
        ax4.plot(triangle[:, 0], triangle[:, 1], "bo-", alpha=0.3)
        ax4.plot(scaled[:, 0], scaled[:, 1], "mo-")
        ax4.set_xlim(-3, 5)
        ax4.set_ylim(-3, 5)
        ax4.grid(True, linestyle="--", alpha=0.7)
        ax4.set_aspect("equal")

        self.fig.tight_layout(rect=[0, 0, 1, 0.96])
        self.canvas.draw()

    def set_orbit_params(self, a, e):
        """设置轨道参数"""
        self.a_entry.delete(0, tk.END)
        self.a_entry.insert(0, str(a))
        self.e_entry.delete(0, tk.END)
        self.e_entry.insert(0, str(e))
        self.refresh_orbit()  # 使用统一刷新函数

    def plot_planetary_orbit(self, a, e):
        """
        绘制行星轨道(椭圆)在极坐标系中的表示
        参数:
        a: 半长轴 (天文单位)
        e: 离心率 (0 < e < 1)
        """
        self.fig.clear()
        self.fig.suptitle(f"行星轨道 $(a={a}, e={e})$", fontsize=14)

        # 计算轨道参数
        b = a * np.sqrt(1 - e**2)  # 半短轴
        focal_distance = e * a  # 焦点间距

        # 直角坐标系视图
        ax1 = self.fig.add_subplot(121)
        ax1.set_aspect("equal")
        ax1.set_title("直角坐标系中的椭圆轨道", fontsize=10)
        ax1.set_xlabel("x (AU)")
        ax1.set_ylabel("y (AU)")

        # 绘制椭圆轨道
        theta = np.linspace(0, 2 * np.pi, 300)
        r = a * (1 - e**2) / (1 + e * np.cos(theta))
        x = r * np.cos(theta)
        y = r * np.sin(theta)

        ax1.plot(x, y, "b-", linewidth=1.8)

        # 标记焦点(太阳位置)
        ax1.plot(-focal_distance, 0, "yo", markersize=12)
        ax1.text(-focal_distance - 0.2, 0.2, "太阳", fontsize=9)

        # 添加网格和标记
        ax1.grid(True, linestyle="--", alpha=0.6)
        ax1.set_xlim(-a * 1.2, a * 1.2)
        ax1.set_ylim(-b * 1.2, b * 1.2)

        # 极坐标系视图
        ax2 = self.fig.add_subplot(122, projection="polar")
        ax2.set_theta_zero_location("E")
        ax2.set_theta_direction(-1)
        ax2.set_title("极坐标系中的椭圆轨道", fontsize=10, pad=20)

        # 绘制轨道
        ax2.plot(theta, r, "b-", linewidth=1.8)

        # 标记近日点和远日点
        perihelion_theta = 0
        aphelion_theta = np.pi
        perihelion_r = a * (1 - e)
        aphelion_r = a * (1 + e)

        ax2.plot(perihelion_theta, perihelion_r, "ro", markersize=8)
        ax2.plot(aphelion_theta, aphelion_r, "go", markersize=8)

        # 添加标签
        ax2.text(
            perihelion_theta,
            perihelion_r,
            " 近日点",
            verticalalignment="bottom",
            fontsize=9,
        )
        ax2.text(
            aphelion_theta,
            aphelion_r,
            " 远日点",
            verticalalignment="bottom",
            fontsize=9,
        )

        # 添加轨道参数标注
        param_text = f"半长轴: ${a}$ AU\n离心率: ${e}$\n半短轴: ${b:.2f}$ AU"
        ax2.annotate(
            param_text,
            xy=(0.5, 0.95),
            xycoords="axes fraction",
            ha="center",
            va="top",
            fontsize=10,
            bbox=dict(boxstyle="round,pad=0.3", fc="white", alpha=0.8),
        )

        ax2.grid(True, linestyle="--", alpha=0.6)

        # 使用完整的LaTeX表达式替代可能缺失的上标字符
        self.fig.text(
            0.5,
            0.02,
            f"椭圆轨道方程: $r = \\frac{{a(1-e^2)}}{{1+e\\cdot\\cos\\theta}} = \\frac{{{a:.2f}\\times(1-{e:.3f}^2)}}{{1+{e:.3f}\\cdot\\cos\\theta}}$",
            ha="center",
            fontsize=11,
        )

        self.fig.tight_layout(rect=[0, 0.05, 1, 0.96])
        self.canvas.draw()

    def save_image(self):
        """保存当前图像"""
        try:
            filename = simpledialog.askstring(
                "保存图像", "请输入文件名:", initialvalue="coordinate_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 = CoordinateVisualizer(root)
    root.mainloop()


if __name__ == "__main__":
    main()

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐