动态演示复平面变换

复变函数可视化工具:动态演示复平面变换

引言

在复变函数的学习过程中,如何直观地理解函数对复平面的变换一直是一个挑战。为了帮助学习者更好地理解复变函数的几何意义,我开发了一个基于Web的复变函数可视化工具。这个工具能够动态展示复变函数如何将复平面上的点进行映射变换,让抽象的数学概念变得生动可见。最后会给出完整代码。

功能概述

界面布局

工具采用左右对比的展示方式:

  • 左侧显示原始复平面,包含标准网格和坐标轴
  • 右侧实时展示函数变换后的结果,通过动画效果直观呈现变换过程

支持的函数变换

目前支持以下几种典型的复变函数:

  1. f(z) = z (恒等映射)

    • 作为参考基准,帮助理解其他变换
    • 输出等同于输入,网格保持不变
  2. f(z) = z² (平方映射)

    • 展示了复数平方的几何效果
    • 可以观察到角度加倍、距离平方的现象
  3. f(z) = 1/z (倒数映射)

    • 演示了复数倒数的几何意义
    • 体现了圆反演的特性
  4. f(z) = z² + 1

    • 展示了复数多项式的行为
    • 观察平移变换的效果
  5. f(z) = e^z (指数映射)

    • 展示了复指数函数的周期性
    • 观察条带区域如何映射到螺旋形状

技术实现

复数运算

核心是实现了Complex类处理复数运算:

javascript 复制代码
class Complex {
    constructor(re, im) {
        this.re = re;
        this.im = im;
    }
    
    // 复数加法
    plus(other) {
        return new Complex(
            this.re + other.re,
            this.im + other.im
        );
    }
    
    // 复数乘法
    times(other) {
        return new Complex(
            this.re * other.re - this.im * other.im,
            this.re * other.im + this.im * other.re
        );
    }
    
    // 其他复数运算...
}

动画实现

采用requestAnimationFrame实现平滑动画效果:

javascript 复制代码
function animate() {
    if (!playing) return;
    t += 0.01; // 控制动画速度
    if (t > 1) {
        t = 1;
        playing = false;
    }
    drawTransGrid();
    if (playing) {
        requestAnimationFrame(animate);
    }
}

绘图技术

使用Canvas进行网格绘制,主要步骤:

  1. 坐标变换:将数学坐标映射到画布坐标
  2. 网格线绘制:通过循环绘制水平和垂直线条
  3. 实时更新:根据动画参数t更新网格位置

使用指南

  1. 选择函数

    • 从下拉菜单中选择要观察的函数
    • 每个函数都有其特定的几何特性
  2. 控制动画

    • 点击"播放"开始动画
    • "暂停"可以在任意时刻停止观察
    • "重置"返回初始状态
  3. 观察要点

    • 关注坐标轴的变化
    • 观察网格线的扭曲方式
    • 注意特殊点的映射关系

教育价值

这个工具在数学教育中有多重价值:

  1. 直观理解

    • 将抽象的数学概念可视化
    • 帮助建立几何直觉
  2. 互动学习

    • 学习者可以自主探索
    • 即时观察变换效果
  3. 概念联系

    • 建立代数和几何的联系
    • 理解不同函数间的关系

技术特点

  1. 响应式设计

    • 适配不同屏幕尺寸
    • 良好的移动端体验
  2. 性能优化

    • 使用requestAnimationFrame确保动画流畅
    • 优化绘图算法提高效率
  3. 代码组织

    • 模块化设计
    • 清晰的代码结构

未来展望

计划添加的功能:

  1. 更多复变函数

    • 三角函数
    • 对数函数
    • 有理函数
  2. 交互增强

    • 自定义函数输入
    • 放大缩小功能
    • 特殊点标记
  3. 教育功能

    • 添加教学注释
    • 保存动画过程
    • 导出图像功能

完整代码

