欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net

示例效果

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

摘要

在微电子与计算电磁学(Computational Electromagnetics)的纵深领域,天线馈线系统的阻抗匹配网络设计长期依赖于 ADS (Advanced Design System) 或 MATLAB 等桌面级重型 EDA 工业软件。而其中最核心的拓扑观测工具,莫过于诞生于 1939 年的“史密斯圆图(Smith Chart)”。本研究以前端跨平台架构 Flutter 为渲染基座,硬核重构了一套极其轻量、高帧率且支持极坐标复平面积分变换的“移动端射频阻抗控制台”。系统利用底层的 CustomPaint 画布与欧拉复数方程,强行映射了归一化复数阻抗在反射系数 Γ \Gamma Γ 空间中的正交轨迹(等电阻圆与等电抗圆)。本文将从微波工程的微积分底层出发,深度解剖这套融合了电压驻波比(VSWR)、回波损耗(Return Loss)核算模型与雷达扫描荧光特效的极客级物理光栅渲染管线。


一、 微电子射频工程中的阻抗匹配黑箱与渲染瓶颈

1.1 阻抗匹配网络的高维复平面困境

在射频微波收发链路中,当信号发生器的特性阻抗( Z 0 Z_0 Z0 通常为 50 Ω 50\Omega 50Ω)与天线负载的物理复数阻抗 Z L = R L + j X L Z_L = R_L + jX_L ZL=RL+jXL 不一致时,高频微波电磁场将在传输线传输端口发生灾难性的全反射或部分反射,导致发射功率严重折损并可能烧毁功放管。

传统的数学调试需要解极其复杂的双曲高阶方程。史密斯圆图的伟大之处,在于它将涵盖半个复平面的阻抗域( 0 ≤ R < ∞ , − ∞ < X < ∞ 0 \le R < \infty, -\infty < X < \infty 0R<,<X<)强行通过保角映射(Conformal Mapping)扭曲坍缩进了一个半径为 1 的绝对极坐标圆盘(即全反射系数界限 ∣ Γ ∣ ≤ 1 |\Gamma| \le 1 ∣Γ∣1)内。

1.2 工业软件的封闭与跨平台降维打击

当前生存在手机端的射频辅助软件,多是以粗暴的“文本输入框 + 僵硬的结果弹窗”堆砌而成。本架构运用 Flutter 内存直达 GPU 的光栅树指令,将冷冰冰的微波输入框降维打击成了可直接手指横向推拉、并且实时伴有赛博绿荧光扫视渲染(Radar Sweep Effect)的军事级雷达控制台。


二、 史密斯圆图的微波极坐标数学物理建模

将物理学变量从笛卡尔坐标系映射到 Flutter 的 UI 坐标系,需要经过极度精密的微积分与代数转换。

2.1 反射系数的复平面保角映射

设传输线的特征阻抗为 Z 0 Z_0 Z0(参考阻抗),天线终端的复负载阻抗为 Z L = R + j X Z_L = R + jX ZL=R+jX。其反射系数 Γ \Gamma Γ(Reflection Coefficient)方程如下:

Γ = Z L − Z 0 Z L + Z 0 \Gamma = \frac{Z_L - Z_0}{Z_L + Z_0} Γ=ZL+Z0ZLZ0

将阻抗进行归一化 z = Z L / Z 0 = r + j x z = Z_L / Z_0 = r + jx z=ZL/Z0=r+jx,带入后反射系数即可分解为实部 u u u 和虚部 v v v 构成的极坐标相量:

Γ = u + j v = ( r − 1 ) + j x ( r + 1 ) + j x \Gamma = u + jv = \frac{(r-1)+jx}{(r+1)+jx} Γ=u+jv=(r+1)+jx(r1)+jx

对其分母实施复数共轭拉伸剥离,可得到核心数学映射双子方程(此方程被直接植入了我们在 Dart 中的核心运算单元):

