Customize default tooltips
The next article is a quick guide on how to customize the default tooltip,if you want to learn more you can read the full article:
Go to the full tooltips articleYou can quickly change the position, the font, the text size or the background color:
View
<UserControl x:Class="WPFSample.Axes.NamedLabels.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"
xmlns:vms="clr-namespace:ViewModelsSamples.Axes.NamedLabels;assembly=ViewModelsSamples">
<UserControl.DataContext>
<vms:ViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<lvc:CartesianChart
Series="{Binding Series}"
XAxes="{Binding XAxes}"
YAxes="{Binding YAxes}"
TooltipPosition="Left"
TooltipBackgroundPaint="{Binding TooltipBackgroundPaint}"
TooltipTextPaint="{Binding TooltipTextPaint}"
TooltipTextSize="16">
</lvc:CartesianChart>
</Grid>
</UserControl>
View model
[ObservableObject]
public partial class ViewModel
{
public ISeries[] Series { get; set; } = { ... };
public Axis[] XAxes { get; set; } = { ... };
public Axis[] YAxes { get; set; } = { ... };
public SolidColorPaint TooltipTextPaint { get; set; } = // mark
new SolidColorPaint // mark
{ // mark
Color = new SKColor(242, 244, 195), // mark
SKTypeface = SKTypeface.FromFamilyName("Courier New") // mark
}; // mark
public SolidColorPaint TooltipBackgroundPaint { get; set; } = // mark
new SolidColorPaint(new SKColor(72, 0, 50)); // mark
}
Customize tooltip format
You can define the text the tooltip will display for a given point, using the
YToolTipLabelFormatter
, XToolTipLabelFormatter
or ToolTipLabelFormatter
properties, these
properties are of type Func<ChartPoint, string>
it means that both are a function, that takes a point as parameter
and return a string, the point will be injected by LiveCharts in this function to get a string out of it when it
requires to build the text for a point in a tooltip, the injected point will be different as the user moves the pointer over the
user interface.
By default the library already defines a default formatter for every series, all the series have a different
formatters, but generally the default value uses the Series.Name
and the ChartPoint.Coordinate.PrimaryValue
properties, the following
code snippet illustrates how to build a custom tooltip formatter.
Lets take the example of the next series:"
public ISeries[] Series { get; set; } = [
new LineSeries<double>
{
Values = [2, 1, 3, 5, 3, 4, 6],
Fill = null,
GeometrySize = 20,
},
new LineSeries<int, StarGeometry>
{
Values = [4, 2, 5, 2, 4, 5, 3],
Fill = null,
GeometrySize = 20
}
];
By default the tooltip will be:
We can add format to the tooltip:
public ISeries[] Series { get; set; } = [
new LineSeries<double>
{
Values = [2, 1, 3, 5, 3, 4, 6],
Fill = null,
GeometrySize = 20,
YToolTipLabelFormatter = point => point.Model.ToString("N2") // mark
},
new LineSeries<int, StarGeometry>
{
Values = [4, 2, 5, 2, 4, 5, 3],
Fill = null,
GeometrySize = 20,
YToolTipLabelFormatter = point => point.Model.ToString("N2") // mark
}
];
We used the Model property of the point, the Model property is just the item in the Values
collection, for example in the next case, the Model property is of type City
.
public ISeries[] Series { get; set; } = [
new LineSeries<City>
{
Values = [new() { Population = 4 }, new() { Population = 2}],
YToolTipLabelFormatter = point => point.Model.Population.ToString("N2") // mark
}
];
// ...
public class City
{
public double Population { get; set; }
}
We can also show a label for the X
coordinate, the default tooltip uses the X label as the header in the tooltip.
new LineSeries<double>
{
Values = [2, 1, 3, 5, 3, 4, 6],
Fill = null,
GeometrySize = 20,
XToolTipLabelFormatter = point => point.Index.ToString(), // mark
YToolTipLabelFormatter = point => point.Model.ToString("C2")
};
When the series is "Stacked" (PieSeries
, StackedColumn
or StackedRow
) we can find information about the stacked data
in the StackedValue
property, for example:
public ISeries[] Series { get; set; } = [
new StackedColumnSeries<double>
{
Values = [2, 1, 3, 5, 3, 4, 6],
YToolTipLabelFormatter =
point => $"{point.Model} / {point.StackedValue!.Total} ({point.StackedValue.Share:P2})"
},
new StackedColumnSeries<int>
{
Values = [4, 2, 5, 2, 4, 5, 3],
YToolTipLabelFormatter =
point => $"{point.Model} / {point.StackedValue!.Total} ({point.StackedValue.Share:P2})"
}
];
Will result in:
The PieSeries class uses the ToolTipLabelFormatter
property to configure the text inside the tooltip.
Override the default tooltip behavior
You can also inherit from SKDefaultTooltip
and override the parts you need to make the tooltip behave as your app needs,
in the next example, we draw a geometry in the tooltip based on the point that is shown in the tooltip.
CustomTooltip.cs
using System;
using System.Collections.Generic;
using LiveChartsCore;
using LiveChartsCore.Drawing;
using LiveChartsCore.Drawing.Layouts;
using LiveChartsCore.Kernel;
using LiveChartsCore.SkiaSharpView.Drawing;
using LiveChartsCore.SkiaSharpView.Drawing.Geometries;
using LiveChartsCore.SkiaSharpView.Drawing.Layouts;
using LiveChartsCore.SkiaSharpView.Painting;
using LiveChartsCore.SkiaSharpView.SKCharts;
using SkiaSharp;
namespace ViewModelsSamples.General.TemplatedTooltips;
public class CustomTooltip : SKDefaultTooltip
{
// the Initialize method is used to set up the tooltip animations, colors, etc.
protected override void Initialize(Chart chart)
{
// the Wedge property, defines the size of the triangle that points to the target point.
Wedge = 5;
Geometry.Fill = new SolidColorPaint(new SKColor(200, 200, 200), 3);
Geometry.Stroke = new SolidColorPaint(new SKColor(28, 49, 58), 3);
Geometry.Wedge = Wedge;
Geometry.WedgeThickness = 2;
this.Animate(
new Animation(
EasingFunctions.BounceOut,
TimeSpan.FromMilliseconds(500)));
}
// the GetLayout method is used to define the content of the tooltip,
// it is called every time the tooltip changes.
protected override Layout<SkiaSharpDrawingContext> GetLayout(IEnumerable<ChartPoint> foundPoints, Chart chart)
{
var layout = new StackLayout
{
Padding = new(10),
Orientation = ContainerOrientation.Vertical,
HorizontalAlignment = Align.Middle,
VerticalAlignment = Align.Middle
};
var i = 0;
foreach (var point in foundPoints)
{
i++;
var series = point.Context.Series;
var geometryPoint = (GeometryPoint)point.Context.DataSource!;
var miniature =
(IDrawnElement<SkiaSharpDrawingContext>)series.GetMiniatureGeometry(point);
var label = new LabelGeometry
{
Text = point.Coordinate.PrimaryValue.ToString("C2"),
Paint = new SolidColorPaint(new SKColor(30, 30, 30)),
TextSize = 15,
Padding = new Padding(8, 0),
VerticalAlign = Align.Start,
HorizontalAlign = Align.Start
};
var customContent = new VariableSVGPathGeometry
{
Fill = new SolidColorPaint(new SKColor(30, 30, 30)),
Width = 30,
Height = 30,
SVGPath = geometryPoint.Geometry
};
var sp = new StackLayout
{
Orientation = ContainerOrientation.Horizontal,
Padding = new Padding(0, 4),
VerticalAlignment = Align.Middle,
HorizontalAlignment = Align.Middle,
Children =
{
miniature,
label,
customContent
}
};
layout.Children.Add(sp);
}
return layout;
}
public override void Show(IEnumerable<ChartPoint> foundPoints, Chart chart)
{
base.Show(foundPoints, chart);
// write code here to add custom behavior when the tooltip is shown.
}
public override void Hide(Chart chart)
{
base.Hide(chart);
// write code here to add custom behavior when the tooltip is hidden.
}
}
View
<UserControl x:Class="WPFSample.General.TemplatedTooltips.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"
xmlns:vms="clr-namespace:ViewModelsSamples.General.TemplatedTooltips;assembly=ViewModelsSamples"
xmlns:ctx="clr-namespace:LiveChartsCore.Kernel;assembly=LiveChartsCore">
<UserControl.DataContext>
<vms:ViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<lvc:CartesianChart Grid.Row="0" Series="{Binding Series}" TooltipPosition="Top" >
<!-- mark -untilCloses CartesianChart.Tooltip -->
<lvc:CartesianChart.Tooltip>
<vms:CustomTooltip></vms:CustomTooltip>
</lvc:CartesianChart.Tooltip>
</lvc:CartesianChart>
</Grid>
</UserControl>
Tooltip control from scratch
You can also create your own tooltip, the recommended way is to use the LiveCharts API but you can
use anything as tooltip as soon as it implements the IChartTooltip
interface.
The LiveCharts API can only draw inside the control bounds, in some cases it could cause issues like #912, you can use the SKDefaultTooltip source code as a guide to build your own implementation, this class is the default tooltip used by the library.
Alternatively, you can build your own Tooltips and use the power of your UI framework, see #1558 for more info.
Override Series.FindPointsInPosition
Depending on the series type and FindingStrategy, LiveCharts decides the logic to show points on tooltips and also the points passed
to any pointer event in the library (like Hover
, HoverLeft
or PointerDown
), lets take as an example the default behavior of the
ColumnSeries<T>
, it selects all the points that share the same X
coordinate:
But for this example, we want to override this behavior, instead we only need the tooltip to display the exact column where the pointer is in:
When the FindingStrategy,
is not enough, we can override the logic to determine whether a given point is inside a drawn ChartPoint
. This method
will be used by the library to resolve the points to show in a tooltip, or the points passed in any pointer event:
using System.Collections.Generic;
using System.Linq;
using LiveChartsCore;
using LiveChartsCore.Drawing;
using LiveChartsCore.Kernel;
using LiveChartsCore.Kernel.Drawing;
using LiveChartsCore.Measure;
using LiveChartsCore.SkiaSharpView;
namespace ViewModelsSamples.Events.OverrideFind;
public class ViewModel
{
public ISeries[] Series { get; set; } = [
new CustomColumnSeries<int> { Values = [9, 5, 7, 3, 7, 3] },
new CustomColumnSeries<int> { Values = [8, 2, 3, 2, 5, 2] }
];
public class CustomColumnSeries<T> : ColumnSeries<T>
{
protected override IEnumerable<ChartPoint> FindPointsInPosition(
Chart chart, LvcPoint pointerPosition, FindingStrategy strategy, FindPointFor findPointFor)
{
return Fetch(chart).Where(point =>
{
var ha = (RectangleHoverArea?)point.Context.HoverArea;
if (ha is null) return false;
var isInsideX = ha.X <= pointerPosition.X && pointerPosition.X <= ha.X + ha.Width;
var isInsideY = ha.Y <= pointerPosition.Y && pointerPosition.Y <= ha.Y + ha.Height;
return findPointFor == FindPointFor.HoverEvent
? isInsideX
: isInsideY;
});
}
}
}