html 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8" />
  <title>复数域函数动态平面变形示例</title>
  <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 min-h-screen flex flex-col items-center p-4">
  <h1 class="text-2xl font-bold mb-4">复数域函数动态平面变形示例</h1>

  <!-- 函数选择区 -->
  <div class="mb-4 flex space-x-2 items-center">
    <label for="funcSelect" class="mr-2">选择函数:</label>
    <select id="funcSelect" class="p-1 border rounded">
      <option value="z">f(z) = z</option>
      <option value="z2">f(z) = z²</option>
      <option value="1z">f(z) = 1/z</option>
      <option value="z2plus1">f(z) = z² + 1</option>
      <option value="expz">f(z) = e^z</option>
    </select>

    <!-- 动画控制按钮 -->
    <button id="playBtn" class="px-2 py-1 bg-blue-500 text-white rounded">播放动画</button>
    <button id="pauseBtn" class="px-2 py-1 bg-red-500 text-white rounded">暂停动画</button>
    <button id="resetBtn" class="px-2 py-1 bg-gray-500 text-white rounded">复位</button>
  </div>

  <!-- 画布容器 -->
  <div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4">
    <!-- 左侧:原平面 -->
    <div class="flex flex-col items-center">
      <h2 class="font-semibold mb-2">原平面 (Domain)</h2>
      <canvas id="domainCanvas" width="400" height="400" class="border border-gray-300"></canvas>
    </div>
    <!-- 右侧:变形过程 (可动态演示从 t=0 到 t=1) -->
    <div class="flex flex-col items-center">
      <h2 class="font-semibold mb-2">动态变形 (Intermediate → Range)</h2>
      <canvas id="transCanvas" width="400" height="400" class="border border-gray-300"></canvas>
    </div>
  </div>

  <script>
    // ========== 全局变量 ==========
    const domainCanvas = document.getElementById("domainCanvas");
    const domainCtx = domainCanvas.getContext("2d");
    const transCanvas = document.getElementById("transCanvas");
    const transCtx = transCanvas.getContext("2d");

    const funcSelect = document.getElementById("funcSelect");
    const playBtn = document.getElementById("playBtn");
    const pauseBtn = document.getElementById("pauseBtn");
    const resetBtn = document.getElementById("resetBtn");

    // 画布大小
    const WIDTH = 400;
    const HEIGHT = 400;

    // 数学坐标范围
    const XMIN = -2, XMAX = 2;
    const YMIN = -2, YMAX = 2;

    // 网格步进
    const STEPS = 20;

    // 动画控制
    let animationRequest = null;
    let t = 0;            // 插值参数 (0 ~ 1)
    let playing = false;  // 是否正在播放

    // ========== 复数结构和函数集 ==========
    class Complex {
      constructor(re, im) {
        this.re = re;
        this.im = im;
      }
      plus(other) {
        return new Complex(this.re + other.re, this.im + other.im);
      }
      times(other) {
        return new Complex(
          this.re * other.re - this.im * other.im,
          this.re * other.im + this.im * other.re
        );
      }
      reciprocal() {
        const denom = this.re * this.re + this.im * this.im;
        return new Complex(this.re / denom, -this.im / denom);
      }
      exp() {
        // e^(x+iy) = e^x (cos y + i sin y)
        const r = Math.exp(this.re);
        return new Complex(r * Math.cos(this.im), r * Math.sin(this.im));
      }
    }

    // (1)恒等映射 f(z) = z
    function f_z(z) {
      return z;
    }
    // (2)平方映射 f(z) = z^2
    function f_z2(z) {
      return z.times(z);
    }
    // (3)倒数映射 f(z) = 1/z
    function f_1z(z) {
      return new Complex(1, 0).times(z.reciprocal());
    }
    // (4)z^2 + 1
    function f_z2plus1(z) {
      return z.times(z).plus(new Complex(1, 0));
    }
    // (5)指数映射 f(z) = e^z
    function f_expz(z) {
      return z.exp();
    }

    // 根据选择返回当前函数
    function getCurrentFunc() {
      switch (funcSelect.value) {
        case "z":          return f_z;
        case "z2":         return f_z2;
        case "1z":         return f_1z;
        case "z2plus1":    return f_z2plus1;
        case "expz":       return f_expz;
      }
      return f_z; // 默认
    }

    // ========== 坐标变换函数 ==========
    // 将数学坐标映射到画布像素坐标
    function toCanvasX(x) {
      return ((x - XMIN) / (XMAX - XMIN)) * WIDTH;
    }
    function toCanvasY(y) {
      // 画布 Y 轴向下
      return HEIGHT - ((y - YMIN) / (YMAX - YMIN)) * HEIGHT;
    }

    // ========== 绘图函数 ==========
    // 绘制原平面的网格
    function drawDomainGrid(ctx) {
      ctx.clearRect(0, 0, WIDTH, HEIGHT);
      ctx.strokeStyle = "#ccc";
      ctx.lineWidth = 1;

      const stepX = (XMAX - XMIN) / STEPS;
      const stepY = (YMAX - YMIN) / STEPS;

      // 水平线
      for (let i = 0; i <= STEPS; i++) {
        const y = YMIN + i * stepY;
        ctx.beginPath();
        ctx.moveTo(toCanvasX(XMIN), toCanvasY(y));
        ctx.lineTo(toCanvasX(XMAX), toCanvasY(y));
        ctx.stroke();
      }

      // 垂直线
      for (let j = 0; j <= STEPS; j++) {
        const x = XMIN + j * stepX;
        ctx.beginPath();
        ctx.moveTo(toCanvasX(x), toCanvasY(YMIN));
        ctx.lineTo(toCanvasX(x), toCanvasY(YMAX));
        ctx.stroke();
      }

      // 画坐标轴
      ctx.strokeStyle = "#000";
      ctx.lineWidth = 2;
      // x 轴
      ctx.beginPath();
      ctx.moveTo(toCanvasX(XMIN), toCanvasY(0));
      ctx.lineTo(toCanvasX(XMAX), toCanvasY(0));
      ctx.stroke();
      // y 轴
      ctx.beginPath();
      ctx.moveTo(toCanvasX(0), toCanvasY(YMIN));
      ctx.lineTo(toCanvasX(0), toCanvasY(YMAX));
      ctx.stroke();
    }

    // 在插值下绘制变形网格
    // t=0 时画与 domainGrid 同位置
    // t=1 时画与 f(z) 后对应的位置
    function drawTransformedGrid(ctx, transformFunc, t) {
      ctx.clearRect(0, 0, WIDTH, HEIGHT);
      ctx.strokeStyle = "#ccc";
      ctx.lineWidth = 1;

      const stepX = (XMAX - XMIN) / STEPS;
      const stepY = (YMAX - YMIN) / STEPS;

      // 插值函数:z(t) = (1-t)*z + t*f(z)
      function interpolate(z, w, t) {
        return new Complex(
          (1 - t) * z.re + t * w.re,
          (1 - t) * z.im + t * w.im
        );
      }

      // 每条水平线
      for (let i = 0; i <= STEPS; i++) {
        const y = YMIN + i * stepY;
        ctx.beginPath();
        let firstPoint = true;
        for (let x = XMIN; x <= XMAX + 1e-9; x += stepX / 4) {
          const z = new Complex(x, y);
          const w = transformFunc(z);
          const zt = interpolate(z, w, t);
          const px = toCanvasX(zt.re);
          const py = toCanvasY(zt.im);

          if (firstPoint) {
            ctx.moveTo(px, py);
            firstPoint = false;
          } else {
            ctx.lineTo(px, py);
          }
        }
        ctx.stroke();
      }

      // 每条垂直线
      for (let j = 0; j <= STEPS; j++) {
        const x = XMIN + j * stepX;
        ctx.beginPath();
        let firstPoint = true;
        for (let y = YMIN; y <= YMAX + 1e-9; y += stepY / 4) {
          const z = new Complex(x, y);
          const w = transformFunc(z);
          const zt = interpolate(z, w, t);
          const px = toCanvasX(zt.re);
          const py = toCanvasY(zt.im);

          if (firstPoint) {
            ctx.moveTo(px, py);
            firstPoint = false;
          } else {
            ctx.lineTo(px, py);
          }
        }
        ctx.stroke();
      }

      // 画插值后的 x 轴和 y 轴
      ctx.strokeStyle = "#000";
      ctx.lineWidth = 2;
      // x 轴: y=0
      ctx.beginPath();
      let firstAxisX = true;
      for (let x = XMIN; x <= XMAX + 1e-9; x += stepX / 4) {
        const z = new Complex(x, 0);
        const w = transformFunc(z);
        const zt = interpolate(z, w, t);
        if (firstAxisX) {
          ctx.moveTo(toCanvasX(zt.re), toCanvasY(zt.im));
          firstAxisX = false;
        } else {
          ctx.lineTo(toCanvasX(zt.re), toCanvasY(zt.im));
        }
      }
      ctx.stroke();

      // y 轴: x=0
      ctx.beginPath();
      let firstAxisY = true;
      for (let y = YMIN; y <= YMAX + 1e-9; y += stepY / 4) {
        const z = new Complex(0, y);
        const w = transformFunc(z);
        const zt = interpolate(z, w, t);
        if (firstAxisY) {
          ctx.moveTo(toCanvasX(zt.re), toCanvasY(zt.im));
          firstAxisY = false;
        } else {
          ctx.lineTo(toCanvasX(zt.re), toCanvasY(zt.im));
        }
      }
      ctx.stroke();
    }

    // ========== 动画相关函数 ==========
    function animate() {
      if (!playing) return;
      t += 0.01; // 调整速度
      if (t > 1) {
        t = 1;
        playing = false; 
      }
      drawTransGrid();    // 绘制当前插值下的网格
      if (playing) {
        animationRequest = requestAnimationFrame(animate);
      }
    }

    function drawAll() {
      // 先画左侧固定的 domain 网格
      drawDomainGrid(domainCtx);
      // 再画右侧根据 t 的插值绘制结果
      drawTransGrid();
    }

    function drawTransGrid() {
      const currentFunc = getCurrentFunc();
      drawTransformedGrid(transCtx, currentFunc, t);
    }

    // ========== 按钮事件 ==========
    playBtn.addEventListener("click", () => {
      if (t >= 1) t = 0; // 如果已经到结尾,再次播放则从头开始
      playing = true;
      if (!animationRequest) animate();
    });

    pauseBtn.addEventListener("click", () => {
      playing = false;
      animationRequest = null;
    });

    resetBtn.addEventListener("click", () => {
      playing = false;
      t = 0;
      animationRequest = null;
      drawAll();
    });

    // 在函数下拉变更时,复位并重新绘制
    funcSelect.addEventListener("change", () => {
      playing = false;
      t = 0;
      animationRequest = null;
      drawAll();
    });

    // 页面载入时初始绘制
    window.onload = () => {
      drawAll();
    };
  </script>