u = r 2 − 1 + x 2 ( r + 1 ) 2 + x 2 , v = 2 x ( r + 1 ) 2 + x 2 u = \frac{r^2 - 1 + x^2}{(r+1)^2 + x^2}, \quad v = \frac{2x}{(r+1)^2 + x^2} u=(r+1)2+x2r21+x2,v=(r+1)2+x22x

2.2 驻波比(VSWR)与能量损耗核算机制

在 HUD 数据面板中,我们需要呈现电磁波在传输线中激荡的驻波状态。其中反射系数的模长 ∣ Γ ∣ = u 2 + v 2 |\Gamma| = \sqrt{u^2 + v^2} ∣Γ∣=u2+v2
系统在 60 Hz 60\text{Hz} 60Hz 渲染循环中实时核算天线致命的驻波比(VSWR)与回波损耗(Return Loss):

VSWR = 1 + ∣ Γ ∣ 1 − ∣ Γ ∣ \text{VSWR} = \frac{1 + |\Gamma|}{1 - |\Gamma|} VSWR=1∣Γ∣1+∣Γ∣
RL ( dB ) = − 20 ⋅ log ⁡ 10 ∣ Γ ∣ \text{RL} (\text{dB}) = -20 \cdot \log_{10}|\Gamma| RL(dB)=20log10∣Γ∣

这些数据决定了控制台上文本字体的生死颜色(驻波过大时闪烁警戒红)。


三、 系统架构域模型与渲染流水线

为了防御在调节 R R R X X X 参数时导致的整个 APP 全局卡顿重建,我们部署了界限分明的隔离域架构。

3.1 实体物理与图层剥离结构 (Mermaid UML)

实例化天线负载

发送时序渲染脏区块

请求计算反射系数坐标

ComplexImpedance

+double r

+double x

+normalize(double z0)

+getReflectionCoefficient(double z0) : Offset

RFMatchingDashboard

+double _loadResistance

+double _loadReactance

+List<ComplexImpedance> _matchingTrajectory

+onImpedanceChanged(r, x)

SmithChartPainter

+double z0

+ComplexImpedance currentLoad

+List<ComplexImpedance> trajectory

+double sweepAngle

+paint(Canvas, Size)

+drawConstantResistanceCircles()

+drawConstantReactanceCircles()

3.2 阻抗寻星与拖拽时序瀑布管线 (Flowchart)

对数缩放

射频工程师在底部拖拽实部 R 虚部 X 滑动条

解析标量

将 0-1 的标量线性映射至 10^x 欧姆

装载最新的 R,jX 阻抗实体

推入长度为 50 的 matchingTrajectory 尾迹序列缓冲池

触发 VSync 垂直同步帧重绘

进入 CustomPaint 光栅化引擎区

绘制底层 Radar 荧光雷达旋转余辉

挂载 clipPath 剪切超限圆外数据防止溢出

遍历渲染 r-circles 与 x-circles 基准网格

反解反射系数公式计算 u,v 叠加物理 R 半径绘制驻点


四、 核心渲染图谱四维代码全息解剖

本节将剥去所有的 UI 糖霜,直接下探到那些让史密斯圆图极其精炼又强悍的绘图几何微积分代码底层。

4.1 核心一:保角映射反射系数发生器 (Complex To Γ \Gamma Γ)

在不可变的实体类内部,利用最基础的代数运算实现了欧拉空间转换,避免引入庞大臃肿的复数依赖包。此函数在防止分母极小值奇点崩溃时做出了绝对拦截。

  /// 计算反射系数 Gamma = (Z - Z0) / (Z + Z0) -> 结果是一个复数 Gamma = u + jv
  Offset getReflectionCoefficient(double z0) {
    final norm = normalize(z0);
    final denom = math.pow(norm.r + 1.0, 2) + math.pow(norm.x, 2);
    if (denom == 0) return const Offset(-1, 0); // 短路极限态拦截

    final u = (math.pow(norm.r, 2) - 1.0 + math.pow(norm.x, 2)) / denom;
    final v = (2.0 * norm.x) / denom;
    return Offset(u, v);
  }

