From 13326879f0e2736bd802ed0a77db9ad33d3ee2db Mon Sep 17 00:00:00 2001 From: Maxim Becker Date: Mon, 28 Oct 2024 09:01:13 +0100 Subject: [PATCH] Render chart tooltips in the same way as standard tooltips (#1745) * Render chart tooltips in the same way as standard tooltips * Move OpenChartTooltip method to TooltipService to avoid code duplications * Avoid partially hiding of chart tooltip near top of page * Make some of the types internal. --------- Co-authored-by: Atanas Korchev --- Radzen.Blazor.Tests/ChartTests.cs | 10 +- Radzen.Blazor/CartesianSeries.cs | 40 ++-- Radzen.Blazor/IChartSeries.cs | 12 +- Radzen.Blazor/IChartSeriesOverlay.cs | 8 +- Radzen.Blazor/RadzenChart.razor | 6 - Radzen.Blazor/RadzenChart.razor.cs | 36 ++-- Radzen.Blazor/RadzenChartTooltip.razor | 184 ++++++++++++++++++ Radzen.Blazor/RadzenComponents.razor | 1 + Radzen.Blazor/RadzenSeriesAnnotation.cs | 8 +- Radzen.Blazor/RadzenSeriesDataLabels.razor | 8 +- Radzen.Blazor/RadzenSeriesTrendLine.razor | 8 +- Radzen.Blazor/RadzenSeriesValueLine.razor | 39 ++-- .../Rendering/ChartSharedTooltip.razor | 8 +- Radzen.Blazor/Rendering/ChartTooltip.razor | 8 +- .../Rendering/ChartTooltipContainer.razor | 12 -- Radzen.Blazor/TooltipService.cs | 39 +++- .../themes/components/blazor/_chart.scss | 42 +++- Radzen.Blazor/wwwroot/Radzen.Blazor.js | 30 ++- getting-started.md | 4 +- 19 files changed, 401 insertions(+), 102 deletions(-) create mode 100644 Radzen.Blazor/RadzenChartTooltip.razor delete mode 100644 Radzen.Blazor/Rendering/ChartTooltipContainer.razor diff --git a/Radzen.Blazor.Tests/ChartTests.cs b/Radzen.Blazor.Tests/ChartTests.cs index 147cb070..8ddb898b 100644 --- a/Radzen.Blazor.Tests/ChartTests.cs +++ b/Radzen.Blazor.Tests/ChartTests.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Bunit; +using Microsoft.Extensions.DependencyInjection; using Radzen.Blazor.Rendering; using Xunit; using Xunit.Abstractions; @@ -23,6 +24,9 @@ public class ChartTests using var ctx = new TestContext(); ctx.JSInterop.Mode = JSRuntimeMode.Loose; ctx.JSInterop.Setup("Radzen.createChart", _ => true).SetResult(new Rect {Left = 0, Top = 0, Width = 200, Height = 200}); + ctx.Services.AddScoped(); + ctx.JSInterop.SetupVoid("Radzen.openChartTooltip", _ => true); + ctx.RenderComponent(); var seriesData = Enumerable.Range(0, 5000).Select(i => new Point { X = i, Y = i }); var chart = ctx.RenderComponent(chartParameters => @@ -42,12 +46,12 @@ public class ChartTests }))); var stopwatch = Stopwatch.StartNew(); - foreach (var _ in Enumerable.Range(0, 10)) + foreach (var invocation in Enumerable.Range(0, 10)) { await chart.InvokeAsync(() => chart.Instance.MouseMove(100, 80)); - Assert.Contains("
x.Identifier == "Radzen.openChartTooltip")); await chart.InvokeAsync(() => chart.Instance.MouseMove(0, 0)); - Assert.DoesNotContain("
x.Identifier == "Radzen.closeTooltip")); } output.WriteLine($"Time took: {stopwatch.Elapsed}"); } diff --git a/Radzen.Blazor/CartesianSeries.cs b/Radzen.Blazor/CartesianSeries.cs index 03c27187..a078b7d5 100644 --- a/Radzen.Blazor/CartesianSeries.cs +++ b/Radzen.Blazor/CartesianSeries.cs @@ -478,38 +478,30 @@ namespace Radzen.Blazor } /// - public virtual RenderFragment RenderTooltip(object data, double marginLeft, double marginTop, double chartHeight) + public virtual RenderFragment RenderTooltip(object data) { var item = (TItem)data; - - var x = TooltipX(item); - var y = TooltipY(item); - + return builder => { - if (Chart.Tooltip.Shared) { var category = PropertyAccess.GetValue(item, CategoryProperty); builder.OpenComponent(0); - builder.AddAttribute(1, nameof(ChartSharedTooltip.X), x + marginLeft); - builder.AddAttribute(2, nameof(ChartSharedTooltip.Y), y + marginTop); - builder.AddAttribute(3, nameof(ChartSharedTooltip.Class), TooltipClass(item)); - builder.AddAttribute(4, nameof(ChartSharedTooltip.Title), TooltipTitle(item)); - builder.AddAttribute(4, nameof(ChartSharedTooltip.ChildContent), RenderSharedTooltipItems(category)); + builder.AddAttribute(1, nameof(ChartSharedTooltip.Class), TooltipClass(item)); + builder.AddAttribute(2, nameof(ChartSharedTooltip.Title), TooltipTitle(item)); + builder.AddAttribute(3, nameof(ChartSharedTooltip.ChildContent), RenderSharedTooltipItems(category)); builder.CloseComponent(); } else { builder.OpenComponent(0); - builder.AddAttribute(1, nameof(ChartTooltip.X), x + marginLeft); - builder.AddAttribute(2, nameof(ChartTooltip.Y), y + marginTop); - builder.AddAttribute(3, nameof(ChartTooltip.ChildContent), TooltipTemplate?.Invoke(item)); - builder.AddAttribute(4, nameof(ChartTooltip.Title), TooltipTitle(item)); - builder.AddAttribute(5, nameof(ChartTooltip.Label), TooltipLabel(item)); - builder.AddAttribute(6, nameof(ChartTooltip.Value), TooltipValue(item)); - builder.AddAttribute(7, nameof(ChartTooltip.Class), TooltipClass(item)); - builder.AddAttribute(8, nameof(ChartTooltip.Style), TooltipStyle(item)); + builder.AddAttribute(1, nameof(ChartTooltip.ChildContent), TooltipTemplate?.Invoke(item)); + builder.AddAttribute(2, nameof(ChartTooltip.Title), TooltipTitle(item)); + builder.AddAttribute(3, nameof(ChartTooltip.Label), TooltipLabel(item)); + builder.AddAttribute(4, nameof(ChartTooltip.Value), TooltipValue(item)); + builder.AddAttribute(5, nameof(ChartTooltip.Class), TooltipClass(item)); + builder.AddAttribute(6, nameof(ChartTooltip.Style), TooltipStyle(item)); builder.CloseComponent(); } }; @@ -546,6 +538,16 @@ namespace Radzen.Blazor }; } + /// + public Point GetTooltipPosition(object data) + { + var item = (TItem)data; + var x = TooltipX(item); + var y = TooltipY(item); + + return new Point { X = x, Y = y }; + } + /// /// Gets the tooltip inline style. /// diff --git a/Radzen.Blazor/IChartSeries.cs b/Radzen.Blazor/IChartSeries.cs index 74b78399..b648ea05 100644 --- a/Radzen.Blazor/IChartSeries.cs +++ b/Radzen.Blazor/IChartSeries.cs @@ -50,20 +50,24 @@ namespace Radzen.Blazor /// The value scale. /// RenderFragment. RenderFragment RenderOverlays(ScaleBase categoryScale, ScaleBase valueScale); + /// /// Renders the series tooltip. /// /// The data. - /// The left margin. - /// The right margin. - /// Height of the whole char area. /// RenderFragment. - RenderFragment RenderTooltip(object data, double marginLeft, double marginTop, double chartHeight); + RenderFragment RenderTooltip(object data); /// /// Renders a tooltip item with the specified data to be displayed in a shared tooltip /// RenderFragment RenderSharedTooltipItem(object category); /// + /// Get position of the series tooltip. + /// + /// The data. + /// Position. + Point GetTooltipPosition(object data); + /// /// Renders the legend item. /// /// RenderFragment. diff --git a/Radzen.Blazor/IChartSeriesOverlay.cs b/Radzen.Blazor/IChartSeriesOverlay.cs index 0c8a3439..96316c3f 100644 --- a/Radzen.Blazor/IChartSeriesOverlay.cs +++ b/Radzen.Blazor/IChartSeriesOverlay.cs @@ -28,6 +28,12 @@ namespace Radzen.Blazor /// /// Renders tooltip /// - RenderFragment RenderTooltip(double mouseX, double mouseY, double marginLeft, double marginTop); + RenderFragment RenderTooltip(double mouseX, double mouseY); + + /// + /// Get position of the overlay tooltip. + /// + /// Position. + Point GetTooltipPosition(double mouseX, double mouseY); } } diff --git a/Radzen.Blazor/RadzenChart.razor b/Radzen.Blazor/RadzenChart.razor index 7e18600d..5e61fd1a 100644 --- a/Radzen.Blazor/RadzenChart.razor +++ b/Radzen.Blazor/RadzenChart.razor @@ -38,12 +38,6 @@ @donut.RenderTitle(MarginLeft, MarginTop) } } - - @if (tooltip != null) - { - @tooltip - } - } diff --git a/Radzen.Blazor/RadzenChart.razor.cs b/Radzen.Blazor/RadzenChart.razor.cs index 389a0d34..bc0f4f3c 100644 --- a/Radzen.Blazor/RadzenChart.razor.cs +++ b/Radzen.Blazor/RadzenChart.razor.cs @@ -52,6 +52,9 @@ namespace Radzen.Blazor /// [Parameter] public EventCallback LegendClick { get; set; } + + [Inject] + TooltipService TooltipService { get; set; } /// /// Gets the runtime width of the chart. @@ -276,7 +279,6 @@ namespace Radzen.Blazor } } - ChartTooltipContainer chartTooltipContainer; RenderFragment tooltip; object tooltipData; double mouseX; @@ -367,11 +369,15 @@ namespace Radzen.Blazor { foreach (var overlay in series.Overlays.Reverse()) { - if (overlay.Visible && overlay.Contains(mouseX - MarginLeft, mouseY - MarginTop, TooltipTolerance)) + if (overlay.Visible && overlay.Contains(queryX, queryY, TooltipTolerance)) { tooltipData = null; - tooltip = overlay.RenderTooltip(mouseX, mouseY, MarginLeft, MarginTop); - chartTooltipContainer.Refresh(); + tooltip = overlay.RenderTooltip(queryX, queryY); + var tooltipPosition = overlay.GetTooltipPosition(queryX, queryY); + TooltipService.OpenChartTooltip(Element, tooltipPosition.X + MarginLeft, tooltipPosition.Y + MarginTop, _ => tooltip, new ChartTooltipOptions + { + ColorScheme = ColorScheme + }); await Task.Yield(); return; @@ -399,21 +405,25 @@ namespace Radzen.Blazor if (closestSeriesData != tooltipData) { tooltipData = closestSeriesData; - tooltip = closestSeries.RenderTooltip(closestSeriesData, MarginLeft, MarginTop, Height ?? 0); - chartTooltipContainer.Refresh(); + tooltip = closestSeries.RenderTooltip(closestSeriesData); + var tooltipPosition = closestSeries.GetTooltipPosition(closestSeriesData); + TooltipService.OpenChartTooltip(Element, tooltipPosition.X + MarginLeft, tooltipPosition.Y + MarginTop, _ => tooltip, new ChartTooltipOptions + { + ColorScheme = ColorScheme + }); await Task.Yield(); } return; } + } - if (tooltip != null) - { - tooltipData = null; - tooltip = null; + if (tooltip != null) + { + tooltipData = null; + tooltip = null; - chartTooltipContainer.Refresh(); - await Task.Yield(); - } + TooltipService.Close(); + await Task.Yield(); } } diff --git a/Radzen.Blazor/RadzenChartTooltip.razor b/Radzen.Blazor/RadzenChartTooltip.razor new file mode 100644 index 00000000..b1c15698 --- /dev/null +++ b/Radzen.Blazor/RadzenChartTooltip.razor @@ -0,0 +1,184 @@ +@implements IAsyncDisposable +@using Microsoft.JSInterop +@inject IJSRuntime JSRuntime + +@foreach (var tooltip in tooltips) +{ + +} + +@code { + public string UniqueID { get; set; } + + [Inject] private TooltipService Service { get; set; } + + List tooltips = new List(); + + async Task Open(ElementReference chart, double x, double y, ChartTooltipOptions options) + { + tooltips.Clear(); + tooltips.Add(new ChartTooltip { Options = options, Chart = chart, X = x, Y = y }); + + await InvokeAsync(StateHasChanged); + + var tooltip = tooltips.LastOrDefault(); + + if (tooltip != null) + { + await JSRuntime.InvokeVoidAsync("Radzen.openChartTooltip", + tooltip.Chart, + x, + y, + UniqueID); + } + } + + bool IsJSRuntimeAvailable { get; set; } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + IsJSRuntimeAvailable = true; + + var tooltip = tooltips.LastOrDefault(); + + if (tooltip != null) + { + await JSRuntime.InvokeVoidAsync("Radzen.openChartTooltip", + tooltip.Chart, + tooltip.X, + tooltip.Y, + UniqueID, + Reference, + "RadzenChartTooltip.CloseTooltip"); + } + } + + private DotNetObjectReference reference; + + /// + /// Gets the reference for the current component. + /// + /// The reference. + protected DotNetObjectReference Reference + { + get + { + if (reference == null) + { + reference = DotNetObjectReference.Create(this); + } + + return reference; + } + } + + /// + /// Closes this instance. + /// + [JSInvokable("RadzenChartTooltip.CloseTooltip")] + public void CloseTooltip() + { + Service.Close(); + } + + public async Task Close() + { + var lastTooltip = tooltips.LastOrDefault(); + if (lastTooltip != null) + { + if (IsJSRuntimeAvailable) + { + try + { + tooltips.Remove(lastTooltip); + await JSRuntime.InvokeVoidAsync("Radzen.closeTooltip", UniqueID); + } + catch + { + // ignored + } + } + + } + + await InvokeAsync(StateHasChanged); + } + + public async ValueTask DisposeAsync() + { + while (tooltips.Count != 0) + { + await Close(); + } + reference?.Dispose(); + reference = null; + + if (IsJSRuntimeAvailable) + { + try + { + await JSRuntime.InvokeVoidAsync("Radzen.destroyPopup", UniqueID); + } + catch + { + // ignored + } + } + + Service.OnOpenChartTooltip -= OnOpen; + Service.OnClose -= OnClose; + Service.OnNavigate -= OnNavigate; + } + + protected override void OnInitialized() + { + UniqueID = Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace("/", "-").Replace("+", "-").Substring(0, 10); + + Service.OnOpenChartTooltip += OnOpen; + Service.OnClose += OnClose; + Service.OnNavigate += OnNavigate; + } + + void OnOpen(ElementReference element, double x, double y, ChartTooltipOptions options) + { + Open(element, x, y, options).ConfigureAwait(false); + } + + void OnClose() + { + Close().ConfigureAwait(false); + } + + void OnNavigate() + { + JSRuntime.InvokeVoidAsync("Radzen.closePopup", UniqueID); + } + + private sealed class ChartTooltip + { + /// + /// Gets or sets the owning chart. + /// + /// The chart. + public ElementReference Chart { get; set; } + /// + /// Gets or sets the horizontal position of the tooltip. + /// + /// The position. + public double X { get; set; } + /// + /// Gets or sets the vertical position of the tooltip. + /// + /// The position. + public double Y { get; set; } + /// + /// Gets or sets the options. + /// + /// The options. + public ChartTooltipOptions Options { get; set; } + } +} diff --git a/Radzen.Blazor/RadzenComponents.razor b/Radzen.Blazor/RadzenComponents.razor index 4d1592d1..ecff8491 100644 --- a/Radzen.Blazor/RadzenComponents.razor +++ b/Radzen.Blazor/RadzenComponents.razor @@ -2,6 +2,7 @@ + @code { /// diff --git a/Radzen.Blazor/RadzenSeriesAnnotation.cs b/Radzen.Blazor/RadzenSeriesAnnotation.cs index 0b8e7f75..d327d244 100644 --- a/Radzen.Blazor/RadzenSeriesAnnotation.cs +++ b/Radzen.Blazor/RadzenSeriesAnnotation.cs @@ -152,11 +152,17 @@ namespace Radzen.Blazor } /// - public RenderFragment RenderTooltip(double mouseX, double mouseY, double marginLeft, double marginTop) + public RenderFragment RenderTooltip(double mouseX, double mouseY) { return null; } + /// + public Point GetTooltipPosition(double mouseX, double mouseY) + { + return new Point { X = mouseX, Y = mouseY }; + } + /// public void Dispose() => series?.Overlays.Remove(this); } diff --git a/Radzen.Blazor/RadzenSeriesDataLabels.razor b/Radzen.Blazor/RadzenSeriesDataLabels.razor index 87189be0..7e500b73 100644 --- a/Radzen.Blazor/RadzenSeriesDataLabels.razor +++ b/Radzen.Blazor/RadzenSeriesDataLabels.razor @@ -54,10 +54,16 @@ return false; } - public RenderFragment RenderTooltip(double mouseX, double mouseY, double marginLeft, double marginTop) + public RenderFragment RenderTooltip(double mouseX, double mouseY) { return null; } + /// + public Point GetTooltipPosition(double mouseX, double mouseY) + { + return new Point { X = mouseX, Y = mouseY }; + } + public void Dispose() => series?.Overlays.Remove(this); } \ No newline at end of file diff --git a/Radzen.Blazor/RadzenSeriesTrendLine.razor b/Radzen.Blazor/RadzenSeriesTrendLine.razor index baa21401..eee12f98 100644 --- a/Radzen.Blazor/RadzenSeriesTrendLine.razor +++ b/Radzen.Blazor/RadzenSeriesTrendLine.razor @@ -73,10 +73,16 @@ return false; } - public RenderFragment RenderTooltip(double mouseX, double mouseY, double marginLeft, double marginTop) + public RenderFragment RenderTooltip(double mouseX, double mouseY) { return null; } + /// + public Point GetTooltipPosition(double mouseX, double mouseY) + { + return new Point { X = mouseX, Y = mouseY }; + } + public void Dispose() => series?.Overlays.Remove(this); } diff --git a/Radzen.Blazor/RadzenSeriesValueLine.razor b/Radzen.Blazor/RadzenSeriesValueLine.razor index b1596103..a78a9ad0 100644 --- a/Radzen.Blazor/RadzenSeriesValueLine.razor +++ b/Radzen.Blazor/RadzenSeriesValueLine.razor @@ -88,19 +88,15 @@ } } - public RenderFragment RenderTooltip(double mouseX, double mouseY, double marginLeft, double marginTop) + public RenderFragment RenderTooltip(double mouseX, double mouseY) { string text; if (Chart.ShouldInvertAxes()) { - mouseX = Chart.CategoryScale.Scale(Value) + marginLeft; - mouseY = Math.Max(marginTop, Math.Min(Chart.ValueScale.OutputSize + marginTop, mouseY)); text = Chart.ValueAxis.Format(Chart.CategoryScale, Value); } else { - mouseY = Chart.ValueScale.Scale(Value) + marginTop; - mouseX = Math.Max(marginLeft, Math.Min(Chart.CategoryScale.OutputSize + marginLeft, mouseX)); text = Chart.ValueAxis.Format(Chart.ValueScale, Value); } @@ -108,20 +104,35 @@ { builder.OpenComponent(0); - builder.AddAttribute(1, nameof(ChartTooltip.X), mouseX); - builder.AddAttribute(2, nameof(ChartTooltip.Y), mouseY); + builder.AddAttribute(1, nameof(ChartTooltip.ChildContent), TooltipTemplate?.Invoke(Value)); - builder.AddAttribute(3, nameof(ChartTooltip.ChildContent), TooltipTemplate == null ? null : TooltipTemplate(Value)); + builder.AddAttribute(6, nameof(ChartTooltip.Title), series.GetTitle()); + builder.AddAttribute(2, nameof(ChartTooltip.Label), Name); + builder.AddAttribute(3, nameof(ChartTooltip.Value), text); - builder.AddAttribute(8, nameof(ChartTooltip.Title), series.GetTitle()); - builder.AddAttribute(4, nameof(ChartTooltip.Label), Name); - builder.AddAttribute(5, nameof(ChartTooltip.Value), text); - - builder.AddAttribute(6, nameof(ChartTooltip.Style), $"border: 1px solid {Stroke};"); - builder.AddAttribute(7, nameof(ChartTooltip.Class), ""); + builder.AddAttribute(4, nameof(ChartTooltip.Style), $"border: 1px solid {Stroke};"); + builder.AddAttribute(5, nameof(ChartTooltip.Class), ""); builder.CloseComponent(); }; } + + /// + public Point GetTooltipPosition(double mouseX, double mouseY) + { + if (Chart.ShouldInvertAxes()) + { + mouseX = Chart.CategoryScale.Scale(Value); + mouseY = Math.Max(0, Math.Min(Chart.ValueScale.OutputSize, mouseY)); + } + else + { + mouseY = Chart.ValueScale.Scale(Value); + mouseX = Math.Max(0, Math.Min(Chart.CategoryScale.OutputSize, mouseX)); + } + + return new Point { X = mouseX, Y = mouseY }; + } + public void Dispose() => series?.Overlays.Remove(this); } \ No newline at end of file diff --git a/Radzen.Blazor/Rendering/ChartSharedTooltip.razor b/Radzen.Blazor/Rendering/ChartSharedTooltip.razor index da6d0232..35c801f7 100644 --- a/Radzen.Blazor/Rendering/ChartSharedTooltip.razor +++ b/Radzen.Blazor/Rendering/ChartSharedTooltip.razor @@ -1,4 +1,4 @@ -
+
@Title
@ChildContent @@ -9,12 +9,6 @@ [Parameter] public RenderFragment ChildContent { get; set; } - [Parameter] - public double X { get; set; } - - [Parameter] - public double Y { get; set; } - [Parameter] public string Class { get; set; } diff --git a/Radzen.Blazor/Rendering/ChartTooltip.razor b/Radzen.Blazor/Rendering/ChartTooltip.razor index a40ffcbf..8d8ead8e 100644 --- a/Radzen.Blazor/Rendering/ChartTooltip.razor +++ b/Radzen.Blazor/Rendering/ChartTooltip.razor @@ -1,4 +1,4 @@ -
+
@if (ChildContent != null) { @@ -21,12 +21,6 @@ [Parameter] public RenderFragment ChildContent { get; set; } - [Parameter] - public double X { get; set; } - - [Parameter] - public double Y { get; set; } - [Parameter] public string Title { get; set; } diff --git a/Radzen.Blazor/Rendering/ChartTooltipContainer.razor b/Radzen.Blazor/Rendering/ChartTooltipContainer.razor deleted file mode 100644 index af7705e4..00000000 --- a/Radzen.Blazor/Rendering/ChartTooltipContainer.razor +++ /dev/null @@ -1,12 +0,0 @@ -@ChildContent - -@code { - [Parameter] - public RenderFragment ChildContent { get; set; } - - internal void Refresh() - { - StateHasChanged(); - } - -} \ No newline at end of file diff --git a/Radzen.Blazor/TooltipService.cs b/Radzen.Blazor/TooltipService.cs index cf54bc5a..aa499d7d 100644 --- a/Radzen.Blazor/TooltipService.cs +++ b/Radzen.Blazor/TooltipService.cs @@ -3,11 +3,12 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Radzen.Blazor; namespace Radzen { /// - /// Class TooltipService. Contains various methods with options to open and close tooltips. + /// Class TooltipService. Contains various methods with options to open and close tooltips. /// Should be added as scoped service in the application services and RadzenTooltip should be added in application main layout. /// Implements the /// @@ -75,6 +76,11 @@ namespace Radzen ///
public event Action OnOpen; + /// + /// Occurs when [on open chart tooltip]. + /// + internal event Action OnOpenChartTooltip; + /// /// Opens the specified element. /// @@ -169,6 +175,23 @@ namespace Radzen OpenTooltip(element, options); } + /// + /// Opens the specified chart tooltip. + /// + /// The chart element. + /// + /// + /// Content of the chart tooltip. + /// The options of the chart tooltip. + internal void OpenChartTooltip(ElementReference element, double x, double y, RenderFragment childContent, ChartTooltipOptions o = null) + { + var options = o ?? new ChartTooltipOptions(); + + options.ChildContent = childContent; + + OnOpenChartTooltip?.Invoke(element, x, y, options); + } + /// /// Opens the tooltip. /// @@ -288,4 +311,18 @@ namespace Radzen /// The element. public ElementReference Element { get; set; } } + + internal class ChartTooltipOptions + { + /// + /// Gets or sets the color scheme used to render the tooltip. + /// + /// The color scheme. + public ColorScheme ColorScheme { get; set; } + /// + /// Gets or sets the child content. + /// + /// The child content. + public RenderFragment ChildContent { get; set; } + } } diff --git a/Radzen.Blazor/themes/components/blazor/_chart.scss b/Radzen.Blazor/themes/components/blazor/_chart.scss index fd3a4978..07a7b726 100644 --- a/Radzen.Blazor/themes/components/blazor/_chart.scss +++ b/Radzen.Blazor/themes/components/blazor/_chart.scss @@ -234,12 +234,18 @@ $chart-color-schemes: ( .rz-chart-tooltip { position: absolute; - transform: translate(-50%, -100%); - transition: top 0.2s, left 0.2s; top: 0; left: 0; } +.rz-top-chart-tooltip { + transform: translate(-50%, -100%); +} + +.rz-bottom-chart-tooltip { + transform: translate(-50%, 0); +} + .rz-chart-tooltip-content { background: var(--rz-chart-tooltip-background); color: var(--rz-chart-tooltip-color); @@ -253,16 +259,25 @@ $chart-color-schemes: ( .rz-chart-tooltip { &:not(.rz-pie-tooltip) { .rz-chart-tooltip-content { - margin-bottom: 15px; &:after { content: ' '; position: absolute; width: 8px; height: 8px; - left: 50%; - bottom: 0; background-color: inherit; transform-origin: center; + } + } + } +} + +.rz-top-chart-tooltip { + &:not(.rz-pie-tooltip) { + .rz-chart-tooltip-content { + margin-bottom: 15px; + &:after { + left: 50%; + bottom: 0; transform: translate(-50%, -11px) rotate(45deg); border-bottom: inherit; border-right: inherit; @@ -271,6 +286,21 @@ $chart-color-schemes: ( } } +.rz-bottom-chart-tooltip { + &:not(.rz-pie-tooltip) { + .rz-chart-tooltip-content { + margin-top: 15px; + &:after { + left: 50%; + top: 0; + transform: translate(-50%, 11px) rotate(45deg); + border-top: inherit; + border-left: inherit; + } + } + } +} + .rz-chart-tooltip-title { font-weight: 700; } @@ -299,7 +329,7 @@ $chart-color-schemes: ( justify-content: space-between; gap: 1rem; padding: 0.125rem 0.5rem; - border-radius: var( --rz-chart-tooltip-item-border-radius); + border-radius: var(--rz-chart-tooltip-item-border-radius); &:hover { background-color: var(--rz-chart-tooltip-item-hover-background-color); diff --git a/Radzen.Blazor/wwwroot/Radzen.Blazor.js b/Radzen.Blazor/wwwroot/Radzen.Blazor.js index b0556db8..15c71fca 100644 --- a/Radzen.Blazor/wwwroot/Radzen.Blazor.js +++ b/Radzen.Blazor/wwwroot/Radzen.Blazor.js @@ -1746,9 +1746,6 @@ window.Radzen = { var inside = false; ref.mouseMoveHandler = this.throttle(function (e) { if (inside) { - if (e.target.matches('.rz-chart-tooltip-content') || e.target.closest('.rz-chart-tooltip-content')) { - return - } var rect = ref.getBoundingClientRect(); var x = e.clientX - rect.left; var y = e.clientY - rect.top; @@ -1758,7 +1755,10 @@ window.Radzen = { ref.mouseEnterHandler = function () { inside = true; }; - ref.mouseLeaveHandler = function () { + ref.mouseLeaveHandler = function (e) { + if (e.relatedTarget && (e.relatedTarget.matches('.rz-chart-tooltip') || e.relatedTarget.closest('.rz-chart-tooltip'))) { + return; + } inside = false; instance.invokeMethodAsync('MouseMove', -1, -1); }; @@ -2440,5 +2440,27 @@ window.Radzen = { } else { start(); } + }, + openChartTooltip: function (chart, x, y, id, instance, callback) { + Radzen.closeTooltip(id); + + var chartRect = chart.getBoundingClientRect(); + x = Math.max(2, chartRect.left + x); + y = Math.max(2, chartRect.top + y); + Radzen.openPopup(chart, id, false, null, x, y, instance, callback, true, false, false); + + var popup = document.getElementById(id); + if (!popup) { + return; + } + var tooltipContent = popup.children[0]; + var tooltipContentRect = tooltipContent.getBoundingClientRect(); + var tooltipContentClassName = 'rz-top-chart-tooltip'; + if (y - tooltipContentRect.height < 0) { + tooltipContentClassName = 'rz-bottom-chart-tooltip'; + } + tooltipContent.classList.remove('rz-top-chart-tooltip'); + tooltipContent.classList.remove('rz-bottom-chart-tooltip'); + tooltipContent.classList.add(tooltipContentClassName); } }; diff --git a/getting-started.md b/getting-started.md index daaf9c18..a0e81eb4 100644 --- a/getting-started.md +++ b/getting-started.md @@ -176,9 +176,9 @@ The Radzen Blazor components are distributed via the Radzen.Blazor -

Dialog, context menu, tooltip and notification

+

Dialog, context menu, tooltip, chart tooltip and notification

-To use RadzenDialog, RadzenContextMenu, RadzenTooltip and RadzenNotification you need to perform a few additional steps. +To use RadzenDialog, RadzenContextMenu, RadzenTooltip, RadzenChartTooltip and RadzenNotification you need to perform a few additional steps.

  1. Open MainLayout.razor and add this code