mirror of
https://github.com/radzenhq/radzen-blazor.git
synced 2026-07-08 18:16:08 +00:00
Add corner radius and exploded slices to pie and donut series
This commit is contained in:
@@ -109,5 +109,195 @@ namespace Radzen.Blazor.Tests
|
||||
// Donut arcs have both outer and inner radius in the path
|
||||
Assert.Contains("A 118 118", chart.Markup); // outer
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async System.Threading.Tasks.Task PieSeries_DefaultCornerRadius_UsesSharpPath()
|
||||
{
|
||||
using var ctx = CreateChartContext();
|
||||
|
||||
var chart = ctx.RenderComponent<RadzenChart>(p => p
|
||||
.AddChildContent<RadzenPieSeries<DataItem>>(s => s
|
||||
.Add(x => x.CategoryProperty, nameof(DataItem.Category))
|
||||
.Add(x => x.ValueProperty, nameof(DataItem.Value))
|
||||
.Add(x => x.Data, SampleData)));
|
||||
|
||||
await chart.InvokeAsync(() => chart.Instance.Resize(400, 300));
|
||||
|
||||
// A sharp pie slice closes through the center with a zero-radius inner arc.
|
||||
Assert.Contains("A 0 0", chart.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async System.Threading.Tasks.Task PieSeries_CornerRadius_RoundsThreeCornersPerSlice()
|
||||
{
|
||||
using var ctx = CreateChartContext();
|
||||
|
||||
var chart = ctx.RenderComponent<RadzenChart>(p => p
|
||||
.AddChildContent<RadzenPieSeries<DataItem>>(s => s
|
||||
.Add(x => x.CategoryProperty, nameof(DataItem.Category))
|
||||
.Add(x => x.ValueProperty, nameof(DataItem.Value))
|
||||
.Add(x => x.CornerRadius, 10.0)
|
||||
.Add(x => x.Data, SampleData)));
|
||||
|
||||
await chart.InvokeAsync(() => chart.Instance.Resize(400, 300));
|
||||
|
||||
// No sharp apex (no zero-radius inner arc) once corners are rounded.
|
||||
Assert.DoesNotContain("A 0 0", chart.Markup);
|
||||
// Three fillet arcs of radius 10 per slice (two outer corners + apex) across three slices.
|
||||
Assert.Equal(9, Count(chart.Markup, "A 10 10"));
|
||||
Assert.DoesNotContain("NaN", chart.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async System.Threading.Tasks.Task DonutSeries_CornerRadius_RoundsFourCornersPerSlice()
|
||||
{
|
||||
using var ctx = CreateChartContext();
|
||||
|
||||
var chart = ctx.RenderComponent<RadzenChart>(p => p
|
||||
.AddChildContent<RadzenDonutSeries<DataItem>>(s => s
|
||||
.Add(x => x.CategoryProperty, nameof(DataItem.Category))
|
||||
.Add(x => x.ValueProperty, nameof(DataItem.Value))
|
||||
.Add(x => x.InnerRadius, 50)
|
||||
.Add(x => x.CornerRadius, 10.0)
|
||||
.Add(x => x.Data, SampleData)));
|
||||
|
||||
await chart.InvokeAsync(() => chart.Instance.Resize(400, 300));
|
||||
|
||||
// Main outer and inner arcs survive.
|
||||
Assert.Contains("A 118 118", chart.Markup);
|
||||
Assert.Contains("A 50 50", chart.Markup);
|
||||
// Four fillet arcs of radius 10 per slice across three slices.
|
||||
Assert.Equal(12, Count(chart.Markup, "A 10 10"));
|
||||
Assert.DoesNotContain("NaN", chart.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async System.Threading.Tasks.Task PieSeries_LargeCornerRadius_ClampedWithoutNaN()
|
||||
{
|
||||
using var ctx = CreateChartContext();
|
||||
|
||||
var chart = ctx.RenderComponent<RadzenChart>(p => p
|
||||
.AddChildContent<RadzenPieSeries<DataItem>>(s => s
|
||||
.Add(x => x.CategoryProperty, nameof(DataItem.Category))
|
||||
.Add(x => x.ValueProperty, nameof(DataItem.Value))
|
||||
.Add(x => x.CornerRadius, 1000.0)
|
||||
.Add(x => x.Data, SampleData)));
|
||||
|
||||
await chart.InvokeAsync(() => chart.Instance.Resize(400, 300));
|
||||
|
||||
Assert.DoesNotContain("NaN", chart.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async System.Threading.Tasks.Task PieSeries_SingleItem_CornerRadiusFallsBackToSharpRing()
|
||||
{
|
||||
using var ctx = CreateChartContext();
|
||||
|
||||
var single = new[] { new DataItem { Category = "A", Value = 10 } };
|
||||
|
||||
var chart = ctx.RenderComponent<RadzenChart>(p => p
|
||||
.AddChildContent<RadzenPieSeries<DataItem>>(s => s
|
||||
.Add(x => x.CategoryProperty, nameof(DataItem.Category))
|
||||
.Add(x => x.ValueProperty, nameof(DataItem.Value))
|
||||
.Add(x => x.CornerRadius, 10.0)
|
||||
.Add(x => x.Data, single)));
|
||||
|
||||
await chart.InvokeAsync(() => chart.Instance.Resize(400, 300));
|
||||
|
||||
// A full-circle slice has no corners; it renders as the sharp ring (zero-radius inner arc).
|
||||
Assert.Contains("A 0 0", chart.Markup);
|
||||
Assert.DoesNotContain("A 10 10", chart.Markup);
|
||||
}
|
||||
|
||||
class ExplodeItem
|
||||
{
|
||||
public string Category { get; set; } = "";
|
||||
public double Value { get; set; }
|
||||
public bool Exploded { get; set; }
|
||||
}
|
||||
|
||||
static ExplodeItem[] ExplodeData => new[]
|
||||
{
|
||||
new ExplodeItem { Category = "A", Value = 10, Exploded = false },
|
||||
new ExplodeItem { Category = "B", Value = 20, Exploded = true },
|
||||
new ExplodeItem { Category = "C", Value = 15, Exploded = false },
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void PieSeries_ExplodedProperty_MarksFlaggedSlices()
|
||||
{
|
||||
using var ctx = CreateChartContext();
|
||||
|
||||
var chart = ctx.RenderComponent<RadzenChart>(p => p
|
||||
.AddChildContent<RadzenPieSeries<ExplodeItem>>(s => s
|
||||
.Add(x => x.CategoryProperty, nameof(ExplodeItem.Category))
|
||||
.Add(x => x.ValueProperty, nameof(ExplodeItem.Value))
|
||||
.Add(x => x.ExplodeOffset, 15.0)
|
||||
.Add(x => x.ExplodedProperty, nameof(ExplodeItem.Exploded))
|
||||
.Add(x => x.Data, ExplodeData)));
|
||||
|
||||
// Only the single flagged slice carries the static-explode class, and the offset vars are emitted.
|
||||
Assert.Equal(1, Count(chart.Markup, "rz-state-exploded"));
|
||||
Assert.Contains("--rz-explode-x", chart.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PieSeries_NoExplodedProperty_NoStaticExplodeClass()
|
||||
{
|
||||
using var ctx = CreateChartContext();
|
||||
|
||||
var chart = ctx.RenderComponent<RadzenChart>(p => p
|
||||
.AddChildContent<RadzenPieSeries<ExplodeItem>>(s => s
|
||||
.Add(x => x.CategoryProperty, nameof(ExplodeItem.Category))
|
||||
.Add(x => x.ValueProperty, nameof(ExplodeItem.Value))
|
||||
.Add(x => x.ExplodeOffset, 15.0)
|
||||
.Add(x => x.Data, ExplodeData)));
|
||||
|
||||
Assert.DoesNotContain("rz-state-exploded", chart.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PieSeries_ExplodedProperty_WithoutOffset_NoStaticExplodeClass()
|
||||
{
|
||||
using var ctx = CreateChartContext();
|
||||
|
||||
var chart = ctx.RenderComponent<RadzenChart>(p => p
|
||||
.AddChildContent<RadzenPieSeries<ExplodeItem>>(s => s
|
||||
.Add(x => x.CategoryProperty, nameof(ExplodeItem.Category))
|
||||
.Add(x => x.ValueProperty, nameof(ExplodeItem.Value))
|
||||
.Add(x => x.ExplodedProperty, nameof(ExplodeItem.Exploded))
|
||||
.Add(x => x.Data, ExplodeData)));
|
||||
|
||||
// ExplodeOffset defaults to 0, which gates static explode off.
|
||||
Assert.DoesNotContain("rz-state-exploded", chart.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DonutSeries_ExplodedProperty_MarksFlaggedSlices()
|
||||
{
|
||||
using var ctx = CreateChartContext();
|
||||
|
||||
var chart = ctx.RenderComponent<RadzenChart>(p => p
|
||||
.AddChildContent<RadzenDonutSeries<ExplodeItem>>(s => s
|
||||
.Add(x => x.CategoryProperty, nameof(ExplodeItem.Category))
|
||||
.Add(x => x.ValueProperty, nameof(ExplodeItem.Value))
|
||||
.Add(x => x.ExplodeOffset, 15.0)
|
||||
.Add(x => x.ExplodedProperty, nameof(ExplodeItem.Exploded))
|
||||
.Add(x => x.Data, ExplodeData)));
|
||||
|
||||
Assert.Equal(1, Count(chart.Markup, "rz-state-exploded"));
|
||||
}
|
||||
|
||||
private static int Count(string haystack, string needle)
|
||||
{
|
||||
var count = 0;
|
||||
var index = 0;
|
||||
while ((index = haystack.IndexOf(needle, index, System.StringComparison.Ordinal)) >= 0)
|
||||
{
|
||||
count++;
|
||||
index += needle.Length;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,8 @@
|
||||
startAngle = endAngle;
|
||||
|
||||
var index = Items.IndexOf(data);
|
||||
var arcClassName = $"rz-series-item-{index}";
|
||||
var exploded = ExplodeOffset > 0 && IsExploded(data);
|
||||
var arcClassName = $"rz-series-item-{index}" + (exploded ? " rz-state-exploded" : "");
|
||||
var fill = PickColor(index, Fills);
|
||||
var stroke = PickColor(index, Strokes);
|
||||
var gradId = $"rzPieGradient{chart.UniqueID}-{seriesIndex}-{index}";
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
startAngle = endAngle;
|
||||
|
||||
var index = Items.IndexOf(data);
|
||||
var arcClassName = $"rz-series-item-{index}";
|
||||
var exploded = ExplodeOffset > 0 && IsExploded(data);
|
||||
var arcClassName = $"rz-series-item-{index}" + (exploded ? " rz-state-exploded" : "");
|
||||
var fill = PickColor(index, Fills);
|
||||
var stroke = PickColor(index, Strokes);
|
||||
var gradId = $"rzPieGradient{chart.UniqueID}-{seriesIndex}-{index}";
|
||||
|
||||
@@ -63,6 +63,15 @@ namespace Radzen.Blazor
|
||||
[Parameter]
|
||||
public double? Radius { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the corner radius in pixels used to round the corners of each segment.
|
||||
/// A pie segment has its two outer corners and its center apex rounded; a donut segment has all four corners rounded.
|
||||
/// The value is clamped per segment so that adjacent corners never overlap.
|
||||
/// </summary>
|
||||
/// <value>The corner radius in pixels. Default is <c>0</c> (sharp corners).</value>
|
||||
[Parameter]
|
||||
public double CornerRadius { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the property that provides a per-item radius value.
|
||||
/// When set, each pie/donut segment can have a different outer radius, allowing visual differentiation by size.
|
||||
@@ -74,12 +83,23 @@ namespace Radzen.Blazor
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the distance in pixels that a segment moves outward from the center when hovered.
|
||||
/// Set to a value greater than 0 to enable the explode-on-hover effect.
|
||||
/// Set to a value greater than 0 to enable the explode-on-hover effect. Also sets the distance for
|
||||
/// segments pulled out permanently via <see cref="ExplodedProperty"/>.
|
||||
/// </summary>
|
||||
/// <value>The explode offset in pixels. Default is 0 (disabled).</value>
|
||||
[Parameter]
|
||||
public double ExplodeOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of a boolean property that marks segments as exploded (pulled out from the
|
||||
/// center) in their normal state, not just on hover. Requires <see cref="ExplodeOffset"/> greater than 0,
|
||||
/// which sets the distance. Exploded segments stay pulled out and move a little further on hover; other
|
||||
/// segments still explode on hover.
|
||||
/// </summary>
|
||||
/// <value>The boolean property name on <typeparamref name="TItem"/>, or null to disable static explode.</value>
|
||||
[Parameter]
|
||||
public string? ExplodedProperty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a collection of fill colors applied to individual pie segments in sequence.
|
||||
/// Each segment gets the color at its index position. If fewer colors than segments, colors are reused cyclically.
|
||||
@@ -222,6 +242,20 @@ namespace Radzen.Blazor
|
||||
return minRadius + (value - minValue) / (maxValue - minValue) * (maxRadius - minRadius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a specific data item is exploded in its normal state, as determined by
|
||||
/// <see cref="ExplodedProperty"/>. Always <c>false</c> when <see cref="ExplodedProperty"/> is not set.
|
||||
/// </summary>
|
||||
internal bool IsExploded(TItem item)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ExplodedProperty))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return PropertyAccess.Getter<TItem, bool>(ExplodedProperty)(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current X coordinate of the center.
|
||||
/// </summary>
|
||||
@@ -511,7 +545,7 @@ namespace Radzen.Blazor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates SVG path that renders the specified segment.
|
||||
/// Creates SVG path that renders the specified segment, rounding its corners by <see cref="CornerRadius"/>.
|
||||
/// </summary>
|
||||
/// <param name="x">The x.</param>
|
||||
/// <param name="y">The y.</param>
|
||||
@@ -520,6 +554,14 @@ namespace Radzen.Blazor
|
||||
/// <param name="startAngle">The start angle.</param>
|
||||
/// <param name="endAngle">The end angle.</param>
|
||||
protected string Segment(double x, double y, double radius, double innerRadius, double startAngle, double endAngle)
|
||||
{
|
||||
return Segment(x, y, radius, innerRadius, startAngle, endAngle, CornerRadius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates SVG path that renders the specified segment with sharp corners.
|
||||
/// </summary>
|
||||
private string SharpSegment(double x, double y, double radius, double innerRadius, double startAngle, double endAngle)
|
||||
{
|
||||
var largeArcFlag = 0;
|
||||
|
||||
@@ -549,6 +591,135 @@ namespace Radzen.Blazor
|
||||
return $"M {startX} {startY} A {r} {r} 0 {largeArcFlag} 1 {endX} {endY} L {innerEndX} {innerEndY} A {innerR} {innerR} 0 {largeArcFlag} 0 {innerStartX} {innerStartY} Z";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates SVG path that renders the specified segment with corners rounded by <paramref name="cornerRadius"/>.
|
||||
/// A pie segment (<paramref name="innerRadius"/> of <c>0</c>) rounds its two outer corners and its center apex;
|
||||
/// a donut segment rounds all four corners. The corner radius is clamped per segment so corners never overlap.
|
||||
/// </summary>
|
||||
/// <param name="x">The x.</param>
|
||||
/// <param name="y">The y.</param>
|
||||
/// <param name="radius">The radius.</param>
|
||||
/// <param name="innerRadius">The inner radius.</param>
|
||||
/// <param name="startAngle">The start angle.</param>
|
||||
/// <param name="endAngle">The end angle.</param>
|
||||
/// <param name="cornerRadius">The corner radius in pixels.</param>
|
||||
protected string Segment(double x, double y, double radius, double innerRadius, double startAngle, double endAngle, double cornerRadius)
|
||||
{
|
||||
var sweep = Math.Abs(startAngle - endAngle);
|
||||
|
||||
// No rounding requested, a full ring (no corners to round), or a degenerate radius: use the sharp path.
|
||||
if (cornerRadius <= 0.01 || sweep >= 359.99 || radius <= 0)
|
||||
{
|
||||
return SharpSegment(x, y, radius, innerRadius, startAngle, endAngle);
|
||||
}
|
||||
|
||||
var isDonut = innerRadius > 0;
|
||||
|
||||
// Clamp the corner radius so adjacent fillets never overlap.
|
||||
var s = Math.Sin(DegToRad(sweep / 2));
|
||||
var rc = Math.Min(cornerRadius, (radius - innerRadius) / 2);
|
||||
rc = Math.Min(rc, radius * s / (1 + s)); // outer arc angular limit
|
||||
if (isDonut && s < 1)
|
||||
{
|
||||
rc = Math.Min(rc, innerRadius * s / (1 - s)); // inner arc angular limit
|
||||
}
|
||||
|
||||
// Too thin to round visibly - fall back to sharp corners.
|
||||
if (rc < 0.01)
|
||||
{
|
||||
return SharpSegment(x, y, radius, innerRadius, startAngle, endAngle);
|
||||
}
|
||||
|
||||
// The slice spans from startAngle (a0) down to endAngle (a1), drawn clockwise.
|
||||
var a0 = startAngle;
|
||||
var a1 = endAngle;
|
||||
|
||||
// Outer fillets are tangent to the outer circle from inside (center at radius - rc).
|
||||
var eOut = Math.Asin(rc / (radius - rc));
|
||||
var eOutDeg = eOut * 180 / Math.PI;
|
||||
var outerEdgeRadius = (radius - rc) * Math.Cos(eOut);
|
||||
|
||||
var outerArcStart = ToCartesian(x, y, radius, a0 - eOutDeg);
|
||||
var outerArcEnd = ToCartesian(x, y, radius, a1 + eOutDeg);
|
||||
var outerEdgeStart = ToCartesian(x, y, outerEdgeRadius, a0);
|
||||
var outerEdgeEnd = ToCartesian(x, y, outerEdgeRadius, a1);
|
||||
var outerCenterStart = ToCartesian(x, y, radius - rc, a0 - eOutDeg);
|
||||
var outerCenterEnd = ToCartesian(x, y, radius - rc, a1 + eOutDeg);
|
||||
|
||||
var largeOuter = (sweep - 2 * eOutDeg) >= 180 ? 1 : 0;
|
||||
|
||||
var path = $"M {Format(outerArcStart)}" +
|
||||
$" {Arc(radius, largeOuter, 1, outerArcEnd)}" +
|
||||
$" {Arc(rc, 0, Sweep(outerArcEnd, outerEdgeEnd, outerCenterEnd), outerEdgeEnd)}";
|
||||
|
||||
if (isDonut)
|
||||
{
|
||||
// Inner fillets are tangent to the inner circle from outside (center at innerRadius + rc).
|
||||
var eIn = Math.Asin(rc / (innerRadius + rc));
|
||||
var eInDeg = eIn * 180 / Math.PI;
|
||||
var innerEdgeRadius = (innerRadius + rc) * Math.Cos(eIn);
|
||||
|
||||
var innerArcStart = ToCartesian(x, y, innerRadius, a0 - eInDeg);
|
||||
var innerArcEnd = ToCartesian(x, y, innerRadius, a1 + eInDeg);
|
||||
var innerEdgeStart = ToCartesian(x, y, innerEdgeRadius, a0);
|
||||
var innerEdgeEnd = ToCartesian(x, y, innerEdgeRadius, a1);
|
||||
var innerCenterStart = ToCartesian(x, y, innerRadius + rc, a0 - eInDeg);
|
||||
var innerCenterEnd = ToCartesian(x, y, innerRadius + rc, a1 + eInDeg);
|
||||
|
||||
var largeInner = (sweep - 2 * eInDeg) >= 180 ? 1 : 0;
|
||||
|
||||
path += $" L {Format(innerEdgeEnd)}" +
|
||||
$" {Arc(rc, 0, Sweep(innerEdgeEnd, innerArcEnd, innerCenterEnd), innerArcEnd)}" +
|
||||
$" {Arc(innerRadius, largeInner, 0, innerArcStart)}" +
|
||||
$" {Arc(rc, 0, Sweep(innerArcStart, innerEdgeStart, innerCenterStart), innerEdgeStart)}" +
|
||||
$" L {Format(outerEdgeStart)}" +
|
||||
$" {Arc(rc, 0, Sweep(outerEdgeStart, outerArcStart, outerCenterStart), outerArcStart)} Z";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pie: round the center apex where the two radial edges meet.
|
||||
var half = DegToRad(sweep / 2);
|
||||
var apexEdgeRadius = rc / Math.Tan(half);
|
||||
var apexStart = ToCartesian(x, y, apexEdgeRadius, a0);
|
||||
var apexEnd = ToCartesian(x, y, apexEdgeRadius, a1);
|
||||
var apexCenter = ToCartesian(x, y, rc / Math.Sin(half), (a0 + a1) / 2);
|
||||
|
||||
path += $" L {Format(apexEnd)}" +
|
||||
$" {Arc(rc, 0, Sweep(apexEnd, apexStart, apexCenter), apexStart)}" +
|
||||
$" L {Format(outerEdgeStart)}" +
|
||||
$" {Arc(rc, 0, Sweep(outerEdgeStart, outerArcStart, outerCenterStart), outerArcStart)} Z";
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a point as "x y" using the invariant culture.
|
||||
/// </summary>
|
||||
private static string Format((double X, double Y) point)
|
||||
{
|
||||
return $"{point.X.ToInvariantString()} {point.Y.ToInvariantString()}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an SVG elliptical-arc command to the given end point.
|
||||
/// </summary>
|
||||
private static string Arc(double radius, int largeArcFlag, int sweepFlag, (double X, double Y) end)
|
||||
{
|
||||
var r = radius.ToInvariantString();
|
||||
return $"A {r} {r} 0 {largeArcFlag} {sweepFlag} {Format(end)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the SVG sweep flag for the minor arc from <paramref name="start"/> to <paramref name="end"/>
|
||||
/// about <paramref name="center"/>. In SVG (y-down) coordinates a positive cross product is drawn clockwise.
|
||||
/// </summary>
|
||||
private static int Sweep((double X, double Y) start, (double X, double Y) end, (double X, double Y) center)
|
||||
{
|
||||
var cross = (start.X - center.X) * (end.Y - center.Y) - (start.Y - center.Y) * (end.X - center.X);
|
||||
return cross > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the inner radius used for data label placement - <c>0</c> for pie, the hole radius for donut.
|
||||
/// </summary>
|
||||
|
||||
@@ -337,6 +337,17 @@ $chart-color-schemes: (
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Segments exploded in their normal state move a little further on hover.
|
||||
> g.rz-state-exploded {
|
||||
&:hover,
|
||||
&.rz-state-hover {
|
||||
> path:not(.rz-hit-area),
|
||||
> .rz-path {
|
||||
transform: translate(calc(var(--rz-explode-x, 0) * 1.2), calc(var(--rz-explode-y, 0) * 1.2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rz-pie-series:has(> .rz-state-hover),
|
||||
@@ -367,6 +378,19 @@ $chart-color-schemes: (
|
||||
}
|
||||
}
|
||||
|
||||
// Segments pulled out permanently via ExplodedProperty. Applied outside the hover wrapper so static
|
||||
// explode works even when AllowSeriesHover is false. The hit-area moves too so it tracks the slice.
|
||||
.rz-pie-series,
|
||||
.rz-donut-series {
|
||||
> g.rz-state-exploded {
|
||||
> path,
|
||||
> .rz-path {
|
||||
transform: translate(var(--rz-explode-x, 0), var(--rz-explode-y, 0));
|
||||
transition: transform var(--rz-transition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@each $scheme, $colors in $chart-color-schemes {
|
||||
@each $color in $colors {
|
||||
$index: index($colors, $color);
|
||||
|
||||
@@ -19,12 +19,17 @@
|
||||
</Items>
|
||||
</RadzenSelectBar>
|
||||
</RadzenStack>
|
||||
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.5rem">
|
||||
<RadzenLabel Text="Corner Radius" Component="cornerRadius" />
|
||||
<RadzenSlider @bind-Value="@cornerRadius" TValue="double" Min="0" Max="30" Step="1" Name="cornerRadius" Style="width: 120px;" />
|
||||
<RadzenText TextStyle="TextStyle.Body2" class="rz-mb-0">@cornerRadius px</RadzenText>
|
||||
</RadzenStack>
|
||||
</RadzenStack>
|
||||
</RadzenCard>
|
||||
|
||||
<RadzenStack Style="width: 100%; max-width: 600px;">
|
||||
<RadzenChart Animate="true">
|
||||
<RadzenDonutSeries FillMode="@fillMode" Data="@revenue" CategoryProperty="Quarter" ValueProperty="Revenue" TotalAngle="180" StartAngle="180" ShowTooltipOnLegend="@showTooltipOnLegend">
|
||||
<RadzenDonutSeries FillMode="@fillMode" CornerRadius="@cornerRadius" Data="@revenue" CategoryProperty="Quarter" ValueProperty="Revenue" TotalAngle="180" StartAngle="180" ShowTooltipOnLegend="@showTooltipOnLegend">
|
||||
<ChildContent>
|
||||
<RadzenSeriesDataLabels Visible="@showDataLabels" />
|
||||
</ChildContent>
|
||||
@@ -43,6 +48,7 @@
|
||||
bool showDataLabels = false;
|
||||
bool showTooltipOnLegend = true;
|
||||
FillMode fillMode = FillMode.Gradient;
|
||||
double cornerRadius = 0;
|
||||
|
||||
class DataItem
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<RadzenStack class="rz-p-0 rz-p-md-6 rz-p-lg-12" AlignItems="AlignItems.Center">
|
||||
<RadzenStack Style="width: 100%; max-width: 600px;">
|
||||
<RadzenChart>
|
||||
<RadzenDonutSeries FillMode="FillMode.Gradient" Data="@revenue" CategoryProperty="Quarter" ValueProperty="Revenue" ExplodeOffset="15">
|
||||
<RadzenDonutSeries FillMode="FillMode.Gradient" Data="@revenue" CategoryProperty="Quarter" ValueProperty="Revenue" ExplodeOffset="15" ExplodedProperty="Highlight">
|
||||
<ChildContent>
|
||||
<RadzenSeriesDataLabels Visible="true" />
|
||||
</ChildContent>
|
||||
@@ -16,6 +16,7 @@
|
||||
{
|
||||
public string Quarter { get; set; }
|
||||
public double Revenue { get; set; }
|
||||
public bool Highlight { get; set; }
|
||||
}
|
||||
|
||||
DataItem[] revenue = new DataItem[]
|
||||
@@ -23,6 +24,6 @@
|
||||
new DataItem { Quarter = "Q1", Revenue = 30000 },
|
||||
new DataItem { Quarter = "Q2", Revenue = 40000 },
|
||||
new DataItem { Quarter = "Q3", Revenue = 50000 },
|
||||
new DataItem { Quarter = "Q4", Revenue = 80000 },
|
||||
new DataItem { Quarter = "Q4", Revenue = 80000, Highlight = true },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,12 +19,17 @@
|
||||
</Items>
|
||||
</RadzenSelectBar>
|
||||
</RadzenStack>
|
||||
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.5rem">
|
||||
<RadzenLabel Text="Corner Radius" Component="cornerRadius" />
|
||||
<RadzenSlider @bind-Value="@cornerRadius" TValue="double" Min="0" Max="30" Step="1" Name="cornerRadius" Style="width: 120px;" />
|
||||
<RadzenText TextStyle="TextStyle.Body2" class="rz-mb-0">@cornerRadius px</RadzenText>
|
||||
</RadzenStack>
|
||||
</RadzenStack>
|
||||
</RadzenCard>
|
||||
|
||||
<RadzenStack Style="width: 100%; max-width: 600px;">
|
||||
<RadzenChart Animate="true" SeriesClick=@OnSeriesClick>
|
||||
<RadzenPieSeries FillMode="@fillMode" Data="@revenue" Title="Revenue" CategoryProperty="Quarter" ValueProperty="Revenue" ShowTooltipOnLegend="@showTooltipOnLegend">
|
||||
<RadzenPieSeries FillMode="@fillMode" CornerRadius="@cornerRadius" Data="@revenue" Title="Revenue" CategoryProperty="Quarter" ValueProperty="Revenue" ShowTooltipOnLegend="@showTooltipOnLegend">
|
||||
<RadzenSeriesDataLabels Visible="@showDataLabels" />
|
||||
</RadzenPieSeries>
|
||||
</RadzenChart>
|
||||
@@ -38,6 +43,7 @@
|
||||
bool showDataLabels = false;
|
||||
bool showTooltipOnLegend = true;
|
||||
FillMode fillMode = FillMode.Gradient;
|
||||
double cornerRadius = 0;
|
||||
|
||||
void OnSeriesClick(SeriesClickEventArgs args)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<RadzenStack class="rz-p-0 rz-p-md-6 rz-p-lg-12" AlignItems="AlignItems.Center">
|
||||
<RadzenStack Style="width: 100%; max-width: 600px;">
|
||||
<RadzenChart>
|
||||
<RadzenPieSeries FillMode="FillMode.Gradient" Data="@revenue" CategoryProperty="Quarter" ValueProperty="Revenue" ExplodeOffset="15">
|
||||
<RadzenPieSeries FillMode="FillMode.Gradient" Data="@revenue" CategoryProperty="Quarter" ValueProperty="Revenue" ExplodeOffset="15" ExplodedProperty="Highlight">
|
||||
<RadzenSeriesDataLabels Visible="true" />
|
||||
</RadzenPieSeries>
|
||||
<RadzenLegend Position="LegendPosition.Bottom" />
|
||||
@@ -14,6 +14,7 @@
|
||||
{
|
||||
public string Quarter { get; set; }
|
||||
public double Revenue { get; set; }
|
||||
public bool Highlight { get; set; }
|
||||
}
|
||||
|
||||
DataItem[] revenue = new DataItem[]
|
||||
@@ -21,6 +22,6 @@
|
||||
new DataItem { Quarter = "Q1", Revenue = 30000 },
|
||||
new DataItem { Quarter = "Q2", Revenue = 40000 },
|
||||
new DataItem { Quarter = "Q3", Revenue = 50000 },
|
||||
new DataItem { Quarter = "Q4", Revenue = 80000 },
|
||||
new DataItem { Quarter = "Q4", Revenue = 80000, Highlight = true },
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user