此处返回的 Offset(u, v) 是一组极其优美的 [ − 1 , 1 ] [-1, 1] [1,1] 归一化坐标系,构成了整个史密斯圆图的绝对寻址基石。

4.2 核心二:极简防溢出的等电抗圆渲染 (x-circles)

史密斯圆图中,绘制向外辐射的等电抗圆弧长期困扰了众多桌面级 C# 程序员,因为这些圆只存在于圆图内部,外部需要被复杂方程截断。在 Flutter 中,我们极其优雅地运用了硬件级蒙版裁剪(Clipping)击穿了这一物理壁垒。

        // 史密斯圆图上等电抗圆的极坐标方程解:
        // 圆心位置: u = 1.0, v = 1 / x
        // 物理半径: radius = 1 / |x|
        final centerV = 1.0 / currentX;
        final physicalRadius = (1.0 / currentX.abs()) * R;
        
        final circleCenter = Offset(center.dx + R, center.dy - centerV * R);
        
        // 由于之前已经通过 canvas.clipPath(chartRect) 限定了主圆区域,
        // 整个完整的超大圆在 GPU 着色阶段超出的无源负载部分将被无缝抹除!
        canvas.drawCircle(circleCenter, physicalRadius, xPaint);

没有令人作呕的三角函数求交点截断代码,纯粹利用 canvas.clipPath(Path()..addOval(chartRect)) 前置防御屏障,直接画圆即可达成完美贴合边界的半弧。

4.3 核心三:对数刻度阻抗滑轮换算机制

对于电阻(Resistance)而言,其物理真实状态分布在零(极度短路态)到无穷大(极度开路态)。如果用普通的线性 Slider 根本无法进行精细匹配。

    // 采用对数刻度控制 R 以实现从短路到开路的平滑过渡
    // sliderValR 的域被强行归一化到 0 到 1
    double sliderValR = (math.log(_loadResistance) / math.ln10 + 1) / 4.0;
    
    // UI 反向解码:
    double r = math.pow(10, v * 4 - 1).toDouble(); // v 为 Slider 返回值

这种 R = 10 ( 4 v − 1 ) R = 10^{(4v-1)} R=10(4v1) 的指数级扭曲,能够让拖拽手感在 50 Ω 50\Omega 50Ω 附近拥有极高的显微镜级别的调整精密度,而在偏向短路或开路时则呈现宏观掠过的手感。

4.4 核心四:雷达荧光余辉扫描层 (Radar Sweep Phosphor)

为了重现古董级军用微波网络分析仪的浪漫感,我们在背景注入了一个通过 AnimationController 独立接管旋转相位的雷达着色器引擎。

    final sweepPaint = Paint()
      ..shader = SweepGradient(
        center: Alignment.center,
        startAngle: sweepAngle - math.pi / 4, // 拖尾 45度
        endAngle: sweepAngle,
        colors: [
          const Color(0xFF00FF00).withValues(alpha: 0.0),
          const Color(0xFF00FF00).withValues(alpha: 0.15), // 扫描边缘峰值亮点
        ],
      ).createShader(Rect.fromCircle(center: center, radius: radius))
      ..style = PaintingStyle.fill;

利用 SweepGradient 的强行裁切生成渐变探海扇面,伴随画板原点的 canvas.translate 独立自转,实现了与阻抗点渲染层绝不干涉的双维空间并行运行机制。


五、 响应式视口防崩塌与结语

无论这套微电子仪表面向的是折叠屏、竖屏平板抑或是纵向极限被压缩的横屏手机,代码中引入了物理直径 math.min(size.width, size.height) / 2 * 0.85 作为核心雷达半径约束阀门。这保证了极坐标大本营永远不会侵入到控制面板与遥测数据的领空,维持了军工医疗级别严苛的空间秩序美感。

