diff --git a/src/Blashing.Core.Tests/MeterWidgetTest.cs b/src/Blashing.Core.Tests/MeterWidgetTest.cs
index 67ce368..c4254ca 100644
--- a/src/Blashing.Core.Tests/MeterWidgetTest.cs
+++ b/src/Blashing.Core.Tests/MeterWidgetTest.cs
@@ -34,21 +34,10 @@ public void MeterWidgetMarkupShouldContainPassedInValues()
.Add(p => p.Min, min)
.Add(p => p.Max, max)
);
-
- //var widget = @"
- //
- //
a
- //
- //
c
- //
d
- //
";
var expectedTitleMarkup = $"{title}
";
cut.FindAll("h1")[0].MarkupMatches(expectedTitleMarkup);
- // var expectedInputMarkup = $"";
- // cut.FindAll("input")[0].MarkupMatches(expectedInputMarkup);
-
var expectedMoreInfoMarkup = $"{moreInfo}
";
cut.FindAll("p")[0].MarkupMatches(expectedMoreInfoMarkup);
@@ -98,4 +87,138 @@ public void MeterWidgetShouldContainPassedInValues()
Assert.Equal(meterWidget.Min, min);
Assert.Equal(meterWidget.Max, max);
}
+
+ [Fact]
+ public void MeterWidgetShouldShowSvgGauge()
+ {
+ var cut = Render(parameters => parameters
+ .Add(p => p.Title, "Test")
+ .Add(p => p.Value, 50)
+ .Add(p => p.Min, 0)
+ .Add(p => p.Max, 100)
+ );
+
+ var svg = cut.Find("svg.meter");
+ Assert.NotNull(svg);
+
+ var backgroundPath = cut.Find("path.meter-background");
+ Assert.NotNull(backgroundPath);
+ Assert.False(string.IsNullOrEmpty(backgroundPath.GetAttribute("d")));
+
+ var valuePath = cut.Find("path.meter-value");
+ Assert.NotNull(valuePath);
+ Assert.False(string.IsNullOrEmpty(valuePath.GetAttribute("d")));
+ }
+
+ [Fact]
+ public void MeterWidgetShouldHideValueArcWhenValueEqualsMin()
+ {
+ var cut = Render(parameters => parameters
+ .Add(p => p.Value, 0)
+ .Add(p => p.Min, 0)
+ .Add(p => p.Max, 100)
+ );
+
+ var valuePaths = cut.FindAll("path.meter-value");
+ Assert.Empty(valuePaths);
+ }
+
+ [Fact]
+ public void MeterWidgetShouldShowCenterTextWhenDisplayInputIsTrue()
+ {
+ var cut = Render(parameters => parameters
+ .Add(p => p.Value, 42)
+ .Add(p => p.Min, 0)
+ .Add(p => p.Max, 100)
+ .Add(p => p.DisplayInput, true)
+ );
+
+ var text = cut.Find("text.meter-text");
+ Assert.NotNull(text);
+ Assert.Contains("42", text.TextContent);
+ }
+
+ [Fact]
+ public void MeterWidgetShouldHideCenterTextWhenDisplayInputIsFalse()
+ {
+ var cut = Render(parameters => parameters
+ .Add(p => p.Value, 42)
+ .Add(p => p.Min, 0)
+ .Add(p => p.Max, 100)
+ .Add(p => p.DisplayInput, false)
+ );
+
+ var texts = cut.FindAll("text.meter-text");
+ Assert.Empty(texts);
+ }
+
+ [Fact]
+ public void MeterWidgetShouldShowPrefixAndSuffix()
+ {
+ var cut = Render(parameters => parameters
+ .Add(p => p.Value, 75)
+ .Add(p => p.Min, 0)
+ .Add(p => p.Max, 100)
+ .Add(p => p.Prefix, "$")
+ .Add(p => p.Suffix, "%")
+ .Add(p => p.DisplayInput, true)
+ );
+
+ var text = cut.Find("text.meter-text");
+ Assert.Contains("$", text.TextContent);
+ Assert.Contains("75", text.TextContent);
+ Assert.Contains("%", text.TextContent);
+ }
+
+ [Fact]
+ public void MeterWidgetDefaultsShouldProduceValidSvgPaths()
+ {
+ var cut = Render(parameters => parameters
+ .Add(p => p.Value, 75)
+ );
+
+ var meterWidget = cut.Instance;
+ Assert.Equal(-125, meterWidget.AngleOffset);
+ Assert.Equal(250, meterWidget.AngleArc);
+ Assert.Equal(200, meterWidget.Width);
+ Assert.Equal(200, meterWidget.Height);
+ Assert.Equal(100, meterWidget.Max);
+
+ var backgroundPath = cut.Find("path.meter-background");
+ var d = backgroundPath.GetAttribute("d");
+ Assert.False(string.IsNullOrEmpty(d));
+ Assert.StartsWith("M ", d);
+
+ var valuePath = cut.Find("path.meter-value");
+ var vd = valuePath.GetAttribute("d");
+ Assert.False(string.IsNullOrEmpty(vd));
+ Assert.StartsWith("M ", vd);
+ }
+
+ [Theory]
+ [InlineData(0, 0, 100)] // at min: no value arc
+ [InlineData(50, 0, 100)] // mid value
+ [InlineData(100, 0, 100)] // at max
+ public void MeterWidgetArcRendersCorrectlyForValues(long value, long min, long max)
+ {
+ var cut = Render(parameters => parameters
+ .Add(p => p.Value, value)
+ .Add(p => p.Min, min)
+ .Add(p => p.Max, max)
+ );
+
+ var backgroundPath = cut.Find("path.meter-background");
+ Assert.False(string.IsNullOrEmpty(backgroundPath.GetAttribute("d")));
+
+ if (value > min)
+ {
+ var valuePath = cut.Find("path.meter-value");
+ Assert.NotNull(valuePath);
+ }
+ else
+ {
+ var valuePaths = cut.FindAll("path.meter-value");
+ Assert.Empty(valuePaths);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Blashing.Core/Components/Meter/MeterWidget.razor b/src/Blashing.Core/Components/Meter/MeterWidget.razor
index 2e6ac34..a71ca6b 100644
--- a/src/Blashing.Core/Components/Meter/MeterWidget.razor
+++ b/src/Blashing.Core/Components/Meter/MeterWidget.razor
@@ -2,18 +2,28 @@
\ No newline at end of file
diff --git a/src/Blashing.Core/Components/Meter/MeterWidget.razor.cs b/src/Blashing.Core/Components/Meter/MeterWidget.razor.cs
index 9114893..df893a5 100644
--- a/src/Blashing.Core/Components/Meter/MeterWidget.razor.cs
+++ b/src/Blashing.Core/Components/Meter/MeterWidget.razor.cs
@@ -12,33 +12,98 @@ public partial class MeterWidget : BaseWidget
[Parameter]
public string? UpdatedAtMessage { get; set; }
-
+
[Parameter]
- public long AngleOffset { get; set; }
-
+ public long AngleOffset { get; set; } = -125;
+
[Parameter]
- public long AngleArc { get; set; }
-
+ public long AngleArc { get; set; } = 250;
+
[Parameter]
- public long Height { get; set; }
-
+ public long Height { get; set; } = 200;
+
[Parameter]
- public long Width { get; set; }
-
+ public long Width { get; set; } = 200;
+
[Parameter]
- public bool ReadOnly { get; set; }
-
+ public bool ReadOnly { get; set; } = true;
+
[Parameter]
public long Value { get; set; }
-
+
[Parameter]
public long Min { get; set; }
-
+
+ [Parameter]
+ public long Max { get; set; } = 100;
+
+ [Parameter]
+ public bool DisplayInput { get; set; } = true;
+
+ [Parameter]
+ public string? Prefix { get; set; }
+
[Parameter]
- public long Max { get; set; }
-
+ public string? Suffix { get; set; }
+
protected override void OnParametersSet()
{
BackgroundColor ??= "#9c4274";
}
+
+ internal double Radius => Math.Min(Width, Height) / 2.0 * 0.8;
+
+ internal string CenterX =>
+ (Width / 2.0).ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
+
+ internal string CenterY =>
+ (Height / 2.0).ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
+
+ internal string StrokeWidth =>
+ (Math.Min(Width, Height) * 0.1).ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
+
+ internal string FontSize =>
+ (Math.Min(Width, Height) * 0.2).ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
+
+ internal string GetBackgroundArcPath()
+ {
+ double cx = Width / 2.0;
+ double cy = Height / 2.0;
+ return GetArcPath(cx, cy, Radius, AngleOffset, AngleOffset + AngleArc);
+ }
+
+ internal string GetValueArcPath()
+ {
+ double cx = Width / 2.0;
+ double cy = Height / 2.0;
+ double ratio = Max > Min
+ ? Math.Clamp((double)(Value - Min) / (Max - Min), 0.0, 1.0)
+ : 0.0;
+ double valueAngle = AngleOffset + ratio * AngleArc;
+ return GetArcPath(cx, cy, Radius, AngleOffset, valueAngle);
+ }
+
+ internal MarkupString GetCenterTextMarkup()
+ {
+ string encodedPrefix = System.Net.WebUtility.HtmlEncode(Prefix ?? string.Empty);
+ string encodedSuffix = System.Net.WebUtility.HtmlEncode(Suffix ?? string.Empty);
+ return (MarkupString)FormattableString.Invariant(
+ $"{encodedPrefix}{Value}{encodedSuffix}");
+ }
+
+ internal static string GetArcPath(double cx, double cy, double r, double startDeg, double endDeg)
+ {
+ double startRad = (startDeg - 90.0) * Math.PI / 180.0;
+ double endRad = (endDeg - 90.0) * Math.PI / 180.0;
+
+ double x1 = cx + r * Math.Cos(startRad);
+ double y1 = cy + r * Math.Sin(startRad);
+ double x2 = cx + r * Math.Cos(endRad);
+ double y2 = cy + r * Math.Sin(endRad);
+
+ int largeArcFlag = Math.Abs(endDeg - startDeg) > 180 ? 1 : 0;
+
+ return FormattableString.Invariant(
+ $"M {x1:F2},{y1:F2} A {r:F2},{r:F2} 0 {largeArcFlag} 1 {x2:F2},{y2:F2}");
+ }
}
\ No newline at end of file
diff --git a/src/Blashing.Core/Components/Meter/MeterWidget.razor.css b/src/Blashing.Core/Components/Meter/MeterWidget.razor.css
index c215c8a..8a381ac 100644
--- a/src/Blashing.Core/Components/Meter/MeterWidget.razor.css
+++ b/src/Blashing.Core/Components/Meter/MeterWidget.razor.css
@@ -1,22 +1,30 @@
.widget-meter {
- /*background-color: #9c4274;*/
height: 100%;
width: 100%;
text-align: center;
- /* width: inherit;*/
- /* height: inherit;*/
- /* display: table-cell;*/
vertical-align: middle;
display: flex;
flex-direction: column;
justify-content: center;
- /*align-items: center;*/
+ align-items: center;
}
-input.meter {
- background-color: #9c4274; /*darken(#9c4274, 15%);*/
- color: #fff;
- filter: brightness(0.85);
+svg.meter {
+ overflow: visible;
+}
+
+path.meter-background {
+ fill: none;
+ stroke: rgba(0, 0, 0, 0.2);
+}
+
+path.meter-value {
+ fill: none;
+ stroke: rgba(255, 255, 255, 0.8);
+}
+
+text.meter-text {
+ fill: white;
}
.title {
diff --git a/src/Blashing.Shared/Pages/Demo.razor b/src/Blashing.Shared/Pages/Demo.razor
index e251578..dc131bf 100644
--- a/src/Blashing.Shared/Pages/Demo.razor
+++ b/src/Blashing.Shared/Pages/Demo.razor
@@ -19,7 +19,7 @@
@**@
-
+
diff --git a/src/Blashing.Stories/Stories/MeterWidget.stories.razor b/src/Blashing.Stories/Stories/MeterWidget.stories.razor
index 1fb5978..a10684e 100644
--- a/src/Blashing.Stories/Stories/MeterWidget.stories.razor
+++ b/src/Blashing.Stories/Stories/MeterWidget.stories.razor
@@ -7,6 +7,12 @@
+
+
+
+
+
+
@@ -14,12 +20,33 @@
+
+
+
-
-
- @*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+