diff --git a/src/Components/Charts/Chart/Renderer/ChartElementRenderer.cs b/src/Components/Charts/Chart/Renderer/ChartElementRenderer.cs index 08e0c3f..696eea3 100644 --- a/src/Components/Charts/Chart/Renderer/ChartElementRenderer.cs +++ b/src/Components/Charts/Chart/Renderer/ChartElementRenderer.cs @@ -128,6 +128,11 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) return; } + if (IsStaticSSR()) + { + SetDefaultRendererValues(); + } + this.CreateCascadingValue(builder, 0, 1, this, 2, BuildRenderers); RendererShouldRender = false; @@ -203,6 +208,13 @@ internal void SetDefaultRendererContainerValues() internal void Prerender() { ContainerUpdate = true; + + if (IsStaticSSR()) + { + ContainerPrerender = true; + RendererShouldRender = true; + SetDefaultRendererValues(); + } _ = InvokeAsync(StateHasChanged); } #endregion diff --git a/src/Components/Charts/Chart/Renderer/SeriesRenderers/BaseRenderers/SeriesContainer.cs b/src/Components/Charts/Chart/Renderer/SeriesRenderers/BaseRenderers/SeriesContainer.cs index def1817..3edd417 100644 --- a/src/Components/Charts/Chart/Renderer/SeriesRenderers/BaseRenderers/SeriesContainer.cs +++ b/src/Components/Charts/Chart/Renderer/SeriesRenderers/BaseRenderers/SeriesContainer.cs @@ -375,7 +375,10 @@ private static void CreateSeriesNestedElements(RenderTreeBuilder builder, ChartS { int seq = 0; ChartSeries series = element; - if (series.Marker?.RendererType is not null) + + bool shouldOpenMarker = series.Marker?.RendererType is not null && + (series.Marker.Visible || series.Marker.DataLabel.Visible); + if (shouldOpenMarker) { builder.OpenComponent(seq++, series.Marker.RendererType); builder.AddAttribute(seq++, "Series", series); @@ -972,14 +975,21 @@ internal override void SetDefaultRendererValues() ProcessData(); Owner?._legendRenderer?.SetDefaultRendererValues(); Owner?._axisContainer?.SetDefaultRendererContainerValues(); - } - catch - { - if (!IsDisposed) + if (Owner?.InitialRect is not null) { - throw; + HandleChartSizeChange(Owner.InitialRect); + foreach (var seriesRenderer in Renderers.OfType()) + { + seriesRenderer.Series?.Marker?.Renderer?.HandleChartSizeChange(Owner.InitialRect); + } + Owner?._striplineBehindContainer?.SetDefaultRendererValues(); + Owner?._striplineOverContainer?.SetDefaultRendererValues(); } } + catch (Exception exception) when (IsDisposed) + { + System.Diagnostics.Debug.WriteLine($"Chart series renderer initialization failed during disposal: {exception}"); + } } internal void OnThemeChanged() @@ -1099,6 +1109,19 @@ public override void HandleChartSizeChange(Rect rect) { renderer.HandleChartSizeChange(rect); } + + if (IsStaticSSR()) + { + // Markers again if they were null during series pass + foreach (var seriesRenderer in Renderers.OfType()) + { + seriesRenderer.Series?.Marker?.Renderer?.HandleChartSizeChange(rect); + } + + // Striplines after axis + series clip are final + Owner?._striplineBehindContainer?.UpdateStriplineCollection(); + Owner?._striplineOverContainer?.UpdateStriplineCollection(); + } } /// diff --git a/src/Components/Charts/Chart/Renderer/SeriesRenderers/MarkerRenders/ChartMarkerRenderer.cs b/src/Components/Charts/Chart/Renderer/SeriesRenderers/MarkerRenders/ChartMarkerRenderer.cs index 36bc881..d374836 100644 --- a/src/Components/Charts/Chart/Renderer/SeriesRenderers/MarkerRenders/ChartMarkerRenderer.cs +++ b/src/Components/Charts/Chart/Renderer/SeriesRenderers/MarkerRenders/ChartMarkerRenderer.cs @@ -13,6 +13,8 @@ internal class ChartMarkerRenderer : ChartRenderer, IChartElementRenderer #region Fields private string? _seriesIndex; private List _symbolOptions = []; + // Static SSR is allowed once; subsequent passes use the normal renderer gate. + private bool _staticSSRRenderPending = true; #endregion #region Properties @@ -359,6 +361,16 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) RenderMarkers(builder); builder.CloseElement(); } + + if (IsStaticSSR()) + { + _staticSSRRenderPending = false; + } + } + + protected override bool ShouldRender() + { + return RendererShouldRender || (IsStaticSSR() && _staticSSRRenderPending); } #endregion @@ -377,7 +389,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) /// Calculated SymbolOptions instance. internal static SymbolOptions CalculateSymbol(ChartEventLocation location, string shape, Size size, string url, PathOptions option, SfChart chart) { - ChartEventLocation currentLocation = null!; + ChartEventLocation currentLocation = chart.IsStaticServerRendering() ? location : null!; if (chart is not null && shape == "Circle") { string[] locations = ChartHelper.AppendTextElements(chart, option.Id, location.X, location.Y, "cx", "cy"); @@ -388,7 +400,10 @@ internal static SymbolOptions CalculateSymbol(ChartEventLocation location, strin if (shapeoption.ShapeName == ShapeName.Path) { - shapeoption.PathOption.Direction = ChartHelper.AppendPathElements(chart ?? null!, shapeoption.PathOption.Direction, shapeoption.PathOption.Id); + if (chart != null) + { + shapeoption.PathOption.Direction = ChartHelper.AppendPathElements(chart, shapeoption.PathOption.Direction, shapeoption.PathOption.Id); + } shapeoption.PathOption.Visibility = option.Visibility; } @@ -506,6 +521,20 @@ internal void UpdateMarkerBorderWidth() } } + /// + /// SSR entry point – computes marker symbols when OnAfterRenderAsync is unavailable. + /// + internal override void SetDefaultRendererValues() + { + SeriesRenderer = Series?.Renderer; + if (SeriesRenderer != null && (Series?.Marker?.Visible ?? false)) + { + CalculateRenderTreeBuilderOptions(); // fills _symbolOptions + SeriesRenderer.CalculateMarkerClipPath(); + } + RendererShouldRender = true; + } + #endregion #region Public Methods diff --git a/src/Components/Charts/Chart/Renderer/StriplineRenderer/ChartStriplineContainer.cs b/src/Components/Charts/Chart/Renderer/StriplineRenderer/ChartStriplineContainer.cs index 0700b03..7629e5f 100644 --- a/src/Components/Charts/Chart/Renderer/StriplineRenderer/ChartStriplineContainer.cs +++ b/src/Components/Charts/Chart/Renderer/StriplineRenderer/ChartStriplineContainer.cs @@ -65,7 +65,9 @@ internal override void SetDefaultRendererValues() { ClipRect = Owner._axisContainer.AxisLayout.SeriesClipRect ?? null!; } + HandleChartSizeChange(Owner?.InitialRect ?? new Rect(0, 0, 0, 0)); + RendererShouldRender = true; } #endregion diff --git a/src/Components/Charts/Chart/Renderer/StriplineRenderer/ChartStriplineRenderer.cs b/src/Components/Charts/Chart/Renderer/StriplineRenderer/ChartStriplineRenderer.cs index 76fc6af..5c30a2b 100644 --- a/src/Components/Charts/Chart/Renderer/StriplineRenderer/ChartStriplineRenderer.cs +++ b/src/Components/Charts/Chart/Renderer/StriplineRenderer/ChartStriplineRenderer.cs @@ -643,6 +643,18 @@ internal void UpdateCustomization(string property) } } + /// + /// SSR entry point – calculates stripline geometry when OnAfterRenderAsync is unavailable. + /// + internal override void SetDefaultRendererValues() + { + if (Stripline != null) + { + InitStripline(); // this is the method that already exists and fills the path/rect collections + } + RendererShouldRender = true; + } + #endregion #region Public Methods diff --git a/src/Components/Charts/Chart/Series/Marker/ChartMarker.cs b/src/Components/Charts/Chart/Series/Marker/ChartMarker.cs index 224adfa..9ffb452 100644 --- a/src/Components/Charts/Chart/Series/Marker/ChartMarker.cs +++ b/src/Components/Charts/Chart/Series/Marker/ChartMarker.cs @@ -1,6 +1,7 @@ -using System.ComponentModel; -using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; +using Syncfusion.Blazor.Toolkit.Charts.Internal; +using System.ComponentModel; namespace Syncfusion.Blazor.Toolkit.Charts { @@ -99,9 +100,26 @@ private void UpdateLegend() /// private void SetDefaultRendererValues() { - if (Series?.Container is not null && Series.Renderer is not null && Series.Renderer.IsStaticSSR()) + // Guard everything – any of these can still be null on the first SSR pass + if (Series == null || Series.Container == null || Series.Renderer == null) { - Series.Container._seriesContainer?.Prerender(); + return; + } + + if (!Series.Renderer.IsStaticSSR()) + { + return; + } + + // 1. Force the series container to run its layout / prerender + Series.Container._seriesContainer?.Prerender(); + + // Recalculate symbols when the marker renderer component already exists. + var markerRenderer = Series.Marker?.Renderer; + if (markerRenderer != null) + { + var rect = Series.Container.InitialRect ?? new Rect(0, 0, 0, 0); + markerRenderer.HandleChartSizeChange(rect); } } diff --git a/src/Components/Charts/Chart/SfChart.razor.cs b/src/Components/Charts/Chart/SfChart.razor.cs index 4f8d94b..d54030b 100644 --- a/src/Components/Charts/Chart/SfChart.razor.cs +++ b/src/Components/Charts/Chart/SfChart.razor.cs @@ -588,7 +588,7 @@ private double GetInitialCoordinate(double adaptiveMargin, double staticMargin, private void CalculateAvailableSize() { double height = Height != NullDimensionValue ? ChartHelper.StringToNumber(Height, _elementOffset.Height) : ChartDefaultHeight; - double width = Width != NullDimensionValue ? ChartHelper.StringToNumber(Width, _elementOffset.Width) : ChartDefaultHeight; + double width = Width != NullDimensionValue ? ChartHelper.StringToNumber(Width, _elementOffset.Width) : ChartDefaultWidth; AvailableSize = new Size(width > 0 ? width : AvailableSize.Width, height > 0 ? height : AvailableSize.Height); if (EnableAdaptiveRendering) @@ -1718,16 +1718,26 @@ internal void InitializeStaticChart() { if (!_render.IsSizeSet) { + // Force defaults when no DOM measurement is possible + if (_elementOffset.Width <= 0 || _elementOffset.Height <= 0) + { + _svgWidth ??= ChartDefaultWidth.ToString(CultureInfo.InvariantCulture); + _svgHeight ??= ChartDefaultHeight.ToString(CultureInfo.InvariantCulture); + } + CalculateAvailableSize(); SetInitialRect(); + + // Border initialization reaches this method before the normal layout pass; + // process data first so the synchronous prerender sees populated renderers. + // IsSizeSet keeps this SSR-only initialization from repeating. + ProcessData(); + Prerender(); } } - catch + catch (Exception exception) when (IsDisposed) { - if (!IsDisposed) - { - throw; - } + System.Diagnostics.Debug.WriteLine($"Static chart initialization failed during disposal: {exception}"); } } diff --git a/src/Components/Charts/Common/ChartUtils/ChartHelper.cs b/src/Components/Charts/Common/ChartUtils/ChartHelper.cs index 78f7518..215afd9 100644 --- a/src/Components/Charts/Common/ChartUtils/ChartHelper.cs +++ b/src/Components/Charts/Common/ChartUtils/ChartHelper.cs @@ -1215,6 +1215,14 @@ internal static string AppendPathElements(SfChart chart, string direction, strin /// An array containing the string representation of X and Y coordinates. internal static string[] AppendTextElements(SfChart chart, string id, double locationX, double locationY, string x = "x", string y = "y") { + if (chart == null && chart.IsStaticServerRendering()) + { + return new string[] + { + locationX.ToString(CultureInfo.InvariantCulture), + locationY.ToString(CultureInfo.InvariantCulture) + }; + } bool redraw = chart._redraw; if (chart._textAnimationElements.TryGetValue(id, out DynamicTextAnimationOptions? existElement))