从计算电磁学回归到前端框架维度来看,这套《微电子射频阻抗史密斯测绘网》验证了一项真理:Flutter 的算力远不止停留在绘制文本排版和商品列表。一旦解开了它的 CPU 与 GPU 微积分限制器,即使是用来解算高维复平面特征方程并即时重绘电磁场轨迹,它依然能做到冷酷、精确并带来极度震撼的机械赛博美学!

完整代码

import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
    statusBarColor: Colors.transparent,
    statusBarIconBrightness: Brightness.light,
  ));
  runApp(const SmithChartApp());
}

class SmithChartApp extends StatelessWidget {
  const SmithChartApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '射频史密斯圆图系统',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF00FF00), // 经典示波器荧光绿
          brightness: Brightness.dark,
          surface: const Color(0xFF050A05), // 极黑带微弱绿色反光底色
          primary: const Color(0xFF00FF00),
          secondary: const Color(0xFF00FFFF),
        ),
        scaffoldBackgroundColor: const Color(0xFF000000), // 纯黑背景
        cardColor: const Color(0xFF0A140A),
      ),
      home: const RFMatchingDashboard(),
    );
  }
}

/// 表示复数阻抗 Z = R + jX
class ComplexImpedance {
  final double r; // 电阻分量 (Resistance)
  final double x; // 电抗分量 (Reactance)

  const ComplexImpedance(this.r, this.x);

  /// 计算归一化复数阻抗 z = Z / Z0
  ComplexImpedance normalize(double z0) {
    return ComplexImpedance(r / z0, x / z0);
  }

  /// 计算反射系数 Gamma = (Z - Z0) / (Z + Z0) -> 结果是一个复数 Gamma = u + jv
  Offset getReflectionCoefficient(double z0) {
    final norm = normalize(z0);
    final denom = math.pow(norm.r + 1.0, 2) + math.pow(norm.x, 2);
    if (denom == 0) return const Offset(-1, 0); // 短路极限态

    final u = (math.pow(norm.r, 2) - 1.0 + math.pow(norm.x, 2)) / denom;
    final v = (2.0 * norm.x) / denom;
    return Offset(u, v);
  }
}

/// 射频匹配操作台
class RFMatchingDashboard extends StatefulWidget {
  const RFMatchingDashboard({super.key});

  @override
  State<RFMatchingDashboard> createState() => _RFMatchingDashboardState();
}

class _RFMatchingDashboardState extends State<RFMatchingDashboard> with TickerProviderStateMixin {
  // 特性阻抗 Z0
  final double _z0 = 50.0;
  
  // 天线负载阻抗 Z_L
  double _loadResistance = 50.0; // 0.1 to 1000 Ohms
  double _loadReactance = 0.0;   // -500 to 500 Ohms
  
  // 匹配历史轨迹缓冲 (用于重绘匹配路径)
  final List<ComplexImpedance> _matchingTrajectory = [];
  
  // 雷达扫描底盘动画
  late AnimationController _sweepController;
  
