diff --git a/.gitignore b/.gitignore index 7a280546db0..680ce26110c 100644 --- a/.gitignore +++ b/.gitignore @@ -347,3 +347,8 @@ Radzen.Blazor.min.js *.md /.gitignore /.gitignore +/nul +/.gitignore +/.gitignore +/.gitignore +/.gitignore diff --git a/Radzen.Blazor/RadzenSpiderChart.razor b/Radzen.Blazor/RadzenSpiderChart.razor new file mode 100644 index 00000000000..cbb87446659 --- /dev/null +++ b/Radzen.Blazor/RadzenSpiderChart.razor @@ -0,0 +1,206 @@ +@typeparam TItem +@inherits RadzenComponent + +
+ @if (Width != null && Height != null && Series?.Any() == true) + { +
+ + + @for (int i = 1; i <= 5; i++) + { + @if (GridShape == SpiderChartGridShape.Circular) + { + var centerX = Width.Value / 2; + var centerY = Height.Value / 2; + var radius = Math.Min(centerX, centerY) * 0.8 * (i / 5.0); + + } + else + { + + } + } + + @for (int i = 0; i < Categories.Count; i++) + { + var (x1, y1) = GetPoint(i, MinValue); + var (x2, y2) = GetPoint(i, MaxValue); + + } + + + + @foreach (var series in Series.Where(s => !HiddenSeries.Contains(s.Name))) + { + var path = GetSeriesPath(series); + var isHovered = HoveredSeries == series; + var shouldShow = !IsLegendHover || HoveredSeries == null || HoveredSeries == series; + var localSeries = series; + + + } + + + + @foreach (var series in Series.Where(s => !HiddenSeries.Contains(s.Name)).OrderBy(s => s == HoveredSeries ? 1 : 0)) + { + var isHovered = series == HoveredSeries; + var shouldShow = !IsLegendHover || HoveredSeries == null || HoveredSeries == series; + @for (int i = 0; i < Categories.Count; i++) + { + var category = Categories[i]; + var value = series.Data.ContainsKey(category) ? series.Data[category] : 0; + var (x, y) = GetPoint(i, value); + var localSeries = series; + var localCategory = category; + var localValue = value; + + + } + } + + + + @for (int i = 0; i < Categories.Count; i++) + { + var (x, y) = GetLabelPoint(i); + var category = Categories[i]; + var anchor = GetLabelAnchor(i); + var baseline = GetLabelBaseline(i); + var xStr = x.ToString("F2", System.Globalization.CultureInfo.InvariantCulture); + var yStr = y.ToString("F2", System.Globalization.CultureInfo.InvariantCulture); + + @((MarkupString)$"{category}") + } + + + + @if (ShowLegend && Series?.Any() == true) + { +
+
+ @if (Series.Count > 1) + { +
+
Details
+
+ } + @if (Series.Count > 2) + { +
+ } +
+ @if (HoveredSeries != null) + { +
+
+ @(HoveredSeries.Name ?? "Series") +
+ @foreach (var category in Categories) + { + var value = HoveredSeries.Data.ContainsKey(category) ? HoveredSeries.Data[category] : 0; +
+ @category + @FormatValue(value) +
+ } +
+ } + else + { +
+ Hover over a series to see details +
+ } +
+
+ +
+ @if (Series.Count > 1) + { +
+
@LegendTitleText
+
+ + +
+
+ } + + @if (Series.Count > 2) + { + + } + +
+ @{ + var filteredSeries = string.IsNullOrWhiteSpace(legendFilter) + ? Series + : Series.Where(s => s.Name.Contains(legendFilter, StringComparison.OrdinalIgnoreCase)).ToList(); + } + @foreach (var series in filteredSeries) + { + var isHidden = HiddenSeries.Contains(series.Name); + var isHovered = HoveredSeries == series; + var shouldShow = HoveredSeries == null || HoveredSeries == series; + var localSeries = series; +
+ +
+ @localSeries.Name +
+ } +
+
+
+ } +
+ } +
\ No newline at end of file diff --git a/Radzen.Blazor/RadzenSpiderChart.razor.cs b/Radzen.Blazor/RadzenSpiderChart.razor.cs new file mode 100644 index 00000000000..2c1bcd8dfa3 --- /dev/null +++ b/Radzen.Blazor/RadzenSpiderChart.razor.cs @@ -0,0 +1,709 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.JSInterop; +using Radzen.Blazor.Rendering; + +namespace Radzen.Blazor +{ + /// + /// RadzenSpiderChart component. + /// + /// The type of data item. + public partial class RadzenSpiderChart : RadzenComponent + { + /// + /// Gets or sets the tooltip service. + /// + [Inject] + public TooltipService TooltipService { get; set; } + + /// + /// Gets or sets the data. + /// + [Parameter] + public IEnumerable Data { get; set; } + + /// + /// Specifies the property of which provides the category/axis name. + /// + [Parameter] + public string CategoryProperty { get; set; } + + /// + /// Specifies the property of which provides the value. + /// + [Parameter] + public string ValueProperty { get; set; } + + /// + /// Specifies the property of which provides the series name. + /// + [Parameter] + public string SeriesProperty { get; set; } + + + /// + /// Gets or sets the grid shape. + /// + [Parameter] + public SpiderChartGridShape GridShape { get; set; } = SpiderChartGridShape.Polygon; + + /// + /// Gets or sets the color scheme. + /// + [Parameter] + public ColorScheme ColorScheme { get; set; } = ColorScheme.Palette; + + /// + /// Gets or sets whether to show the legend. + /// + [Parameter] + public bool ShowLegend { get; set; } = false; + + /// + /// A callback that will be invoked when the user clicks on a series. + /// + [Parameter] + public EventCallback SeriesClick { get; set; } + + /// + /// A callback that will be invoked when the user clicks on a legend. + /// + [Parameter] + public EventCallback LegendClick { get; set; } + + /// + /// Gets or sets the value formatter. + /// + [Parameter] + public Func ValueFormatter { get; set; } + + /// + /// Gets or sets the format string for values. + /// + [Parameter] + public string FormatString { get; set; } = "F0"; + + /// + /// Gets or sets the legend title text. + /// + [Parameter] + public string LegendTitleText { get; set; } = "Series"; + + /// + /// Gets or sets the legend filter placeholder text. + /// + [Parameter] + public string LegendFilterPlaceholder { get; set; } = "Filter series..."; + + /// + /// Gets or sets the select all text. + /// + [Parameter] + public string LegendSelectAllText { get; set; } = "All"; + + /// + /// Gets or sets the deselect all text. + /// + [Parameter] + public string LegendDeselectAllText { get; set; } = "None"; + + private double? Width { get; set; } + private double? Height { get; set; } + private List Series { get; set; } = new(); + private List Categories { get; set; } = new(); + private double MaxValue { get; set; } = 100; + private double MinValue { get; set; } = 0; + private HashSet HiddenSeries { get; set; } = new(); + private SpiderChartSeries HoveredSeries { get; set; } + private bool IsLegendHover { get; set; } = false; + private string legendFilter = ""; + + /// + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + + if (firstRender) + { + await GetDimensions(); + } + } + + /// + /// Gets the dimensions of the chart element. + /// + /// A task that represents the asynchronous operation. + private async Task GetDimensions() + { + var rect = await JSRuntime.InvokeAsync("Radzen.createResizable", Element, Reference); + + if (!Width.HasValue && rect.Width > 0) + { + Width = ShowLegend ? rect.Width - 380 : rect.Width; + } + + if (!Height.HasValue && rect.Height > 0) + { + Height = rect.Height; + } + + if (Width.HasValue && Height.HasValue) + { + StateHasChanged(); + } + } + + /// + /// Called when the chart is resized. + /// + [JSInvokable] + public void Resize(double width, double height) + { + Width = ShowLegend ? width - 380 : width; + Height = height; + StateHasChanged(); + } + + /// + protected override void OnParametersSet() + { + base.OnParametersSet(); + ProcessData(); + } + + /// + /// Processes the data and creates series and categories. + /// + private void ProcessData() + { + if (Data == null || string.IsNullOrEmpty(CategoryProperty) || + string.IsNullOrEmpty(ValueProperty)) return; + + Series.Clear(); + Categories.Clear(); + + var categoryGetter = PropertyAccess.Getter(CategoryProperty); + var valueGetter = PropertyAccess.Getter(ValueProperty); + var seriesGetter = string.IsNullOrEmpty(SeriesProperty) ? null : + PropertyAccess.Getter(SeriesProperty); + + var grouped = seriesGetter != null + ? Data.GroupBy(item => seriesGetter(item)) + : new[] { Data.GroupBy(_ => "Series").First() }; + + var allValues = new List(); + + foreach (var group in grouped) + { + var seriesData = new Dictionary(); + + foreach (var item in group) + { + var category = categoryGetter(item); + var value = valueGetter(item); + + if (!Categories.Contains(category)) + Categories.Add(category); + + seriesData[category] = value; + allValues.Add(value); + } + + Series.Add(new SpiderChartSeries + { + Name = group.Key ?? "Series", + Data = seriesData, + ColorIndex = Series.Count + }); + } + + if (allValues.Any()) + { + MinValue = Math.Min(0, allValues.Min()); + MaxValue = Math.Max(100, allValues.Max()); + } + } + + + /// + /// Gets the x,y coordinates for a point on the chart. + /// + /// The index of the axis/category. + /// The value at that point. + /// A tuple containing the x and y coordinates. + private (double x, double y) GetPoint(int axisIndex, double value) + { + if (Width == null || Height == null) return (0, 0); + + var centerX = Width.Value / 2; + var centerY = Height.Value / 2; + var radius = Math.Min(centerX, centerY) * 0.8; + + var angle = (Math.PI * 2 * axisIndex / Categories.Count) - Math.PI / 2; + var normalizedValue = (value - MinValue) / (MaxValue - MinValue); + var r = radius * normalizedValue; + + return (centerX + r * Math.Cos(angle), centerY + r * Math.Sin(angle)); + } + + /// + /// Gets the x,y coordinates for a category label. + /// + /// The index of the axis/category. + /// A tuple containing the x and y coordinates for the label. + private (double x, double y) GetLabelPoint(int axisIndex) + { + if (Width == null || Height == null) return (0, 0); + + var centerX = Width.Value / 2; + var centerY = Height.Value / 2; + var radius = Math.Min(centerX, centerY) * 0.8; + + var angle = (Math.PI * 2 * axisIndex / Categories.Count) - Math.PI / 2; + var labelDistance = radius * 1.1; + var x = centerX + labelDistance * Math.Cos(angle); + var y = centerY + labelDistance * Math.Sin(angle); + + return (x, y); + } + + /// + /// Gets the text anchor alignment for a category label. + /// + /// The index of the axis/category. + /// The text-anchor value ("start", "middle", or "end"). + private string GetLabelAnchor(int axisIndex) + { + var angle = (Math.PI * 2 * axisIndex / Categories.Count) - Math.PI / 2; + var x = Math.Cos(angle); + var threshold = 0.1; + + if (x > threshold) + return "start"; + else if (x < -threshold) + return "end"; + else + return "middle"; + } + + /// + /// Gets the dominant baseline for a category label. + /// + /// The index of the axis/category. + /// The dominant-baseline value. + private string GetLabelBaseline(int axisIndex) + { + var angle = (Math.PI * 2 * axisIndex / Categories.Count) - Math.PI / 2; + var y = Math.Sin(angle); + var threshold = 0.1; + + if (y < -threshold) + return "text-after-edge"; + else if (y > threshold) + return "text-before-edge"; + else + return "middle"; + } + + /// + /// Gets the SVG path string for a series. + /// + /// The series to generate the path for. + /// An SVG path string. + private string GetSeriesPath(SpiderChartSeries series) + { + var points = new List(); + + for (int i = 0; i < Categories.Count; i++) + { + var category = Categories[i]; + var value = series.Data.ContainsKey(category) ? series.Data[category] : 0; + var (x, y) = GetPoint(i, value); + + var xStr = x.ToString("F2", System.Globalization.CultureInfo.InvariantCulture); + var yStr = y.ToString("F2", System.Globalization.CultureInfo.InvariantCulture); + + points.Add(i == 0 ? $"M{xStr},{yStr}" : $"L{xStr},{yStr}"); + } + + if (points.Any()) + points.Add("Z"); + + return string.Join(" ", points); + } + + /// + /// Gets the SVG path string for a grid level. + /// + /// The level between 0 and 1. + /// An SVG path string for the grid. + private string GetGridPath(double level) + { + var points = new List(); + + for (int i = 0; i < Categories.Count; i++) + { + var (x, y) = GetPoint(i, MinValue + level * (MaxValue - MinValue)); + var xStr = x.ToString("F2", System.Globalization.CultureInfo.InvariantCulture); + var yStr = y.ToString("F2", System.Globalization.CultureInfo.InvariantCulture); + points.Add(i == 0 ? $"M{xStr},{yStr}" : $"L{xStr},{yStr}"); + } + + if (points.Any()) + points.Add("Z"); + + return string.Join(" ", points); + } + + /// + /// Handles the series click event. + /// + /// The series that was clicked. + /// The mouse event arguments. + /// A task that represents the asynchronous operation. + private async Task OnSeriesClick(SpiderChartSeries series, MouseEventArgs args) + { + await SeriesClick.InvokeAsync(new SeriesClickEventArgs + { + Title = series.Name, + Data = series.Data.Values.ToArray() + }); + } + + /// + /// Handles the legend item click event. + /// + /// The series whose legend item was clicked. + /// A task that represents the asynchronous operation. + private async Task OnLegendClick(SpiderChartSeries series) + { + if (HiddenSeries.Contains(series.Name)) + HiddenSeries.Remove(series.Name); + else + HiddenSeries.Add(series.Name); + + if (LegendClick.HasDelegate) + { + await LegendClick.InvokeAsync(new LegendClickEventArgs + { + Title = series.Name, + Data = series.Data.Values.ToArray() + }); + } + + StateHasChanged(); + } + + /// + /// Handles the legend checkbox change event. + /// + /// The series whose checkbox was changed. + /// Whether the checkbox is now checked. + private void OnLegendCheckboxChange(SpiderChartSeries series, bool isChecked) + { + if (isChecked && HiddenSeries.Contains(series.Name)) + { + HiddenSeries.Remove(series.Name); + } + else if (!isChecked && !HiddenSeries.Contains(series.Name)) + { + HiddenSeries.Add(series.Name); + } + + StateHasChanged(); + } + + /// + /// Shows a tooltip for a data point. + /// + /// The mouse event arguments. + /// The series of the data point. + /// The category of the data point. + /// The value of the data point. + private void ShowTooltip(MouseEventArgs args, SpiderChartSeries series, string category, double value) + { + if (TooltipService == null || series == null) return; + + if (currentTooltipSeries == series && currentTooltipCategory == category) return; + currentTooltipSeries = series; + currentTooltipCategory = category; + + var tooltip = new RenderFragment(builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, "Title", series.Name ?? "Series"); + builder.AddAttribute(2, "Label", category ?? ""); + builder.AddAttribute(3, "Value", FormatValue(value)); + builder.AddAttribute(4, "Class", $"rz-series-{series.ColorIndex}-tooltip"); + builder.CloseComponent(); + }); + + TooltipService.OpenChartTooltip(Element, args.OffsetX + 15, args.OffsetY - 5, _ => tooltip, new ChartTooltipOptions()); + } + + /// + /// Hides the current tooltip. + /// + private void HideTooltip() + { + currentTooltipSeries = null; + currentTooltipCategory = null; + TooltipService?.Close(); + } + + private SpiderChartSeries currentTooltipSeries; + private string currentTooltipCategory; + + /// + /// Handles the mouse enter event for a marker. + /// + /// The mouse event arguments. + /// The series of the marker. + /// The category of the marker. + /// The value of the marker. + private void OnMarkerMouseEnter(MouseEventArgs args, SpiderChartSeries series, string category, double value) + { + if (HoveredSeries != series) + { + HoveredSeries = series; + IsLegendHover = false; + StateHasChanged(); + } + ShowTooltip(args, series, category, value); + } + + /// + /// Handles the mouse leave event for a marker. + /// + private void OnMarkerMouseLeave() + { + if (HoveredSeries != null && !IsLegendHover) + { + HoveredSeries = null; + StateHasChanged(); + } + HideTooltip(); + } + + /// + /// Handles the mouse enter event for an area. + /// + /// The mouse event arguments. + /// The series of the area. + private void OnAreaMouseEnter(MouseEventArgs args, SpiderChartSeries series) + { + if (HoveredSeries != series) + { + HoveredSeries = series; + IsLegendHover = false; + StateHasChanged(); + } + } + + /// + /// Handles the mouse leave event for an area. + /// + private void OnAreaMouseLeave() + { + if (HoveredSeries != null && !IsLegendHover) + { + HoveredSeries = null; + StateHasChanged(); + } + } + + + /// + /// Handles the mouse enter event for a legend item. + /// + /// The series of the legend item. + /// Whether the series is currently hidden. + private void OnLegendItemMouseEnter(SpiderChartSeries series, bool isHidden) + { + if (!isHidden) + { + HoveredSeries = series; + IsLegendHover = true; + StateHasChanged(); + } + } + + /// + /// Handles the mouse leave event for a legend item. + /// + private void OnLegendItemMouseLeave() + { + if (HoveredSeries != null && IsLegendHover) + { + HoveredSeries = null; + IsLegendHover = false; + StateHasChanged(); + } + } + + /// + /// Handles the mouse leave event for the entire chart. + /// + private void OnChartMouseLeave() + { + if (HoveredSeries != null || IsLegendHover) + { + HoveredSeries = null; + IsLegendHover = false; + HideTooltip(); + StateHasChanged(); + } + } + + /// + /// Shows a tooltip displaying all values for a series. + /// + /// The mouse event arguments. + /// The series to show values for. + private void ShowAreaTooltip(MouseEventArgs args, SpiderChartSeries series) + { + if (TooltipService == null || series == null) return; + + var tooltip = new RenderFragment(builder => + { + builder.OpenElement(0, "div"); + builder.AddAttribute(1, "class", "rz-chart-tooltip-content"); + builder.AddAttribute(2, "class", $"rz-series-{series.ColorIndex}-tooltip"); + builder.AddAttribute(3, "style", "background: var(--rz-base-background-color); padding: 8px; border-radius: 4px; min-width: 150px; border-width: 2px; border-style: solid;"); + + builder.OpenElement(4, "div"); + builder.AddAttribute(5, "class", $"rz-series-{series.ColorIndex}"); + builder.AddAttribute(6, "style", "font-weight: bold; margin-bottom: 8px; font-size: 14px; color: inherit;"); + builder.AddContent(7, series.Name ?? "Series"); + builder.CloseElement(); + + var index = 8; + foreach (var category in Categories) + { + var value = series.Data.ContainsKey(category) ? series.Data[category] : 0; + + builder.OpenElement(index++, "div"); + builder.AddAttribute(index++, "style", "display: flex; justify-content: space-between; gap: 12px; padding: 2px 0; font-size: 12px;"); + + builder.OpenElement(index++, "span"); + builder.AddAttribute(index++, "style", "color: var(--rz-text-secondary-color);"); + builder.AddContent(index++, category); + builder.CloseElement(); + + builder.OpenElement(index++, "span"); + builder.AddAttribute(index++, "style", "font-weight: 600;"); + builder.AddContent(index++, FormatValue(value)); + builder.CloseElement(); + + builder.CloseElement(); + } + + builder.CloseElement(); + }); + + TooltipService.OpenChartTooltip(Element, args.OffsetX + 15, args.OffsetY - 5, _ => tooltip, new ChartTooltipOptions()); + } + + /// + /// Formats a value for display. + /// + /// The value to format. + /// The formatted value string. + private string FormatValue(double value) + { + if (ValueFormatter != null) + return ValueFormatter(value); + + return value.ToString(FormatString ?? "F0"); + } + + /// + /// Gets the filtered series based on the legend filter. + /// + /// The filtered series collection. + private IEnumerable GetFilteredSeries() + { + if (string.IsNullOrWhiteSpace(legendFilter)) + return Series; + + return Series.Where(s => s.Name.Contains(legendFilter, StringComparison.OrdinalIgnoreCase)); + } + + /// + protected override string GetComponentCssClass() + { + return "rz-spider-chart"; + } + + /// + /// Selects all series (makes them visible). + /// + private void SelectAllSeries() + { + HiddenSeries.Clear(); + StateHasChanged(); + } + + /// + /// Deselects all series (hides them). + /// + private void DeselectAllSeries() + { + HiddenSeries.Clear(); + foreach (var series in Series) + { + HiddenSeries.Add(series.Name); + } + StateHasChanged(); + } + + /// + /// Represents a series in the spider chart. + /// + private class SpiderChartSeries + { + /// + /// Gets or sets the name of the series. + /// + public string Name { get; set; } + + /// + /// Gets or sets the data points for the series, keyed by category. + /// + public Dictionary Data { get; set; } + + /// + /// Gets or sets the color of the series. + /// + public string Color { get; set; } + + /// + /// Gets or sets the color index for CSS class generation. + /// + public int ColorIndex { get; set; } + } + + } + + /// + /// Spider chart grid shapes. + /// + public enum SpiderChartGridShape + { + /// + /// Polygon grid shape. + /// + Polygon, + /// + /// Circular grid shape. + /// + Circular + } + +} \ No newline at end of file diff --git a/Radzen.Blazor/themes/_components.scss b/Radzen.Blazor/themes/_components.scss index de15182b479..f09bb886b7a 100644 --- a/Radzen.Blazor/themes/_components.scss +++ b/Radzen.Blazor/themes/_components.scss @@ -64,6 +64,7 @@ @import 'components/blazor/progressbar'; @import 'components/blazor/chart'; @import 'components/blazor/sankey'; +@import 'components/blazor/spider'; @import 'components/blazor/link'; @import 'components/blazor/editor'; @import 'components/blazor/colorpicker'; diff --git a/Radzen.Blazor/themes/components/blazor/_spider.scss b/Radzen.Blazor/themes/components/blazor/_spider.scss new file mode 100644 index 00000000000..fc1299ecdce --- /dev/null +++ b/Radzen.Blazor/themes/components/blazor/_spider.scss @@ -0,0 +1,76 @@ +// Spider Chart Variables +$rz-spider-label-font-size: 0.875rem !default; +$rz-spider-label-color: var(--rz-text-color) !default; + +.rz-spider-chart { + position: relative; + display: block; + height: 100%; + width: 100%; + + svg { + overflow: visible; + } + + .rz-spider-chart-label { + font-size: $rz-spider-label-font-size; + fill: $rz-spider-label-color; + user-select: none; + } + + .rz-spider-chart-legend { + background: var(--rz-base-background-color); + border-left: 1px solid var(--rz-border-lighter); + + .rz-spider-chart-legend-title { + font-weight: 600; + margin-bottom: 12px; + color: var(--rz-text-title-color); + } + + .rz-spider-chart-legend-item { + transition: opacity 0.2s; + + &:hover { + opacity: 0.8; + } + + &.rz-state-inactive { + opacity: 0.5; + } + } + } + + @for $i from 0 through 50 { + .rz-series-#{$i} { + fill: var(--rz-series-#{$i}); + stroke: var(--rz-series-#{$i}); + background-color: var(--rz-series-#{$i}); + color: var(--rz-series-#{$i}); + } + } + &.rz-animate { + path { + stroke-dasharray: 1000; + stroke-dashoffset: 1000; + animation: spider-draw 1s ease-out forwards; + } + + circle { + opacity: 0; + animation: spider-fade-in 0.3s ease-out 0.8s forwards; + } + } +} + +@keyframes spider-draw { + to { + stroke-dashoffset: 0; + } +} + +@keyframes spider-fade-in { + to { + opacity: 1; + } +} \ No newline at end of file diff --git a/RadzenBlazorDemos/Pages/SpiderChartBasic.razor b/RadzenBlazorDemos/Pages/SpiderChartBasic.razor new file mode 100644 index 00000000000..090b230b75d --- /dev/null +++ b/RadzenBlazorDemos/Pages/SpiderChartBasic.razor @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/RadzenBlazorDemos/Pages/SpiderChartBasic.razor.cs b/RadzenBlazorDemos/Pages/SpiderChartBasic.razor.cs new file mode 100644 index 00000000000..29cf0e0405c --- /dev/null +++ b/RadzenBlazorDemos/Pages/SpiderChartBasic.razor.cs @@ -0,0 +1,31 @@ +namespace RadzenBlazorDemos.Pages +{ + public partial class SpiderChartBasic + { + private class ProductComparison + { + public string Product { get; set; } + public string Feature { get; set; } + public double Score { get; set; } + } + + private ProductComparison[] productData = new ProductComparison[] + { + // Product A + new ProductComparison { Product = "Product A", Feature = "Performance", Score = 0 }, + new ProductComparison { Product = "Product A", Feature = "Reliability", Score = 92 }, + new ProductComparison { Product = "Product A", Feature = "Usability", Score = 12 }, + new ProductComparison { Product = "Product A", Feature = "Features", Score = 95 }, + new ProductComparison { Product = "Product A", Feature = "Support", Score = 100 }, + new ProductComparison { Product = "Product A", Feature = "Value", Score = 73 }, + + // Product B + new ProductComparison { Product = "Product B", Feature = "Performance", Score = 78 }, + new ProductComparison { Product = "Product B", Feature = "Reliability", Score = 88 }, + new ProductComparison { Product = "Product B", Feature = "Usability", Score = 93 }, + new ProductComparison { Product = "Product B", Feature = "Features", Score = 82 }, + new ProductComparison { Product = "Product B", Feature = "Support", Score = 75 }, + new ProductComparison { Product = "Product B", Feature = "Value", Score = 90 } + }; + } +} \ No newline at end of file diff --git a/RadzenBlazorDemos/Pages/SpiderChartConfiguration.razor b/RadzenBlazorDemos/Pages/SpiderChartConfiguration.razor new file mode 100644 index 00000000000..a3e65d62ab0 --- /dev/null +++ b/RadzenBlazorDemos/Pages/SpiderChartConfiguration.razor @@ -0,0 +1,71 @@ + + + + + + + Grid Shape + + + + + + + + + + Color Scheme + + + + + + + + + + + + + + + + + + Value Format + + + + + + + + + + + + + + + + + + + + + + + +@code { + // Configuration options + +} \ No newline at end of file diff --git a/RadzenBlazorDemos/Pages/SpiderChartConfiguration.razor.cs b/RadzenBlazorDemos/Pages/SpiderChartConfiguration.razor.cs new file mode 100644 index 00000000000..04b2f927c3c --- /dev/null +++ b/RadzenBlazorDemos/Pages/SpiderChartConfiguration.razor.cs @@ -0,0 +1,230 @@ +using Radzen.Blazor; +using System; + +namespace RadzenBlazorDemos.Pages +{ + public partial class SpiderChartConfiguration + { + private SpiderChartGridShape gridShape = SpiderChartGridShape.Polygon; + private ColorScheme colorScheme = ColorScheme.Palette; + private bool showLegend = true; + private string valueFormat = "percent"; + + private Func GetValueFormatter() + { + return valueFormat switch + { + "percent" => (value) => $"{value:F0}%", + "number" => (value) => value.ToString("F1"), + "score" => (value) => $"{value:F0}/100", + "currency" => (value) => $"${value:F0}", + _ => (value) => value.ToString("F1") + }; + } + + private class PerformanceData + { + public string Department { get; set; } + public string DepartmentName { get; set; } + public string Metric { get; set; } + public string MetricLabel { get; set; } + public double Score { get; set; } + } + + private PerformanceData[] performanceData = + [ + new PerformanceData + { + Department = "ehab", + DepartmentName = "Ehab Hussein", + Metric = "csharp", + MetricLabel = "C#", + Score = 95 + }, + new PerformanceData + { + Department = "ehab", + DepartmentName = "Ehab Hussein", + Metric = "ai", + MetricLabel = "AI/ML", + Score = 88 + }, + new PerformanceData + { + Department = "ehab", + DepartmentName = "Ehab Hussein", + Metric = "webapp", + MetricLabel = "Web App Testing", + Score = 92 + }, + new PerformanceData + { + Department = "ehab", + DepartmentName = "Ehab Hussein", + Metric = "api", + MetricLabel = "API Testing", + Score = 90 + }, + new PerformanceData + { + Department = "ehab", + DepartmentName = "Ehab Hussein", + Metric = "burp", + MetricLabel = "Burp Suite", + Score = 94 + }, + new PerformanceData + { + Department = "ehab", + DepartmentName = "Ehab Hussein", + Metric = "scripting", + MetricLabel = "Scripting", + Score = 87 + }, + new PerformanceData + { + Department = "ehab", + DepartmentName = "Ehab Hussein", + Metric = "reporting", + MetricLabel = "Reporting", + Score = 85 + }, + new PerformanceData + { + Department = "ehab", + DepartmentName = "Ehab Hussein", + Metric = "cloud", + MetricLabel = "Cloud Security", + Score = 82 + }, + new PerformanceData + { + Department = "mohamed", + DepartmentName = "Mohamed Samy", + Metric = "csharp", + MetricLabel = "C#", + Score = 78 + }, + new PerformanceData + { + Department = "mohamed", + DepartmentName = "Mohamed Samy", + Metric = "ai", + MetricLabel = "AI/ML", + Score = 72 + }, + new PerformanceData + { + Department = "mohamed", + DepartmentName = "Mohamed Samy", + Metric = "webapp", + MetricLabel = "Web App Testing", + Score = 88 + }, + new PerformanceData + { + Department = "mohamed", + DepartmentName = "Mohamed Samy", + Metric = "api", + MetricLabel = "API Testing", + Score = 85 + }, + new PerformanceData + { + Department = "mohamed", + DepartmentName = "Mohamed Samy", + Metric = "burp", + MetricLabel = "Burp Suite", + Score = 91 + }, + new PerformanceData + { + Department = "mohamed", + DepartmentName = "Mohamed Samy", + Metric = "scripting", + MetricLabel = "Scripting", + Score = 93 + }, + new PerformanceData + { + Department = "mohamed", + DepartmentName = "Mohamed Samy", + Metric = "reporting", + MetricLabel = "Reporting", + Score = 80 + }, + new PerformanceData + { + Department = "mohamed", + DepartmentName = "Mohamed Samy", + Metric = "cloud", + MetricLabel = "Cloud Security", + Score = 76 + }, + new PerformanceData + { + Department = "tony", + DepartmentName = "Tony Dorrell", + Metric = "csharp", + MetricLabel = "C#", + Score = 82 + }, + new PerformanceData + { + Department = "tony", + DepartmentName = "Tony Dorrell", + Metric = "ai", + MetricLabel = "AI/ML", + Score = 75 + }, + new PerformanceData + { + Department = "tony", + DepartmentName = "Tony Dorrell", + Metric = "webapp", + MetricLabel = "Web App Testing", + Score = 95 + }, + new PerformanceData + { + Department = "tony", + DepartmentName = "Tony Dorrell", + Metric = "api", + MetricLabel = "API Testing", + Score = 93 + }, + new PerformanceData + { + Department = "tony", + DepartmentName = "Tony Dorrell", + Metric = "burp", + MetricLabel = "Burp Suite", + Score = 89 + }, + new PerformanceData + { + Department = "tony", + DepartmentName = "Tony Dorrell", + Metric = "scripting", + MetricLabel = "Scripting", + Score = 84 + }, + new PerformanceData + { + Department = "tony", + DepartmentName = "Tony Dorrell", + Metric = "reporting", + MetricLabel = "Reporting", + Score = 92 + }, + new PerformanceData + { + Department = "tony", + DepartmentName = "Tony Dorrell", + Metric = "cloud", + MetricLabel = "Cloud Security", + Score = 88 + } + ]; + } +} \ No newline at end of file diff --git a/RadzenBlazorDemos/Pages/SpiderChartPage.razor b/RadzenBlazorDemos/Pages/SpiderChartPage.razor new file mode 100644 index 00000000000..c2a700df312 --- /dev/null +++ b/RadzenBlazorDemos/Pages/SpiderChartPage.razor @@ -0,0 +1,27 @@ +@page "/spider-chart" + +Blazor Spider Chart Component | Radzen Blazor Components + + + + + + Spider Chart + + + + Display multivariate data in a radial format, perfect for comparing multiple metrics across different categories. + + + + + + + + Comprehensive Configuration + + + + + + diff --git a/RadzenBlazorDemos/Services/ExampleService.cs b/RadzenBlazorDemos/Services/ExampleService.cs index 7ac22a1dc45..76f2e402472 100644 --- a/RadzenBlazorDemos/Services/ExampleService.cs +++ b/RadzenBlazorDemos/Services/ExampleService.cs @@ -1958,6 +1958,16 @@ public class ExampleService Description = "This example demonstrates different color schemes, custom colors and styling of Radzen Blazor Chart component.", Tags = new [] { "chart", "graph", "styling" } }, + new Example + { + Name = "Spider Chart", + Path = "spider-chart", + Title = "Blazor Spider Chart Component | Free UI Components by Radzen", + Description = "Radzen Blazor Spider Chart for displaying multivariate data in a radial format.", + Icon = "\uf04e", + Tags = new [] { "spider", "radar", "chart", "multivariate", "radial", "web" }, + New = true + }, } }, new Example