From 3b44ea6d8d9d8aadbf2665671b1fe4f90263fcca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90=C4=83ng=20Nguy=E1=BB=85n?= Date: Thu, 2 Oct 2025 11:02:44 +0700 Subject: [PATCH] update ruller --- .../Pages/Components/Mapping/MapView.razor | 215 ++++++++++++++++-- RobotApp/robot.db | Bin 167936 -> 167936 bytes 2 files changed, 190 insertions(+), 25 deletions(-) diff --git a/RobotApp.Client/Pages/Components/Mapping/MapView.razor b/RobotApp.Client/Pages/Components/Mapping/MapView.razor index afe0338..5900caf 100644 --- a/RobotApp.Client/Pages/Components/Mapping/MapView.razor +++ b/RobotApp.Client/Pages/Components/Mapping/MapView.razor @@ -68,8 +68,8 @@ private double MouseX; private double MouseY; - private double OriginX = 0; - private double OriginY = 0; + private double OriginX = 0; + private double OriginY = 0; private double WorldMouseX; private double WorldMouseY; @@ -80,6 +80,8 @@ private double CanvasTranslateX = 0; private double CanvasTranslateY = 0; + private const double RulerHeight = 20; + protected override async Task OnAfterRenderAsync(bool first_render) { await base.OnAfterRenderAsync(first_render); @@ -103,6 +105,10 @@ await using var ctx = await JS.GetContext2DAsync(CanvasRef); await ctx.ClearRectAsync(0, 0, CanvasWidth, CanvasHeight); + + // Draw rulers first (outside transform) + await DrawRulers(ctx); + await ctx.SaveAsync(); await ctx.TranslateAsync(CanvasTranslateX, CanvasTranslateY); @@ -114,6 +120,164 @@ await ctx.RestoreAsync(); } + private async Task DrawRulers(Context2D ctx) + { + double visibleWorldLeft = CanvasToWorldX(0); + double visibleWorldRight = CanvasToWorldX(CanvasWidth); + double visibleWorldTop = CanvasToWorldY(0); + double visibleWorldBottom = CanvasToWorldY(CanvasHeight); + + double scaleInterval = GetRulerScaleInterval(); + + await DrawXRuler(ctx, RulerHeight, visibleWorldLeft, visibleWorldRight, scaleInterval); + await DrawYRuler(ctx, RulerHeight, visibleWorldTop, visibleWorldBottom, scaleInterval); + } + + private double GetRulerScaleInterval() + { + double pixelsPerMeter = BASE_PIXELS_PER_METER * ZoomScale; + + if (pixelsPerMeter >= 400) return 0.1; + else if (pixelsPerMeter >= 200) return 0.2; + else if (pixelsPerMeter >= 100) return 0.5; + else if (pixelsPerMeter >= 50) return 1.0; + else if (pixelsPerMeter >= 25) return 2.0; + else if (pixelsPerMeter >= 12) return 5.0; + else if (pixelsPerMeter >= 6) return 10.0; + else return 20.0; + } + + private async Task DrawXRuler(Context2D ctx, double rulerHeight, double visibleWorldLeft, double visibleWorldRight, double scaleInterval) + { + await ctx.FillStyleAsync("rgba(240, 240, 240, 0.9)"); + await ctx.FillRectAsync(0, 0, CanvasWidth, rulerHeight); + + await ctx.StrokeStyleAsync("rgba(100, 100, 100, 0.8)"); + await ctx.LineWidthAsync(1); + await ctx.BeginPathAsync(); + await ctx.MoveToAsync(0, rulerHeight); + await ctx.LineToAsync(CanvasWidth, rulerHeight); + await ctx.StrokeAsync(); + + double startWorld = Math.Floor(visibleWorldLeft / scaleInterval) * scaleInterval; + double endWorld = Math.Ceiling(visibleWorldRight / scaleInterval) * scaleInterval; + + startWorld -= scaleInterval; + endWorld += scaleInterval; + + for (double worldX = startWorld; worldX <= endWorld; worldX += scaleInterval) + { + double canvasX = WorldToCanvasX(worldX); + + if (canvasX < -50 || canvasX > CanvasWidth + 50) continue; + + bool isMajorTick = IsNearMultiple(worldX, scaleInterval * 2) || Math.Abs(worldX) < 0.001; + double tickHeight = isMajorTick ? rulerHeight * 0.4 : rulerHeight * 0.2; + + await ctx.StrokeStyleAsync("rgba(60, 60, 60, 0.8)"); + await ctx.LineWidthAsync(1); + await ctx.BeginPathAsync(); + await ctx.MoveToAsync(canvasX, rulerHeight); + await ctx.LineToAsync(canvasX, rulerHeight - tickHeight); + await ctx.StrokeAsync(); + + if (isMajorTick && canvasX >= -20 && canvasX <= CanvasWidth + 20) + { + await ctx.SaveAsync(); + + await ctx.TranslateAsync(canvasX, rulerHeight - tickHeight - 8); + await ctx.ScaleAsync(1, -1); + + await ctx.FillStyleAsync("blue"); + await ctx.FontAsync("bold 10px Arial"); + await ctx.TextAlignAsync(TextAlign.Center); + + string labelText = FormatRulerLabel(worldX, scaleInterval); + await ctx.FillTextAsync(labelText, 0, 0); + + await ctx.RestoreAsync(); + } + } + } + + private async Task DrawYRuler(Context2D ctx, double rulerWidth, double visibleWorldTop, double visibleWorldBottom, double scaleInterval) + { + await ctx.FillStyleAsync("rgba(240, 240, 240, 0.9)"); + await ctx.FillRectAsync(0, 0, rulerWidth, CanvasHeight); + + await ctx.StrokeStyleAsync("rgba(100, 100, 100, 0.8)"); + await ctx.LineWidthAsync(1); + await ctx.BeginPathAsync(); + await ctx.MoveToAsync(rulerWidth, 0); + await ctx.LineToAsync(rulerWidth, CanvasHeight); + await ctx.StrokeAsync(); + + double startWorld = Math.Floor(visibleWorldTop / scaleInterval) * scaleInterval; + double endWorld = Math.Ceiling(visibleWorldBottom / scaleInterval) * scaleInterval; + + startWorld -= scaleInterval; + endWorld += scaleInterval; + + for (double worldY = startWorld; worldY <= endWorld; worldY += scaleInterval) + { + double canvasY = WorldToCanvasY(worldY); + + if (canvasY < -50 || canvasY > CanvasHeight + 50) continue; + + bool isMajorTick = IsNearMultiple(worldY, scaleInterval * 2) || Math.Abs(worldY) < 0.001; + double tickWidth = isMajorTick ? rulerWidth * 0.4 : rulerWidth * 0.2; + + await ctx.StrokeStyleAsync("rgba(60, 60, 60, 0.8)"); + await ctx.LineWidthAsync(1); + await ctx.BeginPathAsync(); + await ctx.MoveToAsync(rulerWidth, canvasY); + await ctx.LineToAsync(rulerWidth - tickWidth, canvasY); + await ctx.StrokeAsync(); + + if (isMajorTick && canvasY >= -20 && canvasY <= CanvasHeight + 20) + { + await ctx.SaveAsync(); + + await ctx.TranslateAsync(rulerWidth - tickWidth - 2, canvasY); + await ctx.ScaleAsync(1, -1); + await ctx.RotateAsync(-Math.PI / 2); + + await ctx.FillStyleAsync("blue"); + await ctx.FontAsync("bold 10px Arial"); + await ctx.TextAlignAsync(TextAlign.Center); + + string labelText = FormatRulerLabel(worldY, scaleInterval); + await ctx.FillTextAsync(labelText, 0, 0); + + await ctx.RestoreAsync(); + } + } + } + + private bool IsNearMultiple(double value, double multiple) + { + if (multiple == 0) return false; + double remainder = Math.Abs(value % multiple); + double epsilon = multiple * 0.001; + return remainder < epsilon || remainder > multiple - epsilon; + } + + private string FormatRulerLabel(double worldValue, double scaleInterval) + { + if (scaleInterval < 1.0) + { + return $"{worldValue:F1}m"; + } + else if (scaleInterval < 10.0) + { + return $"{worldValue:F0}m"; + } + else + { + return $"{worldValue:F0}m"; + } + } + private async Task DrawAxes(Context2D ctx) { double originCanvasX = OriginX * BASE_PIXELS_PER_METER; @@ -139,18 +303,18 @@ await ctx.StrokeAsync(); double gridSpacingMeters = GetGridSpacingMeters(); - double arrowLength = gridSpacingMeters * BASE_PIXELS_PER_METER; - double arrowHeadSize = 16 / ZoomScale; + double arrowLength = gridSpacingMeters * BASE_PIXELS_PER_METER; + double arrowHeadSize = 16 / ZoomScale; await ctx.FillStyleAsync("blue"); await ctx.StrokeStyleAsync("blue"); await ctx.LineWidthAsync(4 / ZoomScale); - + await ctx.BeginPathAsync(); await ctx.MoveToAsync(originCanvasX, originCanvasY); await ctx.LineToAsync(originCanvasX + arrowLength, originCanvasY); await ctx.StrokeAsync(); - + await ctx.BeginPathAsync(); await ctx.MoveToAsync(originCanvasX + arrowLength, originCanvasY); await ctx.LineToAsync(originCanvasX + arrowLength - arrowHeadSize, originCanvasY - arrowHeadSize / 2); @@ -161,12 +325,12 @@ await ctx.FillStyleAsync("red"); await ctx.StrokeStyleAsync("red"); await ctx.LineWidthAsync(4 / ZoomScale); - + await ctx.BeginPathAsync(); await ctx.MoveToAsync(originCanvasX, originCanvasY); await ctx.LineToAsync(originCanvasX, originCanvasY + arrowLength); await ctx.StrokeAsync(); - + await ctx.BeginPathAsync(); await ctx.MoveToAsync(originCanvasX, originCanvasY + arrowLength); await ctx.LineToAsync(originCanvasX - arrowHeadSize / 2, originCanvasY + arrowLength - arrowHeadSize); @@ -178,8 +342,8 @@ private async Task DrawGrid(Context2D ctx) { await ctx.StrokeStyleAsync("rgba(200, 200, 200, 0.4)"); - await ctx.LineWidthAsync(1 / ZoomScale); - await ctx.SetLineDashAsync(new double[] { 5 / ZoomScale, 5 / ZoomScale }); + await ctx.LineWidthAsync(1 / ZoomScale); + await ctx.SetLineDashAsync(new double[] { 5 / ZoomScale, 5 / ZoomScale }); double gridSpacingMeters = GetGridSpacingMeters(); double gridSpacingPixels = gridSpacingMeters * BASE_PIXELS_PER_METER; @@ -213,13 +377,14 @@ private double GetGridSpacingMeters() { - if (BASE_PIXELS_PER_METER * ZoomScale >= 300) return 0.2; - else if (BASE_PIXELS_PER_METER * ZoomScale >= 150) return 0.5; - else if (BASE_PIXELS_PER_METER * ZoomScale >= 75) return 1.0; - else if (BASE_PIXELS_PER_METER * ZoomScale >= 40) return 2.0; - else if (BASE_PIXELS_PER_METER * ZoomScale >= 20) return 5.0; - else if (BASE_PIXELS_PER_METER * ZoomScale >= 10) return 10.0; - else return 20.0; + double PixelsPerMeter = BASE_PIXELS_PER_METER * ZoomScale; + if (PixelsPerMeter >= 300) return 0.2; + else if (PixelsPerMeter >= 150) return 0.5; + else if (PixelsPerMeter >= 75) return 1.0; + else if (PixelsPerMeter >= 40) return 2.0; + else if (PixelsPerMeter >= 20) return 5.0; + else if (PixelsPerMeter >= 10) return 10.0; + else return 20.0; } private double CanvasToWorldX(double canvasX) @@ -253,10 +418,10 @@ StateHasChanged(); - if (e.Buttons == 4) + if (e.Buttons == 4) { CanvasTranslateX += e.MovementX; - CanvasTranslateY -= e.MovementY; + CanvasTranslateY -= e.MovementY; await DrawCanvas(); } } @@ -275,18 +440,15 @@ if (e.Buttons == 4) return; const double zoomFactor = 0.1; double oldZoom = ZoomScale; - - if (e.DeltaY < 0) ZoomScale = Math.Min(MAX_ZOOM, ZoomScale * (1 + zoomFactor)); - else ZoomScale = Math.Max(MIN_ZOOM, ZoomScale * (1 - zoomFactor)); + + if (e.DeltaY < 0) ZoomScale = Math.Min(MAX_ZOOM, ZoomScale * (1 + zoomFactor)); + else ZoomScale = Math.Max(MIN_ZOOM, ZoomScale * (1 - zoomFactor)); if (Math.Abs(ZoomScale - oldZoom) < 0.001) return; MouseX = e.OffsetX; MouseY = e.OffsetY; - WorldMouseX = CanvasToWorldX(MouseX); - WorldMouseY = CanvasToWorldY(MouseY); - double zoomPointWorldX = (MouseX - CanvasTranslateX) / oldZoom / BASE_PIXELS_PER_METER - OriginX; double zoomPointWorldY = (MouseY - CanvasTranslateY) / oldZoom / BASE_PIXELS_PER_METER - OriginY; @@ -296,6 +458,9 @@ CanvasTranslateX = MouseX - newZoomPointCanvasX; CanvasTranslateY = MouseY - newZoomPointCanvasY; + WorldMouseX = CanvasToWorldX(MouseX); + WorldMouseY = CanvasToWorldY(MouseY); + StateHasChanged(); await DrawCanvas(); } diff --git a/RobotApp/robot.db b/RobotApp/robot.db index b7dd223dac6b03db7e2ee7a2f71bf62e46c9a34b..d2792ed259f13d40d7066815b3677c054b0f50db 100644 GIT binary patch delta 72 zcmV-O0Jr~upbCJX3XmHClaU-l0h6&{sSg3#vk_3x4