  @override
  void initState() {
    super.initState();
    _sweepController = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 4),
    )..repeat();
    
    _recordTrajectory();
  }

  @override
  void dispose() {
    _sweepController.dispose();
    super.dispose();
  }

  void _recordTrajectory() {
    _matchingTrajectory.add(ComplexImpedance(_loadResistance, _loadReactance));
    if (_matchingTrajectory.length > 50) {
      _matchingTrajectory.removeAt(0); // 维持尾迹长度
    }
  }

  void _onImpedanceChanged(double r, double x) {
    setState(() {
      _loadResistance = r;
      _loadReactance = x;
      _recordTrajectory();
    });
  }

  @override
  Widget build(BuildContext context) {
    final screenWidth = MediaQuery.of(context).size.width;
    final isWide = screenWidth > 850;

    return Scaffold(
      body: Row(
        children: [
          if (isWide) _buildControlPanel(340),
          Expanded(
            child: Column(
              children: [
                if (!isWide) _buildMobileHeader(),
                Expanded(
                  child: Stack(
                    children: [
                      // 史密斯圆图极坐标底座渲染
                      Positioned.fill(
                        child: AnimatedBuilder(
                          animation: _sweepController,
                          builder: (context, _) {
                            return CustomPaint(
                              painter: SmithChartPainter(
                                z0: _z0,
                                currentLoad: ComplexImpedance(_loadResistance, _loadReactance),
                                trajectory: _matchingTrajectory,
                                sweepAngle: _sweepController.value * 2 * math.pi,
                              ),
                            );
                          },
                        ),
                      ),
                      // 实时物理遥测 HUD
                      Positioned(
                        top: 16,
                        left: 16,
                        child: _buildTelemetryHUD(),
                      ),
                    ],
                  ),
                ),
                if (!isWide) _buildMobileBottomBar(),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildTelemetryHUD() {
    final zL = ComplexImpedance(_loadResistance, _loadReactance);
    final gamma = zL.getReflectionCoefficient(_z0);
    final gammaMag = math.sqrt(gamma.dx * gamma.dx + gamma.dy * gamma.dy);
    final gammaPhase = math.atan2(gamma.dy, gamma.dx) * 180 / math.pi;
    
    // 计算 VSWR (电压驻波比)
    double vswr = double.infinity;
    if (gammaMag < 0.999) {
      vswr = (1.0 + gammaMag) / (1.0 - gammaMag);
    }
    
    // 计算 Return Loss (回波损耗)
    double returnLoss = double.infinity;
    if (gammaMag > 0.001) {
      returnLoss = -20.0 * math.log(gammaMag) / math.ln10;
    }

    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: const Color(0xFF0A140A).withValues(alpha: 0.8),
        border: Border.all(color: const Color(0xFF00FF00).withValues(alpha: 0.3)),
        borderRadius: BorderRadius.circular(8),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: [
          _buildHUDText('Z₀ (特性阻抗):', '${_z0.toStringAsFixed(1)} Ω'),
          _buildHUDText('Zʟ (负载阻抗):', '${_loadResistance.toStringAsFixed(1)} ${(_loadReactance >= 0) ? '+' : '-'} j${_loadReactance.abs().toStringAsFixed(1)} Ω'),
          const Divider(color: Colors.greenAccent, height: 16),
          _buildHUDText('Γ (反射系数):', '${gammaMag.toStringAsFixed(3)} ∠ ${gammaPhase.toStringAsFixed(1)}°', color: const Color(0xFF00FFFF)),
          _buildHUDText('VSWR (驻波比):', vswr > 99 ? '> 99.0' : vswr.toStringAsFixed(2), color: vswr < 2.0 ? const Color(0xFF00FF00) : Colors.redAccent),
          _buildHUDText('RL (回波损耗):', returnLoss > 99 ? '> 99 dB' : '${returnLoss.toStringAsFixed(1)} dB'),
        ],
      ),
    );
  }

  Widget _buildHUDText(String label, String value, {Color? color}) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 4),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          SizedBox(width: 120, child: Text(label, style: const TextStyle(color: Colors.white54, fontSize: 12, fontFamily: 'monospace'))),
          Text(value, style: TextStyle(color: color ?? Colors.white, fontSize: 13, fontWeight: FontWeight.bold, fontFamily: 'monospace')),
        ],
      ),
    );
  }

  Widget _buildControlPanel(double width) {
    return Container(
      width: width,
      decoration: BoxDecoration(
        color: Theme.of(context).cardColor,
        border: Border(right: BorderSide(color: const Color(0xFF00FF00).withValues(alpha: 0.1))),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Padding(
            padding: EdgeInsets.fromLTRB(24, 48, 24, 24),
            child: Text(
              '射频阻抗匹配矩阵',
              style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Color(0xFF00FF00), letterSpacing: 1.5),
            ),
          ),
          _buildSliders(),
          const Spacer(),
          Padding(
            padding: const EdgeInsets.all(24.0),
            child: SizedBox(
              width: double.infinity,
              child: OutlinedButton.icon(
                onPressed: () {
                  setState(() {
                    _loadResistance = 50.0;
                    _loadReactance = 0.0;
                    _matchingTrajectory.clear();
                    _recordTrajectory();
                  });
                },
                icon: const Icon(Icons.center_focus_strong, color: Color(0xFF00FF00)),
                label: const Text('系统中心点复位 (50Ω 理想匹配)', style: TextStyle(color: Color(0xFF00FF00))),
                style: OutlinedButton.styleFrom(
                  side: const BorderSide(color: Color(0xFF00FF00)),
                  padding: const EdgeInsets.symmetric(vertical: 16),
                ),
              ),
            ),
          )
        ],
      ),
    );
  }

  Widget _buildMobileHeader() {
    return Container(
      height: 80,
      padding: const EdgeInsets.only(top: 30, left: 20),
      alignment: Alignment.centerLeft,
      decoration: BoxDecoration(
        color: Theme.of(context).cardColor,
        border: Border(bottom: BorderSide(color: const Color(0xFF00FF00).withValues(alpha: 0.1))),
      ),
      child: const Text(
        'Smith Chart 终端',
        style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Color(0xFF00FF00), letterSpacing: 2.0),
      ),
    );
  }

  Widget _buildMobileBottomBar() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Theme.of(context).cardColor,
        border: Border(top: BorderSide(color: const Color(0xFF00FF00).withValues(alpha: 0.1))),
      ),
      child: _buildSliders(),
    );
  }

  Widget _buildSliders() {
    // 采用对数刻度控制 R 以实现从短路到开路的平滑过渡
    // slider_val_r: 01,映射到 R: 10^(slider_val_r * 4 - 1) -> 0.11000 Ohms
    double sliderValR = (math.log(_loadResistance) / math.ln10 + 1) / 4.0;
    sliderValR = sliderValR.clamp(0.0, 1.0);

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 24),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              const Text('实部 Resistance (R)', style: TextStyle(color: Colors.white70)),
              Text('${_loadResistance.toStringAsFixed(1)} Ω', style: const TextStyle(color: Color(0xFF00FF00), fontFamily: 'monospace')),
            ],
          ),
          Slider(
            value: sliderValR,
            min: 0.0,
            max: 1.0,
            activeColor: const Color(0xFF00FF00),
            inactiveColor: Colors.green.withValues(alpha: 0.1),
            onChanged: (v) {
              double r = math.pow(10, v * 4 - 1).toDouble();
              _onImpedanceChanged(r, _loadReactance);
            },
          ),
          const SizedBox(height: 16),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              const Text('虚部 Reactance (X)', style: TextStyle(color: Colors.white70)),
              Text('${_loadReactance >= 0 ? '+' : ''}${_loadReactance.toStringAsFixed(1)} Ω', style: const TextStyle(color: Color(0xFF00FFFF), fontFamily: 'monospace')),
            ],
          ),
          Slider(
            value: _loadReactance,
            min: -500.0,
            max: 500.0,
            activeColor: const Color(0xFF00FFFF),
            inactiveColor: Colors.cyan.withValues(alpha: 0.1),
            onChanged: (v) {
              _onImpedanceChanged(_loadResistance, v);
            },
          ),
        ],
      ),
    );
  }
}

