NavisworksTransport/src/UI/WPF/Views/PathAnalysisDialog.xaml.cs

587 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Shapes;
using NavisworksTransport.Core;
using NavisworksTransport.Core.Models;
using NavisworksTransport.UI.WPF.ViewModels;
namespace NavisworksTransport.UI.WPF.Views
{
/// <summary>
/// PathAnalysisDialog.xaml 的交互逻辑
/// 路径规划分析对话框 - 支持多维度评分和同终点组分析(非模态窗口)
/// </summary>
public partial class PathAnalysisDialog : Window
{
#region
private static PathAnalysisDialog _currentInstance = null;
private static readonly object _lock = new object();
#endregion
public PathAnalysisDialog()
{
try
{
InitializeComponent();
var viewModel = new ViewModels.PathAnalysisViewModel();
DataContext = viewModel;
// 订阅分析完成事件,绘制图表
viewModel.AnalysisCompleted += OnAnalysisCompleted;
LogManager.Info("PathAnalysisDialog初始化完成");
Title = $"路径规划分析 - 多路径对比 [{DateTime.Now:MM-dd HH:mm}]";
}
catch (Exception ex)
{
LogManager.Error($"初始化PathAnalysisDialog失败: {ex.Message}", ex);
}
}
/// <summary>
/// 分析完成时绘制图表
/// </summary>
private void OnAnalysisCompleted(object sender, AnalysisCompletedEventArgs e)
{
try
{
Dispatcher.Invoke(() =>
{
DrawBarChart(e.EndpointGroups);
DrawRadarChart(e.SelectedGroup);
});
}
catch (Exception ex)
{
LogManager.Error($"绘制图表失败: {ex.Message}");
}
}
/// <summary>
/// 绘制柱状图
/// </summary>
private void DrawBarChart(List<EndpointGroupViewModel> groups)
{
if (groups == null || groups.Count == 0) return;
var canvas = BarChartCanvas;
canvas.Children.Clear();
// 收集所有路径数据
var allPaths = new List<PathDetailedAnalysis>();
foreach (var group in groups)
{
allPaths.AddRange(group.PathAnalyses);
}
if (allPaths.Count == 0) return;
// 限制显示数量
var displayPaths = allPaths.Take(8).ToList();
double canvasWidth = canvas.Width;
double canvasHeight = canvas.Height;
double marginLeft = 60;
double marginRight = 20;
double marginTop = 30;
double marginBottom = 50;
double chartWidth = canvasWidth - marginLeft - marginRight;
double chartHeight = canvasHeight - marginTop - marginBottom;
// 绘制坐标轴
DrawAxis(canvas, marginLeft, marginTop, chartWidth, chartHeight);
// 四个维度的颜色
var colors = new[] {
Color.FromRgb(40, 167, 69), // 安全性 - 绿
Color.FromRgb(0, 123, 255), // 效率 - 蓝
Color.FromRgb(255, 193, 7), // 转弯 - 黄
Color.FromRgb(23, 162, 184) // 直达性 - 青
};
var dimensionNames = new[] { "安全", "效率", "转弯", "直达" };
int barGroupCount = displayPaths.Count;
double groupWidth = chartWidth / barGroupCount;
double barWidth = groupWidth * 0.7 / 4; // 4个柱子
for (int i = 0; i < displayPaths.Count; i++)
{
var analysis = displayPaths[i];
double groupX = marginLeft + i * groupWidth + groupWidth * 0.15;
double[] scores = new[] { analysis.SafetyScore, analysis.EfficiencyScore,
analysis.TurnDifficultyScore, analysis.TortuosityScore };
// 绘制每个维度的柱子
for (int j = 0; j < 4; j++)
{
double barHeight = scores[j] / 100.0 * chartHeight;
double barX = groupX + j * barWidth;
double barY = marginTop + chartHeight - barHeight;
var rect = new Rectangle
{
Width = barWidth - 2,
Height = barHeight,
Fill = new SolidColorBrush(colors[j]),
RadiusX = 2,
RadiusY = 2
};
Canvas.SetLeft(rect, barX);
Canvas.SetTop(rect, barY);
canvas.Children.Add(rect);
// 在柱子上方显示分数
var scoreText = new TextBlock
{
Text = scores[j].ToString("F0"),
FontSize = 8,
FontWeight = FontWeights.Bold,
Foreground = new SolidColorBrush(colors[j]),
TextAlignment = TextAlignment.Center,
Width = barWidth
};
Canvas.SetLeft(scoreText, barX);
Canvas.SetTop(scoreText, barY - 12);
canvas.Children.Add(scoreText);
}
// 路径名称(水平显示)
var label = new TextBlock
{
Text = analysis.RouteName,
FontSize = 9,
Foreground = new SolidColorBrush(Colors.Gray),
TextAlignment = TextAlignment.Center,
Width = groupWidth * 0.85,
TextWrapping = TextWrapping.Wrap
};
Canvas.SetLeft(label, groupX);
Canvas.SetTop(label, marginTop + chartHeight + 5);
canvas.Children.Add(label);
}
// 绘制图例
DrawLegend(canvas, dimensionNames, colors, canvasWidth - 150, 5);
}
/// <summary>
/// 绘制雷达图
/// </summary>
private void DrawRadarChart(EndpointGroupViewModel selectedGroup)
{
if (selectedGroup?.PathAnalyses == null || selectedGroup.PathAnalyses.Count == 0) return;
var canvas = RadarChartCanvas;
canvas.Children.Clear();
var analyses = selectedGroup.PathAnalyses.Take(5).ToList();
double canvasWidth = canvas.Width;
double canvasHeight = canvas.Height;
double centerX = canvasWidth / 2;
double centerY = canvasHeight / 2 + 10;
double radius = Math.Min(canvasWidth, canvasHeight) / 2 - 30;
var dimensions = new[] { "安全性", "效率", "转弯难度", "直达性" };
int dimensionCount = dimensions.Length;
// 绘制网格
for (int i = 1; i <= 5; i++)
{
double r = radius * i / 5;
var polygon = new Polygon
{
Stroke = new SolidColorBrush(Color.FromRgb(200, 200, 200)),
StrokeThickness = 0.5,
Fill = Brushes.Transparent
};
for (int j = 0; j < dimensionCount; j++)
{
double angle = j * 2 * Math.PI / dimensionCount - Math.PI / 2;
double x = centerX + r * Math.Cos(angle);
double y = centerY + r * Math.Sin(angle);
polygon.Points.Add(new Point(x, y));
}
canvas.Children.Add(polygon);
}
// 绘制轴线和标签
for (int i = 0; i < dimensionCount; i++)
{
double angle = i * 2 * Math.PI / dimensionCount - Math.PI / 2;
double x = centerX + radius * Math.Cos(angle);
double y = centerY + radius * Math.Sin(angle);
// 轴线
var line = new Line
{
X1 = centerX,
Y1 = centerY,
X2 = x,
Y2 = y,
Stroke = new SolidColorBrush(Color.FromRgb(200, 200, 200)),
StrokeThickness = 0.5
};
canvas.Children.Add(line);
// 标签
var labelX = centerX + (radius + 20) * Math.Cos(angle);
var labelY = centerY + (radius + 20) * Math.Sin(angle);
var label = new TextBlock
{
Text = dimensions[i],
FontSize = 10,
FontWeight = FontWeights.Bold,
Foreground = new SolidColorBrush(Color.FromRgb(44, 90, 160))
};
Canvas.SetLeft(label, labelX - 15);
Canvas.SetTop(label, labelY - 8);
canvas.Children.Add(label);
}
// 绘制数据
var pathColors = new[] {
Colors.Red, Colors.Blue, Colors.Green, Colors.Orange, Colors.Purple
};
for (int i = 0; i < analyses.Count; i++)
{
var analysis = analyses[i];
double[] scores = new[] { analysis.SafetyScore, analysis.EfficiencyScore,
analysis.TurnDifficultyScore, analysis.TortuosityScore };
var polygon = new Polygon
{
Stroke = new SolidColorBrush(pathColors[i % pathColors.Length]),
StrokeThickness = analysis.IsBestInGroup ? 2.5 : 1.5,
Fill = new SolidColorBrush(pathColors[i % pathColors.Length]) { Opacity = 0.1 }
};
for (int j = 0; j < dimensionCount; j++)
{
double angle = j * 2 * Math.PI / dimensionCount - Math.PI / 2;
double r = radius * scores[j] / 100;
double x = centerX + r * Math.Cos(angle);
double y = centerY + r * Math.Sin(angle);
polygon.Points.Add(new Point(x, y));
}
canvas.Children.Add(polygon);
// 数据点
for (int j = 0; j < dimensionCount; j++)
{
double angle = j * 2 * Math.PI / dimensionCount - Math.PI / 2;
double r = radius * scores[j] / 100;
double x = centerX + r * Math.Cos(angle);
double y = centerY + r * Math.Sin(angle);
var ellipse = new Ellipse
{
Width = 4,
Height = 4,
Fill = new SolidColorBrush(pathColors[i % pathColors.Length])
};
Canvas.SetLeft(ellipse, x - 2);
Canvas.SetTop(ellipse, y - 2);
canvas.Children.Add(ellipse);
}
}
}
private void DrawAxis(Canvas canvas, double left, double top, double width, double height)
{
// Y轴
var yAxis = new Line
{
X1 = left,
Y1 = top,
X2 = left,
Y2 = top + height,
Stroke = new SolidColorBrush(Colors.Gray),
StrokeThickness = 1
};
canvas.Children.Add(yAxis);
// X轴
var xAxis = new Line
{
X1 = left,
Y1 = top + height,
X2 = left + width,
Y2 = top + height,
Stroke = new SolidColorBrush(Colors.Gray),
StrokeThickness = 1
};
canvas.Children.Add(xAxis);
// Y轴刻度和标签
for (int i = 0; i <= 5; i++)
{
double y = top + height - i * height / 5;
var tick = new Line
{
X1 = left - 3,
Y1 = y,
X2 = left,
Y2 = y,
Stroke = new SolidColorBrush(Colors.Gray),
StrokeThickness = 1
};
canvas.Children.Add(tick);
var label = new TextBlock
{
Text = (i * 20).ToString(),
FontSize = 9,
Foreground = new SolidColorBrush(Colors.Gray)
};
Canvas.SetLeft(label, left - 20);
Canvas.SetTop(label, y - 6);
canvas.Children.Add(label);
}
}
private void DrawLegend(Canvas canvas, string[] names, Color[] colors, double x, double y)
{
for (int i = 0; i < names.Length; i++)
{
var rect = new Rectangle
{
Width = 10,
Height = 10,
Fill = new SolidColorBrush(colors[i]),
RadiusX = 2,
RadiusY = 2
};
Canvas.SetLeft(rect, x + i * 35);
Canvas.SetTop(rect, y);
canvas.Children.Add(rect);
var label = new TextBlock
{
Text = names[i],
FontSize = 9,
Foreground = new SolidColorBrush(Colors.Gray)
};
Canvas.SetLeft(label, x + i * 35 + 12);
Canvas.SetTop(label, y - 1);
canvas.Children.Add(label);
}
}
/// <summary>
/// 显示路径分析窗口(单例模式,非模态)
/// </summary>
public static PathAnalysisDialog ShowAnalysis(Window owner = null)
{
try
{
lock (_lock)
{
// 如果当前已有实例且仍然打开,则激活窗口
if (_currentInstance != null)
{
try
{
if (_currentInstance.IsLoaded)
{
_currentInstance.Activate();
_currentInstance.Focus();
LogManager.Info("PathAnalysisDialog已激活现有窗口");
return _currentInstance;
}
}
catch (Exception)
{
// 如果访问失败,认为窗口已关闭,创建新实例
_currentInstance = null;
}
}
// 创建新实例
var dialog = new PathAnalysisDialog();
if (owner != null)
{
dialog.Owner = owner;
}
// 设置为当前实例
_currentInstance = dialog;
// 使用Show()非模态显示,允许与主窗口同时操作
dialog.Show();
LogManager.Info("PathAnalysisDialog已以非模态方式显示");
return dialog;
}
}
catch (Exception ex)
{
LogManager.Error($"显示路径分析窗口失败: {ex.Message}");
return null;
}
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
try
{
LogManager.Info("用户关闭路径分析对话框");
this.Close();
}
catch (Exception ex)
{
LogManager.Error($"关闭PathAnalysisDialog时发生错误: {ex.Message}", ex);
}
}
protected override void OnClosed(EventArgs e)
{
try
{
LogManager.Info("PathAnalysisDialog已关闭");
// 清理单例引用
lock (_lock)
{
if (_currentInstance == this)
{
_currentInstance = null;
}
}
base.OnClosed(e);
}
catch (Exception ex)
{
LogManager.Error($"PathAnalysisDialog清理过程中发生错误: {ex.Message}", ex);
}
}
}
#region
/// <summary>
/// 分数转颜色转换器
/// </summary>
public class ScoreToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double score)
{
if (score >= 80) return new SolidColorBrush(Colors.Green);
if (score >= 60) return new SolidColorBrush(Colors.Orange);
return new SolidColorBrush(Colors.Red);
}
return new SolidColorBrush(Colors.Gray);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
/// <summary>
/// 优先级转颜色转换器
/// </summary>
public class PriorityToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int priority)
{
if (priority >= 4) return new SolidColorBrush(Color.FromRgb(220, 53, 69)); // 红色 - 高优先级
if (priority >= 3) return new SolidColorBrush(Color.FromRgb(255, 193, 7)); // 黄色 - 中优先级
return new SolidColorBrush(Color.FromRgb(40, 167, 69)); // 绿色 - 低优先级
}
return new SolidColorBrush(Colors.Gray);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
/// <summary>
/// 策略描述转换器
/// </summary>
public class StrategyDescriptionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string strategy)
{
switch (strategy)
{
case "安全优先":
return "优先选择无碰撞路径适合精密组件运输安全权重50%";
case "效率优先":
return "优先选择最短路径适合紧急任务效率权重40%";
case "平衡模式":
return "综合考虑安全与效率,通用场景推荐(默认)";
default:
return "请选择分析策略";
}
}
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
/// <summary>
/// 布尔转背景色转换器
/// </summary>
public class BoolToBackgroundConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool isSelected && isSelected)
{
return new SolidColorBrush(Color.FromRgb(230, 240, 255)); // 选中时的浅蓝色
}
return new SolidColorBrush(Colors.Transparent);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
/// <summary>
/// Null转Visibility转换器
/// </summary>
public class NullToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value == null ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
#endregion
}