using System; using System.Linq; using System.Reactive.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Threading; using Masuit.Tools; namespace DispenserUI.Views.Controls; public partial class CoordinateViewer : UserControl { public static readonly StyledProperty CoordsProperty = AvaloniaProperty.Register(nameof(Coords)); public static readonly StyledProperty PathProperty = AvaloniaProperty.Register(nameof(Path)); public static readonly StyledProperty RotateProperty = AvaloniaProperty.Register(nameof(Rotate)); private readonly object _lock = new(); public CoordinateViewer() { InitializeComponent(); this.GetObservable(CoordsProperty).Throttle(TimeSpan.FromMilliseconds(1000)) .Subscribe(val => { if (val.IsNullOrEmpty()) return; Dispatcher.UIThread.InvokeAsync(InvalidateVisual); }); this.GetObservable(PathProperty).Throttle(TimeSpan.FromSeconds(1)) .Subscribe(val => { if (val.IsNullOrEmpty()) return; Dispatcher.UIThread.InvokeAsync(InvalidateVisual); }); } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } public double[][] Coords { get => GetValue(CoordsProperty); set => SetValue(CoordsProperty, value); } public double[][] Path { get => GetValue(PathProperty); set => SetValue(PathProperty, value); } public bool Rotate { get => GetValue(RotateProperty); set => SetValue(RotateProperty, value); } public override void Render(DrawingContext context) { lock (_lock) { base.Render(context); if (Coords.IsNullOrEmpty()) { return; } var coords = CloneCoords(Coords); var boundsWidth = DesiredSize.Width; var boundsHeight = DesiredSize.Height; if (Rotate) { // 将坐标进行旋转90° Rotate90Degrees(coords); } // 将坐标进行Y轴翻转 var points = coords.Select(point => new Point(-point[0], point[1])).ToList(); // 找到坐标中最小的X、Y坐标 var minX = points.Min(point => point.X); // 查找最小的Y var minY = points.Min(point => point.Y); // 找到最大的X var maxX = points.Max(point => point.X); // 找到最大的Y var maxY = points.Max(point => point.Y); var aspectRatio = (maxY - minY) / (maxX - minX); // 更改缩放比例,使得容器的宽高比尽量接近坐标的宽高比 var scaleBoundsWidth = boundsHeight / aspectRatio; var scaleX = scaleBoundsWidth / (maxX - minX); var scaleY = boundsHeight / (maxY - minY); var offsetX = (boundsWidth - (maxX - minX) * scaleX) / 2; foreach (var point in points) { // 将X 进行居中对齐 var x = (point.X - minX) * scaleX + offsetX; var y = (point.Y - minY) * scaleY; var rect = new Rect(x, y, 2, 5); var pen = new Pen(Brushes.Black, 0.5); context.DrawRectangle(pen, rect); } // 如果路径不为空的话,绘制路径 if (Path.IsNullOrEmpty()) return; // 计算出路径的坐标点 var paths = Path .Select(point => new Point((-point[0] - minX) * scaleX + offsetX, (point[1] - minY) * scaleY)).ToList(); var geometry = new StreamGeometry(); using (var ctx = geometry.Open()) { ctx.BeginFigure(paths[0], false); foreach (var point in paths.Skip(1)) { ctx.LineTo(point); } ctx.EndFigure(false); } // 绘制路径 context.DrawGeometry(null, new Pen(Brushes.Red, 1), geometry); // 在开始的第一个点标注为绿色 var startPoint = paths[0]; context.DrawEllipse(new SolidColorBrush(Colors.Green), new Pen(Brushes.Green, 1), new Rect(new Point(startPoint.X - 2, startPoint.Y - 2), new Size(5, 5))); // 最后一个点标注为黄色 var endPoint = paths[^1]; context.DrawEllipse(new SolidColorBrush(Colors.Yellow), new Pen(Brushes.Yellow, 1), new Rect(new Point(endPoint.X - 2, endPoint.Y - 2), new Size(5, 5))); } } private static void Rotate90Degrees(double[][] coords) { foreach (var coord in coords) { var tempX = coord[0]; var tempY = coord[1]; coord[0] = -tempY; coord[1] = tempX; } } private static double[][] CloneCoords(double[][] original) { var copy = new double[original.Length][]; // 逐行复制 for (var i = 0; i < original.Length; i++) { copy[i] = new double[original[i].Length]; Array.Copy(original[i], copy[i], original[i].Length); } return copy; } }