// ==========================================
// 史密斯极坐标圆图渲染引擎
// ==========================================
class SmithChartPainter extends CustomPainter {
  final double z0;
  final ComplexImpedance currentLoad;
  final List<ComplexImpedance> trajectory;
  final double sweepAngle; // 雷达扫描线角度

  SmithChartPainter({
    required this.z0,
    required this.currentLoad,
    required this.trajectory,
    required this.sweepAngle,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final chartRadius = math.min(size.width, size.height) / 2 * 0.85;

    // 0. 保存画板状态并切割主图区域
    canvas.save();
    
    // 1. 绘制极坐标扫描雷达余辉 (Sweep Radar Effect)
    _drawRadarSweep(canvas, center, chartRadius);

    // 2. 截取整个史密斯圆图的主轮廓边界,防止电抗圆越界溢出
    final chartRect = Rect.fromCircle(center: center, radius: chartRadius);
    canvas.clipPath(Path()..addOval(chartRect));
    
    // 3. 绘制等电阻圆 (r-circles) 和 等电抗圆 (x-circles)
    _drawConstantResistanceCircles(canvas, center, chartRadius);
    _drawConstantReactanceCircles(canvas, center, chartRadius);
    
    // 4. 绘制阻抗主轴
    final axisPaint = Paint()
      ..color = const Color(0xFF00FF00).withValues(alpha: 0.5)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1.0;
    canvas.drawLine(Offset(center.dx - chartRadius, center.dy), Offset(center.dx + chartRadius, center.dy), axisPaint);

    // 取消剪切区域,以便绘制边界刻度与指示点
    canvas.restore();
    
    // 5. 绘制外边界光晕
    final borderPaint = Paint()
      ..color = const Color(0xFF00FF00)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2.0;
    canvas.drawCircle(center, chartRadius, borderPaint);
    
    final borderGlow = Paint()
      ..color = const Color(0xFF00FF00).withValues(alpha: 0.2)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 6.0
      ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4.0);
    canvas.drawCircle(center, chartRadius, borderGlow);

    // 6. 绘制匹配演化轨迹 (Impedance Trajectory Locus)
    _drawTrajectory(canvas, center, chartRadius);

    // 7. 绘制当前驻点 (Current Impedance Point)
    _drawCurrentImpedance(canvas, center, chartRadius);
  }
  
