开源鸿蒙跨平台Flutter开发:微波射频阻抗匹配系统-极坐标史密斯圆图与天线信号渲染架构
摘要: 本研究基于Flutter框架开发了一款轻量级移动端射频阻抗控制台,创新性地重构了传统史密斯圆图工具。通过底层CustomPaint画布与欧拉复数方程,系统实现了归一化复数阻抗在反射系数空间的实时映射,支持电压驻波比、回波损耗等关键参数计算。采用GPU加速渲染管线,系统突破了传统EDA软件局限,在移动端实现了高帧率极坐标渲染与交互式阻抗调试功能。核心算法包含复平面保角映射、对数刻度转换和光栅
欢迎加入开源鸿蒙跨平台社区: 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 0≤R<∞,−∞<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+Z0ZL−Z0
将阻抗进行归一化 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(r−1)+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+x2r2−1+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)=−20⋅log10∣Γ∣
这些数据决定了控制台上文本字体的生死颜色(驻波过大时闪烁警戒红)。
三、 系统架构域模型与渲染流水线
为了防御在调节 R R R 与 X X X 参数时导致的整个 APP 全局卡顿重建,我们部署了界限分明的隔离域架构。
3.1 实体物理与图层剥离结构 (Mermaid UML)
3.2 阻抗寻星与拖拽时序瀑布管线 (Flowchart)
四、 核心渲染图谱四维代码全息解剖
本节将剥去所有的 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(4v−1) 的指数级扭曲,能够让拖拽手感在 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: 0 到 1,映射到 R: 10^(slider_val_r * 4 - 1) -> 0.1 到 1000 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;
}
}
更多推荐

所有评论(0)