diff --git a/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor b/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor
index 501eb37..826bb53 100644
--- a/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor
+++ b/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor
@@ -17,6 +17,13 @@
+
+
+
+ Follow Robot
+
+
+
@if (MonitorData?.RobotPosition != null)
{
@@ -58,6 +65,9 @@
@* Background Map Image *@
@if (MapImageLoaded && MapImageWidth > 0 && MapImageHeight > 0)
{
+ @* Image origin is at bottom-left corner (MapImageOriginX, MapImageOriginY) in world coordinates
+ In SVG (after Y flip), image top-left corner is at (MapImageOriginX, -MapImageOriginY - MapImageHeight)
+ So we render image at: x = MapImageOriginX, y = -MapImageOriginY - MapImageHeight *@
}
- @* Origin Marker (2 arrows: X+ and Y+) *@
+ @* Origin Marker (2 arrows: X+ and Y+) at (MapImageOriginX, MapImageOriginY) *@
@* X+ Arrow (pointing right) *@
@* Origin point *@
-
@@ -144,7 +154,7 @@
private ElementReference SvgRef;
private ElementReference SvgContainerRef;
- private double ZoomScale = 2.0; // Zoom vào robot hơn khi mở
+ private double ZoomScale = 1.5; // Zoom vào robot hơn khi mở
private const double MIN_ZOOM = 0.1;
private const double MAX_ZOOM = 5.0;
private const double BASE_PIXELS_PER_METER = 50.0;
@@ -166,6 +176,19 @@
private double? MouseWorldX = null;
private double? MouseWorldY = null;
+ // Auto-follow robot
+ private bool AutoFollowRobot = false;
+
+ private void OnAutoFollowRobotChanged(bool value)
+ {
+ AutoFollowRobot = value;
+ if (AutoFollowRobot && MonitorData?.RobotPosition != null)
+ {
+ UpdateViewToFollowRobot();
+ StateHasChanged();
+ }
+ }
+
// Map image properties
private const double MapImageOriginX = -20.0; // OriginX in world coordinates (meters)
private const double MapImageOriginY = -20.0; // OriginY in world coordinates (meters)
@@ -215,8 +238,6 @@
MapImageWidth = imageDimensions.Width * MapImageResolution;
MapImageHeight = imageDimensions.Height * MapImageResolution;
- Console.WriteLine($"Map image loaded: {imageDimensions.Width}x{imageDimensions.Height} pixels, {MapImageWidth}x{MapImageHeight} meters");
-
if (MapImageWidth > 0 && MapImageHeight > 0)
{
MapImageLoaded = true;
@@ -232,6 +253,12 @@
private string GetTransform()
{
+ // Transform applies: first translate (in screen pixels), then scale (pixels per meter)
+ // World coordinates are in meters
+ // After transform: screenX = TranslateX + worldX * (ZoomScale * BASE_PIXELS_PER_METER)
+ // screenY = TranslateY + worldY * (ZoomScale * BASE_PIXELS_PER_METER)
+ // But we need to flip Y: screenY = TranslateY - worldY * (ZoomScale * BASE_PIXELS_PER_METER)
+ // This is handled by WorldToSvgY which flips Y before applying transform
return $"translate({TranslateX}, {TranslateY}) scale({ZoomScale * BASE_PIXELS_PER_METER})";
}
@@ -254,7 +281,7 @@
// Điều chỉnh kích thước dựa trên ZoomScale
// Tăng kích thước lên 1.5x để robot to hơn
- double scaleFactor = 3 / ZoomScale; // Tăng kích thước hiển thị
+ double scaleFactor = 2 / ZoomScale; // Tăng kích thước hiển thị
double width = RobotWidthMeters * scaleFactor;
double height = RobotLengthMeters * scaleFactor;
@@ -299,17 +326,20 @@
private string GetOriginMarkerTransform()
{
- // Origin is at (MapImageOriginX, MapImageOriginY) in world coordinates
- // In SVG: (MapImageOriginX, -MapImageOriginY)
- var x = WorldToSvgX(MapImageOriginX);
- var y = WorldToSvgY(MapImageOriginY);
+ // Origin is at (MapImageOriginX, MapImageOriginY) in world coordinates (bottom-left corner of image)
+ // In SVG coordinates (after Y flip): (MapImageOriginX, -MapImageOriginY)
+ // Note: Image is rendered at (MapImageOriginX, -MapImageOriginY - MapImageHeight) in SVG
+ // So origin marker should be at (MapImageOriginX, -MapImageOriginY) in SVG
+ var x = WorldToSvgX(0);
+ var y = WorldToSvgY(0);
+
return $"translate({x}, {y})";
}
private double GetOriginMarkerSize()
{
// Marker size in world coordinates (meters)
- const double BaseMarkerSize = 0.5; // 1 meter
+ const double BaseMarkerSize = 1; // 1 meter
double scaleFactor = 1.0 / ZoomScale; // Keep visual size constant
return BaseMarkerSize * scaleFactor;
}
@@ -322,6 +352,25 @@
return BaseStrokeWidth * scaleFactor;
}
+ public void OnMonitorDataUpdated()
+ {
+ // Auto-follow robot when MonitorData changes
+ if (AutoFollowRobot && !IsPanning && MonitorData?.RobotPosition != null)
+ {
+ UpdateViewToFollowRobot();
+ }
+ }
+
+ private void UpdateViewToFollowRobot()
+ {
+ if (MonitorData?.RobotPosition == null) return;
+
+ // Center view on robot
+ TranslateX = SvgWidth / 2 - MonitorData.RobotPosition.X * BASE_PIXELS_PER_METER * ZoomScale;
+ TranslateY = SvgHeight / 2 + MonitorData.RobotPosition.Y * BASE_PIXELS_PER_METER * ZoomScale;
+ StateHasChanged();
+ }
+
public void UpdatePath()
{
if (MonitorData is not null && MonitorData.EdgeStates.Length > 0)
diff --git a/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor.css b/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor.css
index 21240f7..edb6c03 100644
--- a/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor.css
+++ b/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor.css
@@ -66,19 +66,27 @@
gap: 8px;
}
+.auto-follow-control {
+ display: flex;
+ align-items: center;
+ padding: 4px 8px;
+ background-color: #3d3d3d;
+ border-radius: 4px;
+}
+
.svg-container {
flex: 1;
overflow: hidden;
position: relative;
- background-color: #fafafa;
+ background-color: #808080;
}
-.svg-container svg {
- width: 100%;
- height: 100%;
- cursor: grab;
- background-color: dimgray;
-}
+ .svg-container svg {
+ width: 100%;
+ height: 100%;
+ cursor: grab;
+ background-color: #808080;
+ }
.svg-container svg:active {
cursor: grabbing;
diff --git a/RobotApp.Client/Pages/RobotMonitor.razor b/RobotApp.Client/Pages/RobotMonitor.razor
index f1f857b..6403ddb 100644
--- a/RobotApp.Client/Pages/RobotMonitor.razor
+++ b/RobotApp.Client/Pages/RobotMonitor.razor
@@ -29,6 +29,7 @@
{
_monitorData = data;
RobotMonitorViewRef?.UpdatePath();
+ RobotMonitorViewRef?.OnMonitorDataUpdated();
StateHasChanged();
}