  void _drawRadarSweep(Canvas canvas, Offset center, double radius) {
    final sweepPaint = Paint()
      ..shader = SweepGradient(
        center: Alignment.center,
        startAngle: sweepAngle - math.pi / 4,
        endAngle: sweepAngle,
        colors: [
          const Color(0xFF00FF00).withValues(alpha: 0.0),
          const Color(0xFF00FF00).withValues(alpha: 0.15),
        ],
      ).createShader(Rect.fromCircle(center: center, radius: radius))
      ..style = PaintingStyle.fill;
      
    // 旋转画板到雷达扫描角度
    canvas.save();
    canvas.translate(center.dx, center.dy);
    canvas.drawCircle(Offset.zero, radius, sweepPaint);
    
    // 扫描导线
    final linePaint = Paint()
      ..color = const Color(0xFF00FF00).withValues(alpha: 0.8)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1.0;
    canvas.drawLine(Offset.zero, Offset(radius * math.cos(sweepAngle), radius * math.sin(sweepAngle)), linePaint);
    canvas.restore();
  }

  void _drawConstantResistanceCircles(Canvas canvas, Offset center, double R) {
    final rPaint = Paint()
      ..color = const Color(0xFF00FF00).withValues(alpha: 0.25)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1.0;
      
    // 归一化电阻值列表 r = R / Z0
    final List<double> rValues = [0.0, 0.2, 0.5, 1.0, 2.0, 5.0];
    
    for (final r in rValues) {
      // 史密斯圆图上等电阻圆的极坐标:
      // 圆心在 U 轴上: u = r / (r + 1), v = 0
      // 物理半径为: radius = 1 / (r + 1)
      final centerU = r / (r + 1.0);
      final physicalRadius = 1.0 / (r + 1.0) * R;
      
      // 映射到 Canvas 像素系
      final circleCenter = Offset(center.dx + centerU * R, center.dy);
      canvas.drawCircle(circleCenter, physicalRadius, rPaint);
      
      // 添加文本标签 (仅在 r=1.0 中心点和其他特定点)
      if (r == 1.0 || r == 0.2 || r == 2.0) {
        _drawLabel(canvas, '${r}Z₀', Offset(center.dx + (centerU - 1.0 / (r + 1.0)) * R + 4, center.dy + 4));
      }
    }
  }