</body>
</html>

结语

这个复变函数可视化工具不仅是一个教学辅助工具,也是理解复变函数几何意义的有力帮手。通过动态、直观的方式展示复变函数的变换效果,让学习复变函数变得更加生动有趣。欢迎教师和学生使用这个工具进行教学和学习,也欢迎开发者参与改进和扩展这个项目。


参考资源:

  • 复变函数教材
  • HTML5 Canvas文档
  • JavaScript动画最佳实践
相关推荐
prince_zxill4 小时前
JavaScript 中的 CSS 与页面响应式设计
前端·javascript·css·前端框架·html
呦呦鹿鸣Rzh6 小时前
HTML-表格,表单标签
java·前端·html
彭友圈1018 小时前
利用HTML和css技术编写学校官网页面
前端·css·html
爱编程的鱼11 小时前
HTML5 教程之标签(3)
前端·html·html5
aichitang202413 小时前
实时波形与频谱分析———傅立叶变换
开发语言·matlab·html·傅立叶分析
大模型铲屎官15 小时前
【HTML性能优化】提升网站加载速度:GZIP、懒加载与资源合并
前端·性能优化·html·gzip·懒加载·网站加载·资源合并
初九之潜龙勿用17 小时前
C#结合html2canvas生成切割图片并导出到PDF
开发语言·ui·pdf·c#·html·asp.net
大模型铲屎官19 小时前
前端框架中 HTML 的应用技巧:React、Vue、Angular 深度解析
react.js·前端框架·vue·html·编程·html5·angular
禁默1 天前
【学术投稿-2025年计算机视觉研究进展与应用国际学术会议 (ACVRA 2025)】从计算机基础到HTML开发:Web开发的第一步
前端·计算机视觉·html