UILabel text margin

本文介绍如何通过UILabel的子类化并重写drawTextInRect:与textRectForBounds:limitedToNumberOfLines:方法来实现UILabel中文本的内边距调整。此方法允许开发者指定文本相对于UILabel背景的位置偏移,例如将文本向左内缩10像素。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Q:

I think I'm missing something really simple here but I'm looking to set the left inset/margin of a UILabel and can't seem to see a method to do so. The label has a background set so it would be ideal to inset the text by 10px or so on the left hand side. Any help would be appreciated.


A:

solved this by subclassingUILabeland overridingdrawTextInRect:like this:

- (void)drawTextInRect:(CGRect)rect {
    UIEdgeInsets insets = {0, 5, 0, 5};
    return [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}

As you might have gathered, this is an adaptation oftc.'s answer. It has two advantages over that one:

  1. there's no need to trigger it by sending asizeToFitmessage
  2. it leaves the label frame alone - handy if your label has a background and you don't want that to shrink

A:

I think you should override bothtextRectForBounds:limitedToNumberOfLines:anddrawTextInRect:like this:

- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines
{
    return CGRectInset(bounds, MARGIN, MARGIN);
}

- (void)drawTextInRect:(CGRect)rect
{
    [super drawTextInRect: CGRectInset(self.bounds, MARGIN, MARGIN)];
}

嗯,这个代码中 ,当前特征值的画图好像没有 显示出来,请问是被覆盖了么,请给出完整修改代码 classdef SVMSpeechRecognitionGUI < matlab.apps.AppBase % 属性 properties (Access = public) UIFigure matlab.ui.Figure RecordButton matlab.ui.control.Button PlayButton matlab.ui.control.Button RecognizeButton matlab.ui.control.Button ResultLabel matlab.ui.control.Label AudioAxes matlab.ui.control.UIAxes FeatureAxes matlab.ui.control.UIAxes Model % SVM模型 FeatureMean % 特征均值 FeatureStd % 特征标准差 AudioData % 录制的音频数据 SampleRate = 16000; % 采样率 IsRecording = false; % 录音状态 Recorder % 录音对象 Categories = {'0','1','2','3','4','5','6','极7','8','9','+','-','×','÷','='}; % 类别标签 FeatureLegend matlab.ui.control.Label % 特征图例标签 ClassFeatures % 各类别平均特征(用于可视化) end % 公共构造函数 methods (Access = public) function app = SVMSpeechRecognitionGUI(model) % 加载模型和标准化参数 try modelData = load('svm_model.mat'); app.Model = modelData.model; app.FeatureMean = modelData.featureMean; app.FeatureStd = modelData.featureStd; % 尝试加载类别平均特征(用于可视化) if isfield(modelData, 'classFeatures') app.ClassFeatures = modelData.classFeatures; else app.ClassFeatures = []; end catch ME % 如果加载失败,使用传入的模型 if exist('model', 'var') && ~isempty(model) app.Model = model; % 尝试从文件加载标准化参数 if exist('svm_model.mat', 'file') modelData = load('svm_model.mat'); app.FeatureMean = modelData.featureMean; app.FeatureStd = modelData.featureStd; if isfield(modelData, 'classFeatures') app.ClassFeatures = modelData.classFeatures; end else % 默认标准化参数 app.FeatureMean = zeros(1, 40); app.FeatureStd = ones(1, 40); app.ClassFeatures = []; warning('标准化参数未找到,使用默认值'); end else uialert([], sprintf('加载模型失败: %s', ME.message), '错误'); delete(app); return; end end % 创建UI createComponents(app); % 注册关闭事件 app.UIFigure.CloseRequestFcn = @(~,~)closeRequest(app); % 初始化图形 initializePlots(app); % 显示UI app.UIFigure.Visible = 'on'; end end % 私有方法 methods (Access = private) % 窗口关闭请求函数 function closeRequest(app) % 停止录音并释放资源 if app.IsRecording && ~isempty(app.Recorder) && isrecording(app.Recorder) stop(app.Recorder); end % 删除UI delete(app.UIFigure); end % 初始化图形 function initializePlots(app) % 初始化音频波形图 plot(app.AudioAxes, 0, 0); title(app.AudioAxes, '等待录音...'); xlabel(app.AudioAxes, '时间(s)'); ylabel(app.AudioAxes, '幅度'); grid(app.AudioAxes, 'on'); % 初始化特征图 bar(app.FeatureAxes, zeros(1, 40)); title(app.FeatureAxes, '特征向量'); xlabel(app.FeatureAxes, '特征索引'); ylabel(app.FeatureAxes, '特征值'); grid(app.FeatureAxes, 'on'); % 设置特征图属性 app.FeatureAxes.XLabel.FontSize = 12; app.FeatureAxes.YLabel.FontSize = 12; app.FeatureAxes.FontSize = 10; app.FeatureAxes.XLim = [0, 41]; % 创建特征图例 featureTypes = {'均值', '标准差', '能量', '熵', '过零率', '最大值', '最小值', '峰度', '偏度', '中值'}; legendText = sprintf('特征类型:\n'); for i = 1:length(featureTypes) legendText = sprintf('%s%d. %s\n', legendText, i, featureTypes{i}); end app.FeatureLegend = uilabel(app.UIFigure, ... 'Position', [700, 50, 150, 120], ... 'Text', legendText, ... 'FontSize', 9, ... 'BackgroundColor', [0.95, 0.95, 0.95], ... 'Visible', 'off'); % 初始不可见 end % 创建UI组件 function createComponents(app) % 创建主窗口(增加高度以容纳更多内容) app.UIFigure = uifigure('Name', 'SVM语音识别系统', ... 'Position', [100 100 900 600]); % 创建录音按钮 app.RecordButton = uibutton(app.UIFigure, 'push', ... 'Text', '开始录音', ... 'Position', [50 550 100 30], ... 'ButtonPushedFcn', @(src,event)recordButtonPushed(app)); % 创建播放按钮 app.PlayButton = uibutton(app.UIFigure, 'push', ... 'Text', '播放录音', ... 'Position', [170 550 100 30], ... 'ButtonPushedFcn', @(src,event)playButtonPushed(app)); % 创建识别按钮 app.RecognizeButton = uibutton(app.UIFigure, 'push', ... 'Text', '识别语音', ... 'Position', [290 550 100 30], ... 'ButtonPushedFcn', @(src,event)recognizeButtonPushed(app)); % 创建结果显示标签 app.ResultLabel = uilabel(app.UIFigure, ... 'Position', [410 550 300 30], ... 'Text', '结果: ', ... 'FontSize', 16, ... 'FontWeight', 'bold'); % 创建音频波形图 app.AudioAxes = uiaxes(app.UIFigure, 'Position', [50 350 800 180]); title(app.AudioAxes, '音频波形'); xlabel(app.AudioAxes, '时间(s)'); ylabel(app.AudioAxes, '幅度'); grid(app.AudioAxes, 'on'); % 创建特征向量图(增加高度) app.FeatureAxes = uiaxes(app.UIFigure, 'Position', [50 100 800 220]); title(app.FeatureAxes, '特征向量'); xlabel(app.FeatureAxes, '特征索引'); ylabel(app.FeatureAxes, '特征值'); grid(app.FeatureAxes, 'on'); % 设置特征图属性 app.FeatureAxes.XLabel.FontSize = 12; app.FeatureAxes.YLabel.FontSize = 12; app.FeatureAxes.FontSize = 10; app.FeatureAxes.XLim = [0, 41]; end % 录音按钮回调(参考CNN方法改进) function recordButtonPushed(app) if ~app.IsRecording % 开始录音 app.RecordButton.Text = '停止录音'; app.IsRecording = true; app.AudioData = []; % 清空旧数据 % 创建录音对象 app.Recorder = audiorecorder(app.SampleRate, 16, 1); record(app.Recorder); % 更新状态 app.ResultLabel.Text = '录音中...'; app.ResultLabel.FontColor = [0 0 0]; % 黑色 % 隐藏特征图例 app.FeatureLegend.Visible = 'off'; % 实时更新波形(使用循环) while app.IsRecording && isrecording(app.Recorder) pause(0.1); % 更新间隔100ms try audioData = getaudiodata(app.Recorder); if ~isempty(audioData) t = (0:length(audioData)-1)/app.SampleRate; plot(app.AudioAxes, t, audioData); title(app.AudioAxes, sprintf('录音中 (时长: %.2fs)', t(end))); drawnow; end catch % 忽略绘图错误 end end % 录音结束后处理 app.RecordButton.Text = '开始录音'; app.IsRecording = false; % 确保Recorder有效 if ~isempty(app.Recorder) && isvalid(app.Recorder) stop(app.Recorder); % 获取音频数据 app.AudioData = getaudiodata(app.Recorder); else app.AudioData = []; end % 端点检测 if ~isempty(app.AudioData) app.AudioData = app.detectSpeechEndpoints(app.AudioData, app.SampleRate); % 显示最终波形 if ~isempty(app.AudioData) t = (0:length(app.AudioData)-1)/app.SampleRate; plot(app.AudioAxes, t, app.AudioData); title(app.AudioAxes, sprintf('录音波形 (时长: %.2fs)', t(end))); % 更新状态 app.ResultLabel.Text = '录音完成!'; app.ResultLabel.FontColor = [0 0.5 0]; % 绿色 else app.ResultLabel.Text = '录音失败或无声!'; app.ResultLabel.FontColor = [1 0 0]; % 红色 end else app.ResultLabel.Text = '录音失败!'; app.ResultLabel.FontColor = [1 0 0]; % 红色 end else % 停止录音 app.IsRecording = false; end end % 端点检测函数(基于CNN方法改进) function audioOut = detectSpeechEndpoints(~, audioIn, fs) % 简单端点检测算法 frameSize = round(0.025 * fs); % 25ms帧 frameStep = round(0.01 * fs); % 10ms步长 % 确保音频长度大于帧长 if length(audioIn) < frameSize audioOut = audioIn; % 返回原始音频 return; end % 计算短时能量 numFrames = floor((length(audioIn)-frameSize)/frameStep)+1; energy = zeros(1, numFrames); for i = 1:numFrames startIdx = (i-1)*frameStep+1; endIdx = startIdx + frameSize - 1; frame = audioIn(startIdx:endIdx); energy(i) = sum(frame.^2); end % 归一化能量 if max(energy) > 0 energy = energy / max(energy); else % 如果能量全为0,则返回原始音频 audioOut = audioIn; return; end % 设置阈值 threshold = 0.05; % 能量阈值 % 检测语音起点 startIdx = find(energy > threshold, 1, 'first'); % 检测语音终点 endIdx = find(energy > threshold, 1, 'last'); if isempty(startIdx) || isempty(endIdx) audioOut = []; % 无声 else % 扩展边界确保包含完整语音 startSample = max(1, (startIdx-5)*frameStep); % 提前5帧 endSample = min(length(audioIn), (endIdx+5)*frameStep+frameSize); % 延后5帧 % 提取有效语音段 audioOut = audioIn(startSample:endSample); end end % 播放按钮回调(参考CNN方法改进) function playButtonPushed(app) if ~isempty(app.AudioData) sound(app.AudioData, app.SampleRate); app.ResultLabel.Text = '播放中...'; app.ResultLabel.FontColor = [0 0.5 0]; % 绿色 % 计算播放时间 duration = length(app.AudioData)/app.SampleRate; startTime = tic; while toc(startTime) < duration % 实时更新进度 elapsed = toc(startTime); progress = min(elapsed/duration, 1); % 更新波形显示 t = (0:length(app.AudioData)-1)/app.SampleRate; plot(app.AudioAxes, t, app.AudioData); hold(app.AudioAxes, 'on'); plot(app.AudioAxes, [elapsed, elapsed], ylim(app.AudioAxes), 'r-', 'LineWidth', 2); hold(app.AudioAxes, 'off'); title(app.AudioAxes, sprintf('播放中 (进度: %.1f%%)', progress*100)); drawnow; end app.ResultLabel.Text = '播放完成!'; plot(app.AudioAxes, (0:length(app.AudioData)-1)/app.SampleRate, app.AudioData); title(app.AudioAxes, sprintf('录音波形 (时长: %.2fs)', duration)); else app.ResultLabel.Text = '无录音数据!'; app.ResultLabel.FontColor = [1 0 0]; % 红色 end end % 识别按钮回调(增强特征可视化) function recognizeButtonPushed(app) if isempty(app.AudioData) app.ResultLabel.Text = '请先录音!'; app.ResultLabel.FontColor = [1 0 0]; % 红色 return; end try % 特征提取 features = extractDWTFeatures(app, app.AudioData); % 显示特征向量 - 改进可视化 cla(app.FeatureAxes); % 清除之前的图形 % 标准化特征用于预测 normalizedFeatures = (features - app.FeatureMean) ./ app.FeatureStd; % 预测 predicted = predict(app.Model, normalizedFeatures); % 显示结果 resultText = ['识别结果: ', char(app.Categories{predicted})]; app.ResultLabel.Text = resultText; app.ResultLabel.FontColor = [0 0 1]; % 蓝色 % 绘制当前特征(蓝色) bar(app.FeatureAxes, features, 'FaceColor', [0.2 0.4 0.8], 'BarWidth', 0.6); hold(app.FeatureAxes, 'on'); % 如果可用,绘制该类别的平均特征(红色) if ~isempty(app.ClassFeatures) % 获取该类别对应的平均特征(注意:模型中的特征已经标准化,但这里我们显示原始量级) classMean = app.ClassFeatures(predicted, :) .* app.FeatureStd + app.FeatureMean; plot(app.FeatureAxes, 1:40, classMean, 'r-o', 'LineWidth', 2, 'MarkerSize', 5); % 添加图例 legend(app.FeatureAxes, {'当前特征', '类别平均特征'}, 'Location', 'best'); else legend(app.FeatureAxes, {'当前特征'}, 'Location', 'best'); end hold(app.FeatureAxes, 'off'); % 设置坐标轴属性 title(app.FeatureAxes, sprintf('%s的特征向量', resultText)); xlabel(app.FeatureAxes, '特征索引'); ylabel(app.FeatureAxes, '特征值'); grid(app.FeatureAxes, 'on'); % 添加特征组标签(D1, D2, D3, D4) yLim = ylim(app.FeatureAxes); labelY = yLim(1) - 0.05 * diff(yLim); for k = 1:4 text(app.FeatureAxes, (k-1)*10+5, labelY, ... sprintf('D%d', k), ... 'HorizontalAlignment', 'center', ... 'VerticalAlignment', 'top', ... 'FontSize', 11, ... 'FontWeight', 'bold'); end % 在特征图上添加特征类型标记 featureTypes = {'Mean','Std','Energy','Entropy','ZCR','Max','Min','Kurt','Skew','Med'}; for group = 0:3 for ftype = 1:10 idx = group*10 + ftype; text(app.FeatureAxes, idx, labelY + 0.01*diff(yLim), ... featureTypes{ftype}, ... 'HorizontalAlignment', 'center', ... 'VerticalAlignment', 'bottom', ... 'FontSize', 8, ... 'Rotation', 90); end end % 显示特征图例 app.FeatureLegend.Visible = 'on'; catch ME app.ResultLabel.Text = '识别失败!'; app.ResultLabel.FontColor = [1 0 0]; % 红色 uialert(app.UIFigure, sprintf('错误: %s', ME.message), '识别错误'); end end % DWT特征提取函数(增强版,防止NaN) function features = extractDWTFeatures(~, audio) % 空音频处理 if isempty(audio) || all(audio == 0) features = nan(1, 40); return; end % 确保音频长度合适 targetLength = 16000; % 1秒@16kHz if length(audio) > targetLength audio = audio(1:targetLength); elseif length(audio) < targetLength audio = [audio; zeros(targetLength-length(audio), 1)]; end % DWT特征提取 wavelet = 'db4'; level = 4; [C, L] = wavedec(audio, level, wavelet); % 提取特征 features = zeros(1, 40); % 40维特征向量 % 各层细节系数的统计特征 for k = 1:level d = detcoef(C, L, k); startIdx = (k-1)*10 + 1; % 安全特征计算 features(startIdx) = mean(d); % 均值 % 标准差(防除0) if numel(d) > 1 features(startIdx+1) = std(d); else features(startIdx+1) = 0; end features(startIdx+2) = sum(d.^2); % 能量 % 熵(防log(0)) if all(d == d(1)) || numel(d) < 2 features(startIdx+3) = 0; else [p, ~] = histcounts(d, 10, 'Normalization', 'probability'); p(p == 0) = []; % 移除零概率 features(startIdx+3) = -sum(p.*log2(p+eps)); end % 过零率 features(startIdx+4) = sum(abs(diff(sign(d)))); % 最大值/最小值 features(startIdx+5) = max(d); features(startIdx+6) = min(d); % 峰度(需要≥4个点) if numel(d) > 3 features(startIdx+7) = kurtosis(d); else features(startIdx+7) = 0; end % 偏度(需要≥3个点) if numel(d) > 2 features(startIdx+8) = skewness(d); else features(startIdx+8) = 0; end features(startIdx+9) = median(d); % 中值 end % 最终NaN检查(防止意外情况) if any(isnan(features)) warning('特征包含NaN,使用零填充'); features(isnan(features)) = 0; end end end end
06-23
优化下面的代码,不显示眼图分析,具体显示其他分析,给我优化后完整的代码:classdef TDMPerformanceAnalyzer < TDMSystem properties analysisFigure % 独立分析窗口 performanceMetrics % 扩展的性能指标 constellationAxes % 星座图坐标轴 spectrumAxes % 频谱分析坐标轴 eyeDiagramAxes % 眼图坐标轴 correlationAxes % 相关分析坐标轴 metricTable % 性能指标表格 systemMetricsLabel % 系统级指标标签 end methods function obj = TDMPerformanceAnalyzer(params) % 调用父类构造函数 obj = obj@TDMSystem(params); % 初始化扩展性能指标 obj.performanceMetrics = struct(... 'evm', [], ... % 误差矢量幅度 'throughput', [], ... % 系统吞吐量 'latency', [], ... % 系统延迟 'spectralEfficiency', [], ... % 频谱效率 'totalThroughput', [], ... % 系统总吞吐量 'overallSpectralEfficiency', [] ... % 总体频谱效率 ); end function obj = runPerformanceAnalysis(obj) % 运行基础仿真流程 obj = runSimulation(obj); % 计算扩展性能指标 obj = obj.calculateExtendedMetrics(); % 创建独立分析窗口 obj.createAnalysisWindow(); % 绘制综合性能分析 obj.plotComprehensiveAnalysis(); end function obj = calculateExtendedMetrics(obj) % 计算扩展性能指标 numSignals = obj.params.numSignals; obj.performanceMetrics.evm = zeros(1, numSignals); obj.performanceMetrics.throughput = zeros(1, numSignals); obj.performanceMetrics.latency = zeros(1, numSignals); obj.performanceMetrics.spectralEfficiency = zeros(1, numSignals); totalSymbols = 0; totalBits = 0; bandwidth = obj.params.fs; % 系统带宽 for i = 1:numSignals % 检查信号存在性 if size(obj.originalSignals, 1) < i || ... size(obj.syncedSignals, 1) < i || ... isempty(obj.originalSignals) || ... isempty(obj.syncedSignals) obj.performanceMetrics.evm(i) = NaN; obj.performanceMetrics.latency(i) = NaN; continue; end % 获取信号并确保行向量 orig = obj.originalSignals(i, :); decoded = obj.syncedSignals(i, :); orig = orig(:).'; % 强制转为行向量 decoded = decoded(:).'; % 强制转为行向量 % 确保信号长度匹配 minLen = min(length(orig), length(decoded)); if minLen < 10 obj.performanceMetrics.evm(i) = NaN; obj.performanceMetrics.latency(i) = NaN; continue; end % 截取相同长度部分 orig = orig(1:minLen); decoded = decoded(1:minLen); % 计算误差矢量幅度(EVM) evm = sqrt(mean(abs(orig - decoded).^2)) / ... sqrt(mean(abs(orig).^2)); obj.performanceMetrics.evm(i) = evm * 100; % 百分比 % 计算吞吐量 (bps) modType = obj.modulationParams.signalModulations{i}; bitsPerSymbol = obj.getBitsPerSymbol(modType); symbolRate = obj.params.fs / bitsPerSymbol; obj.performanceMetrics.throughput(i) = symbolRate * bitsPerSymbol; % 估计延迟 (ms) - 基于同步过程 [corr, lags] = xcorr(orig, decoded); if ~isempty(corr) [~, idx] = max(abs(corr)); delay = lags(idx) / obj.params.fs * 1000; % 转换为毫秒 obj.performanceMetrics.latency(i) = delay; else obj.performanceMetrics.latency(i) = NaN; end % 计算频谱效率 (bps/Hz) obj.performanceMetrics.spectralEfficiency(i) = ... obj.performanceMetrics.throughput(i) / bandwidth; % 统计总符号和比特数 if length(obj.modulatedSignals) >= i totalSymbols = totalSymbols + length(obj.modulatedSignals{i}); end if length(obj.encodedSignals) >= i totalBits = totalBits + length(obj.encodedSignals{i}); end end % 系统级指标 if totalBits > 0 && obj.params.duration > 0 obj.performanceMetrics.totalThroughput = totalBits / obj.params.duration; else obj.performanceMetrics.totalThroughput = 0; end obj.performanceMetrics.overallSpectralEfficiency = ... obj.performanceMetrics.totalThroughput / bandwidth; end function createAnalysisWindow(obj) % 创建独立分析窗口 (使用uifigure解决兼容性问题) obj.analysisFigure = uifigure('Name', 'TDM系统性能综合分析', ... 'Position', [100, 100, 1200, 850], ... 'Color', [0.95, 0.95, 0.95]); % 创建网格布局 (4行3列) gridLayout = uigridlayout(obj.analysisFigure, [4, 3]); gridLayout.RowHeight = {'1x', '1x', '0.5x', 40}; % 第4行固定高度 gridLayout.ColumnWidth = {'1x', '1x', '1x'}; gridLayout.Padding = [10, 10, 10, 10]; gridLayout.RowSpacing = 5; gridLayout.ColumnSpacing = 10; gridLayout.BackgroundColor = [0.95, 0.95, 0.95]; % === 星座图坐标轴 === obj.constellationAxes = uiaxes(gridLayout); obj.constellationAxes.Layout.Row = 1; obj.constellationAxes.Layout.Column = 1; title(obj.constellationAxes, '星座图分析'); xlabel(obj.constellationAxes, '同相分量 (I)'); ylabel(obj.constellationAxes, '正交分量 (Q)'); grid(obj.constellationAxes, 'on'); axis(obj.constellationAxes, 'equal'); % === 频谱分析坐标轴 === obj.spectrumAxes = uiaxes(gridLayout); obj.spectrumAxes.Layout.Row = 1; obj.spectrumAxes.Layout.Column = 2; title(obj.spectrumAxes, '频谱分析'); xlabel(obj.spectrumAxes, '频率 (kHz)'); ylabel(obj.spectrumAxes, '功率谱密度 (dB/Hz)'); grid(obj.spectrumAxes, 'on'); % === 眼图坐标轴 === obj.eyeDiagramAxes = uiaxes(gridLayout); obj.eyeDiagramAxes.Layout.Row = 1; obj.eyeDiagramAxes.Layout.Column = 3; title(obj.eyeDiagramAxes, '眼图分析'); xlabel(obj.eyeDiagramAxes, '时间 (符号周期)'); ylabel(obj.eyeDiagramAxes, '幅度'); grid(obj.eyeDiagramAxes, 'on'); % === 相关分析坐标轴 === obj.correlationAxes = uiaxes(gridLayout); obj.correlationAxes.Layout.Row = 2; obj.correlationAxes.Layout.Column = [1, 3]; title(obj.correlationAxes, '信号相关性分析'); xlabel(obj.correlationAxes, '滞后 (样本)'); ylabel(obj.correlationAxes, '相关系数'); grid(obj.correlationAxes, 'on'); % === 性能指标表格 === obj.metricTable = uitable(gridLayout); obj.metricTable.Layout.Row = 3; obj.metricTable.Layout.Column = [1, 3]; obj.metricTable.ColumnName = {'信号', 'BER', 'SNR (dB)', 'MSE', 'EVM (%)', ... '吞吐量 (bps)', '延迟 (ms)', '频谱效率 (bps/Hz)'}; obj.metricTable.RowName = {}; obj.metricTable.FontName = 'Consolas'; obj.metricTable.ColumnWidth = {60, 80, 80, 80, 80, 100, 80, 120}; % === 系统级指标标签 === obj.systemMetricsLabel = uilabel(gridLayout); obj.systemMetricsLabel.Layout.Row = 4; obj.systemMetricsLabel.Layout.Column = [1, 3]; obj.systemMetricsLabel.Text = '系统总吞吐量: 计算中...'; obj.systemMetricsLabel.HorizontalAlignment = 'center'; obj.systemMetricsLabel.BackgroundColor = [0.9, 0.9, 0.9]; obj.systemMetricsLabel.FontSize = 12; obj.systemMetricsLabel.FontWeight = 'bold'; end function plotComprehensiveAnalysis(obj) % 绘制星座图 plotConstellation(obj); % 绘制频谱分析 plotSpectrumAnalysis(obj); % 绘制眼图 plotEyeDiagram(obj); % 绘制信号相关性 plotSignalCorrelation(obj); % 更新性能指标表格 updateMetricTable(obj); % 更新系统级指标标签 if ~isempty(obj.performanceMetrics.totalThroughput) && ... ~isempty(obj.performanceMetrics.overallSpectralEfficiency) obj.systemMetricsLabel.Text = sprintf(... '系统总吞吐量: %.2f Mbps | 总频谱效率: %.2f bps/Hz', ... obj.performanceMetrics.totalThroughput/1e6, ... obj.performanceMetrics.overallSpectralEfficiency); else obj.systemMetricsLabel.Text = '系统指标计算失败'; end end function plotConstellation(obj) % 绘制第一个信号的星座图 signalIdx = 1; if length(obj.demuxSignals) >= signalIdx signal = obj.demuxSignals{signalIdx}; signal = signal(:); % 确保列向量 if length(signal) < 10 text(obj.constellationAxes, 0.5, 0.5, '样本不足', ... 'HorizontalAlignment', 'center', 'Units', 'normalized'); return; end % 提取实部和虚部 I = real(signal); Q = imag(signal); % 确保维度匹配 if length(I) ~= length(Q) minLen = min(length(I), length(Q)); I = I(1:minLen); Q = Q(1:minLen); end % 绘制星座图 scatter(obj.constellationAxes, I, Q, 15, ... 'filled', 'MarkerFaceAlpha', 0.6, 'MarkerEdgeColor', 'none'); title(obj.constellationAxes, sprintf('信号%d星座图 (%s调制)', ... signalIdx, obj.modulationParams.signalModulations{signalIdx})); % 添加EVM信息 if ~isnan(obj.performanceMetrics.evm(signalIdx)) text(obj.constellationAxes, ... 0.05, 0.95, ... % 归一化位置 sprintf('EVM: %.2f%%', obj.performanceMetrics.evm(signalIdx)), ... 'Units', 'normalized', ... 'BackgroundColor', [1, 1, 0.8], ... 'Margin', 3, ... 'FontSize', 10, ... 'VerticalAlignment', 'top'); end else text(obj.constellationAxes, 0.5, 0.5, '无可用信号数据', ... 'HorizontalAlignment', 'center', 'Units', 'normalized'); end end function plotSpectrumAnalysis(obj) % 分析TDM信号的频谱 fs = obj.params.fs; if isempty(obj.tdmSignal) text(obj.spectrumAxes, 0.5, 0.5, '无TDM信号', ... 'HorizontalAlignment', 'center', 'Units', 'normalized'); return; end signal = obj.tdmSignal(:); % 强制列向量 if length(signal) < 1024 text(obj.spectrumAxes, 0.5, 0.5, '信号长度不足', ... 'HorizontalAlignment', 'center', 'Units', 'normalized'); return; end % 计算功率谱密度 [psd, freq] = pwelch(signal, hamming(1024), 512, 1024, fs); % 关键修复1: 确保频率和PSD长度匹配 minLen = min(length(freq), length(psd)); freq = freq(1:minLen); psd = psd(1:minLen); % 关键修复2: 验证向量方向一致性 freq = freq(:); % 强制列向量 psd = psd(:); % 强制列向量 % 绘制频谱 plot(obj.spectrumAxes, freq/1e3, 10*log10(psd), ... 'LineWidth', 1.5, 'Color', [0, 0.4470, 0.7410,0.3]); xlim(obj.spectrumAxes, [0, fs/2/1e3]); % 添加带宽信息 try bandwidth = obw(signal, fs)/1e3; % 占用带宽(kHz) text(obj.spectrumAxes, ... 0.05, 0.95, ... % 归一化位置 sprintf('占用带宽: %.2f kHz', bandwidth), ... 'Units', 'normalized', ... 'BackgroundColor', [1, 1, 0.8], ... 'Margin', 3, ... 'FontSize', 10, ... 'VerticalAlignment', 'top'); catch % 带宽计算失败时跳过 end end function plotEyeDiagram(obj) % 绘制第一个信号的眼图 signalIdx = 1; if length(obj.demuxSignals) >= signalIdx signal = obj.demuxSignals{signalIdx}; signal = real(signal(:)); % 强制列向量并取实部 % 设置眼图参数 samplesPerSymbol = 20; % 每符号采样数 numSymbols = 500; % 显示符号数 % 创建眼图 plotEye(obj.eyeDiagramAxes, signal, samplesPerSymbol, numSymbols); title(obj.eyeDiagramAxes, sprintf('信号%d眼图', signalIdx)); else text(obj.eyeDiagramAxes, 0.5, 0.5, '无解复用信号数据', ... 'HorizontalAlignment', 'center', 'Units', 'normalized'); end end function plotSignalCorrelation(obj) % 分析原始信号与解码信号的相关性 signalIdx = 1; if size(obj.originalSignals, 1) >= signalIdx && ... size(obj.syncedSignals, 1) >= signalIdx orig = obj.originalSignals(signalIdx, :); decoded = obj.syncedSignals(signalIdx, :); % 强制行向量并截取相同长度 orig = orig(:).'; decoded = decoded(:).'; minLen = min(length(orig), length(decoded)); if minLen < 10 text(obj.correlationAxes, 0.5, 0.5, '信号长度不足', ... 'HorizontalAlignment', 'center', 'Units', 'normalized'); return; end orig = orig(1:minLen); decoded = decoded(1:minLen); % 计算互相关 try [corr, lags] = xcorr(orig, decoded, 'normalized'); % 关键修复1: 检查空结果 if isempty(corr) || isempty(lags) text(obj.correlationAxes, 0.5, 0.5, '互相关计算失败', ... 'HorizontalAlignment', 'center', 'Units', 'normalized'); return; end % 关键修复2: 确保维度匹配 if length(lags) ~= length(corr) minLen = min(length(lags), length(corr)); lags = lags(1:minLen); corr = corr(1:minLen); end % 关键修复3: 统一向量方向 lags = lags(:); corr = corr(:); % 绘制相关结果 stem(obj.correlationAxes, lags, corr, ... 'filled', 'MarkerSize', 4, 'MarkerFaceColor', [0.3010, 0.7450, 0.9330]); title(obj.correlationAxes, '原始信号与解码信号互相关'); % 标记峰值位置 if ~isempty(corr) [maxCorr, maxIdx] = max(abs(corr)); maxLag = lags(maxIdx); hold(obj.correlationAxes, 'on'); plot(obj.correlationAxes, maxLag, maxCorr, 'ro', 'MarkerSize', 8, 'LineWidth', 1.5); text(obj.correlationAxes, maxLag, maxCorr, ... sprintf(' 延迟: %d 样本 (%.2f ms)', maxLag, maxLag/obj.params.fs*1000), ... 'VerticalAlignment', 'bottom', ... 'FontSize', 10, ... 'BackgroundColor', [1, 1, 0.8]); hold(obj.correlationAxes, 'off'); end catch e % 捕获并显示相关计算错误 warning('相关计算错误: %s (信号长度: %d)', E.message, length(orig)); text(obj.correlationAxes, 0.5, 0.5, '相关计算失败', ... 'HorizontalAlignment', 'center', 'Units', 'normalized'); end else text(obj.correlationAxes, 0.5, 0.5, '无信号相关性数据', ... 'HorizontalAlignment', 'center', 'Units', 'normalized'); end end function updateMetricTable(obj) % 准备表格数据 numSignals = obj.params.numSignals; tableData = cell(numSignals, 8); for i = 1:numSignals tableData{i, 1} = sprintf('信号 %d', i); % 处理BER数据 if i <= length(obj.performance.ber) && ~isempty(obj.performance.ber) tableData{i, 2} = sprintf('%.2e', obj.performance.ber(i)); else tableData{i, 2} = 'N/A'; end % 处理SNR数据 if i <= length(obj.performance.snr) && ~isempty(obj.performance.snr) tableData{i, 3} = sprintf('%.1f', obj.performance.snr(i)); else tableData{i, 3} = 'N/A'; end % 处理其他指标 tableData{i, 4} = sprintf('%.2e', obj.performance.mse(i)); tableData{i, 5} = formatNumber(obj.performanceMetrics.evm(i), '%.2f%%'); tableData{i, 6} = formatNumber(obj.performanceMetrics.throughput(i), 'bps'); tableData{i, 7} = formatNumber(obj.performanceMetrics.latency(i), '%.2f ms'); tableData{i, 8} = formatNumber(obj.performanceMetrics.spectralEfficiency(i), '%.4f bps/Hz'); end % 设置表格数据 obj.metricTable.Data = tableData; end end methods (Access = private) function plotEye(~, ax, signal, samplesPerSymbol, numSymbols) % 绘制眼图辅助函数 if isempty(signal) || length(signal) < samplesPerSymbol text(ax, 0.5, 0.5, '数据不足', ... 'HorizontalAlignment', 'center', 'Units', 'normalized'); return; end % 确保样本数可被整除 numCompleteSymbols = floor(length(signal) / samplesPerSymbol); if numCompleteSymbols < 1 text(ax, 0.5, 0.5, '样本不足', ... 'HorizontalAlignment', 'center', 'Units', 'normalized'); return; end % 限制符号数量,防止内存溢出 maxSymbols = min(numCompleteSymbols, numSymbols); numSamples = maxSymbols * samplesPerSymbol; signal = signal(1:numSamples); % 重塑为矩阵 (每行代表一个符号周期) try % 截断信号到整数倍长度 newLength = floor(length(signal) / samplesPerSymbol) * samplesPerSymbol; signal = signal(1:newLength); if newLength < samplesPerSymbol error('信号长度不足,无法形成完整的符号周期'); end eyeMatrix = reshape(signal, samplesPerSymbol, []); % 验证矩阵维度 if size(eyeMatrix, 1) ~= samplesPerSymbol || isempty(eyeMatrix) error('眼图矩阵重塑失败'); end % 创建时间向量 t = (0:(samplesPerSymbol-1)) / samplesPerSymbol; t = t(:); % 强制列向量 % 关键修复: 验证时间向量与矩阵行数匹配 if length(t) ~= size(eyeMatrix, 1) error('时间向量长度(%d)与矩阵行数(%d)不匹配', length(t), size(eyeMatrix, 1)); end % 绘制眼图轨迹 (最多100条) numTraces = min(100, size(eyeMatrix, 2)); if numTraces > 0 % 确保时间向量与数据匹配 X = repmat(t, 1, numTraces); Y = eyeMatrix(:, 1:numTraces); % 关键修复: 验证维度一致性 if ~isequal(size(X), size(Y)) error('X(%dx%d)和Y(%dx%d)维度不匹配', size(X,1), size(X,2), size(Y,1), size(Y,2)); end plot(ax, X, Y, ... 'Color', [0, 0.4470, 0.7410, 0.3]); % 计算并绘制平均值 hold(ax, 'on'); meanTrace = mean(eyeMatrix, 2); plot(ax, t, meanTrace, 'r', 'LineWidth', 2); hold(ax, 'off'); end % 设置坐标轴属性 xlim(ax, [0, 1]); grid(ax, 'on'); box(ax, 'on'); catch e % 捕获并显示错误 warning('眼图绘制错误: %s', E.message); text(ax, 0.5, 0.5, '眼图绘制失败', ... 'HorizontalAlignment', 'center', 'Units', 'normalized'); end end end end % 增强型辅助函数:格式化数字和单位 function str = formatNumber(num, formatSpec) if nargin < 2 formatSpec = ‘%.2f’; end if isempty(num) || isnan(num) str = 'N/A'; return; end % 处理带单位的格式化 if contains(formatSpec, 'bps') if num >= 1e9 str = sprintf([formatSpec ' Gbps'], num/1e9); elseif num >= 1e6 str = sprintf([formatSpec ' Mbps'], num/1e6); elseif num >= 1e3 str = sprintf([formatSpec ' Kbps'], num/1e3); else str = sprintf([formatSpec ' bps'], num); end else % 直接应用格式规范 str = sprintf(formatSpec, num); end end
最新发布
06-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值