  void _drawConstantReactanceCircles(Canvas canvas, Offset center, double R) {
    final xPaint = Paint()
      ..color = const Color(0xFF00FFFF).withValues(alpha: 0.25)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1.0;
      
    // 归一化电抗值列表 x = X / Z0
    final List<double> xValues = [0.2, 0.5, 1.0, 2.0, 5.0];
    
    for (final x in xValues) {
      for (final sign in [1.0, -1.0]) {
        final currentX = x * sign;
        // 史密斯圆图上等电抗圆的极坐标:
        // 圆心位置: u = 1.0, v = 1 / x
        // 物理半径: radius = 1 / |x|
        final centerV = 1.0 / currentX;
        final physicalRadius = (1.0 / currentX.abs()) * R;
        
        // 映射到 Canvas 像素系 (v 轴在 UI 中方向朝上,故使用减法)
        final circleCenter = Offset(center.dx + R, center.dy - centerV * R);
        
        // 由于之前已经通过 canvas.clipPath(chartRect) 限定了画板范围,
        // 这里可以直接绘制完整的圆,超出大圆(无源负载区)的部分会自动被裁切掉,这是极其优雅的光栅处理手法
        canvas.drawCircle(circleCenter, physicalRadius, xPaint);
      }
    }
  }

  void _drawTrajectory(Canvas canvas, Offset center, double R) {
    if (trajectory.length < 2) return;

    final path = Path();
    for (int i = 0; i < trajectory.length; i++) {
      final gamma = trajectory[i].getReflectionCoefficient(z0);
      final pt = Offset(center.dx + gamma.dx * R, center.dy - gamma.dy * R);
      if (i == 0) {
        path.moveTo(pt.dx, pt.dy);
      } else {
        path.lineTo(pt.dx, pt.dy);
      }
    }

    // 绘制轨迹线 (使用透明度渐变尾迹效果难以直接在 Path 上实现,使用纯色加高斯模糊替代)
    final trailPaint = Paint()
      ..color = const Color(0xFF00FFFF).withValues(alpha: 0.4)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2.5
      ..strokeJoin = StrokeJoin.round;
    canvas.drawPath(path, trailPaint);
  }

  void _drawCurrentImpedance(Canvas canvas, Offset center, double R) {
    // 根据负载阻抗计算反射系数 Gamma (即中心极坐标 u, v)
    final gamma = currentLoad.getReflectionCoefficient(z0);
    
    // Canvas 坐标转换
    // 注意:微波工程中 v 轴(电抗轴)正向向上,而 Flutter Canvas 的 Y 轴正向向下
    final point = Offset(center.dx + gamma.dx * R, center.dy - gamma.dy * R);

    // 绘制光晕
    final glowPaint = Paint()
      ..color = Colors.redAccent
      ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 8.0);
    canvas.drawCircle(point, 10.0, glowPaint);

    // 绘制核心光点
    final corePaint = Paint()
      ..color = Colors.white
      ..style = PaintingStyle.fill;
    canvas.drawCircle(point, 4.0, corePaint);
    
    // 绘制导引准星
    final crossPaint = Paint()
      ..color = Colors.redAccent
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1.0;
    canvas.drawLine(Offset(point.dx - 15, point.dy), Offset(point.dx + 15, point.dy), crossPaint);
    canvas.drawLine(Offset(point.dx, point.dy - 15), Offset(point.dx, point.dy + 15), crossPaint);
  }

  void _drawLabel(Canvas canvas, String text, Offset position) {
    final tp = TextPainter(
      text: TextSpan(
        text: text,
        style: const TextStyle(color: Colors.white70, fontSize: 10, fontFamily: 'monospace'),
      ),
      textDirection: TextDirection.ltr,
    );
    tp.layout();
    tp.paint(canvas, position);
  }

  @override
  bool shouldRepaint(covariant SmithChartPainter oldDelegate) {
    return oldDelegate.z0 != z0 || 
           oldDelegate.currentLoad != currentLoad ||
           oldDelegate.sweepAngle != sweepAngle ||
           oldDelegate.trajectory.length != trajectory.length;
  }
}

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