Hi,
I've a collection data, X values are hours, Y values some values.
I want to display my data hourly, BUT, i want the X labels to show only Days.
With that code :
<telerik:DateTimeContinuousAxis
LabelFormat="dd/MM"
LabelInterval="1"
LabelOffset="1"
MajorStepUnit="Day"
MajorStep="1"
PlotMode="OnTicks"
LabelRotationAngle="-45"
LabelFitMode="None"
VerticalLocation="Bottom">
My data are displayed hour by hour, the X label is the day, but the label is under the Ticks on the left. What i want is the label centered, idealy bordered on the whole area where the label is displayed (please see screenshot).
In addition, i display major lines on Days :
<telerik:RadCartesianChart.Grid>
<telerik:CartesianChartGrid MajorLinesVisibility="X">
<telerik:CartesianChartGrid.MajorXLineStyle>
<Style TargetType="Line">
<Setter Property="Stroke" Value="Black" />
</Style>
</telerik:CartesianChartGrid.MajorXLineStyle>
</telerik:CartesianChartGrid>
</telerik:RadCartesianChart.Grid>
I would like to display minor line every six hours too (please see screenshot on the two first days).
How can i do this ?
Thanks in advance
13 Answers, 1 is accepted
Hello Yoan,
To achieve your requirement you can consider setting the PlotMode set to BetweenTicks. Also, you can see if the DateTimeCategoricalAxis is suitable for your scenario. If not, you can try to achieve your requirement with custom annotations. You can see such approach demonstrated in the AxisLikeAnnotations SDK example.
Regards,
Martin Ivanov
Progress Telerik
Virtual Classroom, the free self-paced technical training that gets you up to speed with Telerik and Kendo UI products quickly just got a fresh new look + new and improved content including a brand new Blazor course! Check it out at https://learn.telerik.com/.
Thanks for your answer.
I tested the SDK Annotations and i succeed to do what i asked : centering labels on X Axis.
But unfortunately, this solution make me lost all the fonctionnality of resizing axis when panning or zooming which are natively done by the RadChartView. If i zoom, labels doesn't stay centered and move away on the left...
Looking on the forum, i found this thread
The solution of defining a Label template shown in the exemple would be good if we can set the left margin of the TextBlock dynamically on resize, pan or zoom.
Is there a way to catch an event while chart axis is creating or drawing to get a reference to the textblock defined in the LabelTemplate and set dynamically it's left margin (calculated in the same way as the SDK exemple whith the PlotArea Size).
Otherwise, is there a way to define by code the LabelTemplate on init, and when the charts is updating while panning or zoomming ?
Thanks in advance
Yoan
Hello Yoan,
There are a couple of approaches that you can try here. The first one is to use the PanOffsetChanged event which fires on zoom and pan. In the event handler, you can re-create the DataTemplate and reset the LabelTemplate property. This will trigger redrawing of the labels. The second approach is to data bind the Margin of the labels content to a property of the window or a view model. In this case, you can use the PanOffsetChanged event to update this property which will update the data binding and therefore the size of the labels.
I hope this helps.
Regards,
Martin Ivanov
Progress Telerik
Virtual Classroom, the free self-paced technical training that gets you up to speed with Telerik and Kendo UI products quickly just got a fresh new look + new and improved content including a brand new Blazor course! Check it out at https://learn.telerik.com/.
Hello,
thanks for your answer. I tried the second approch, binding the template textblock margin.
It works fine, margin is changing while pan, zoom or resize.
Unfortunately, the label is not always center.
The best think would be that i can know the drawing size avec the axis categorie area, which changes every zoom, pan or resize (see screenshot).
Is there a way to get this size at the end of the plotting routine ?
thanks in advance
Yoan
Here is my code.
Please note that i calculate the center by cheking whose point is visible in order to know which axis categories are displayed :
MainWindow
<Window x:Class="MainWindow" xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" xmlns:local="clr-namespace:POChartTelerik" mc:Ignorable="d" Title="MainWindow" WindowStartupLocation="CenterScreen" WindowState="Maximized"> <DockPanel> <TextBlock DockPanel.Dock="Top" x:Name="tbMarge" Text="{Binding MargeStr}" /> <telerik:RadCartesianChart x:Name="chart"> <telerik:RadCartesianChart.Behaviors> <telerik:ChartPanAndZoomBehavior /> <telerik:ChartSelectionBehavior DataPointSelectionMode="Single" HitTestMargin="2"/> <telerik:ChartTrackBallBehavior ShowIntersectionPoints="True" local:ChartViewUtilities.TrackBallGroup="g1" /> </telerik:RadCartesianChart.Behaviors> <telerik:RadCartesianChart.Grid> <telerik:CartesianChartGrid MajorLinesVisibility="X" /> </telerik:RadCartesianChart.Grid> <telerik:RadCartesianChart.VerticalAxis> <telerik:LinearAxis x:Name="verticalAxisBas"/> </telerik:RadCartesianChart.VerticalAxis> <telerik:RadCartesianChart.HorizontalAxis> <telerik:DateTimeContinuousAxis LabelInterval="1" LabelOffset="0" SmartLabelsMode="SmartStep" LabelFormat="dd/MM/yyyy" MajorStepUnit="Day" MajorStep="1" PlotMode="OnTicks"> <telerik:DateTimeContinuousAxis.LabelTemplate> <DataTemplate> <Border BorderThickness="1" BorderBrush="Black" Margin="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=Marge}"> <TextBlock Text="{Binding}" FontSize="12" /> </Border> </DataTemplate> </telerik:DateTimeContinuousAxis.LabelTemplate> </telerik:DateTimeContinuousAxis> </telerik:RadCartesianChart.HorizontalAxis> <telerik:RadCartesianChart.Series> <telerik:LineSeries> <telerik:LineSeries.StrokeShapeStyle> <Style TargetType="Path"> <Setter Property="StrokeThickness" Value="2"/> <Setter Property="Stroke" Value="DarkSlateGray"/> </Style> </telerik:LineSeries.StrokeShapeStyle> </telerik:LineSeries> </telerik:RadCartesianChart.Series> </telerik:RadCartesianChart> </DockPanel></Window>
MainWindows.xaml.vb
Imports System.ComponentModelImports Telerik.ChartingImports Telerik.Windows.Controls.ChartViewClass MainWindow Implements INotifyPropertyChanged Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged Private _marge As Thickness Public Property Marge() As Thickness Get Return _marge End Get Set(value As Thickness) _marge = value MargeStr = _marge.Left.ToString() RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Marge")) RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("MargeStr")) End Set End Property Public Property MargeStr() As String Public Sub New() ' Cet appel est requis par le concepteur. InitializeComponent() ' Ajoutez une initialisation quelconque après l'appel InitializeComponent(). Me.DataContext = Me 'Marge = new Thickness(50,0,0,0) 'Génération de fausses mesures FillSeries(DirectCast(chart.Series(0), LineSeries), GenerateDataSource(97, 40)) CType(Me.chart.HorizontalAxis, DateTimeContinuousAxis).Maximum = Now.AddDays(3).Date AddHandler chart.PanOffsetChanged, AddressOf Chart_PanOffsetChanged AddHandler verticalAxisBas.ActualRangeChanged, AddressOf VerticalAxis_ActualRangeChanged AddHandler chart.SizeChanged, AddressOf Chart_SizeChanged End Sub Private Sub Chart_PanOffsetChanged(sender As Object, e As ChartPanOffsetChangedEventArgs) SetMargin End Sub Private Sub Chart_SizeChanged(sender As Object, e As SizeChangedEventArgs) SetMargin End Sub Private Sub VerticalAxis_ActualRangeChanged(sender As Object, e As NumericalRangeChangedEventArgs) SetMargin End Sub Private Sub SetMargin() Dim width = chart.PlotAreaClip.Width dim points = FindFirstLastVisiblePoints(chart.Series(0)) dim nb = CDate(CType(points(1), CategoricalDataPoint).Category).Date - CDate(CType(points(0), CategoricalDataPoint).Category).Date ' Marge = New Thickness(width / 8, 0, 0, 0) Marge = New Thickness(width / ((nb.Days ) * 2), 0, 0, 0) End Sub Private Sub FillSeries(series As LineSeries, dataSource As List(Of DummyModel)) Dim categoryBinding = New PropertyNameDataPointBinding With { .PropertyName = "DateModel" } Dim valueBinding = New PropertyNameDataPointBinding With { .PropertyName = "Value" } series.CategoryBinding = categoryBinding series.ValueBinding = valueBinding series.ItemsSource = dataSource End Sub Private Function GenerateDataSource(valuesCount As Integer, maxValue As Integer) As List(Of DummyModel) Randomize() 'Création d'une date fixe pour le début de génération des points (on enlève 1h pour bien débuter à 0h lors de la création de la 1ere valeur) Dim firstDate As Date = Date.Now.Date.AddDays(-1) Dim lastVal = New List(Of Integer) For i = 1 To (valuesCount) lastVal.Add(Int(Rnd() * maxValue)) Next Dim dataSource = New List(Of DummyModel) For Each rdm In lastVal Dim obj = New DummyModel With { .DateModel = firstDate, .Value = rdm } dataSource.Add(obj) firstDate = firstDate.AddHours(1) Next Return dataSource End Function Private Function FindFirstLastVisiblePoints(ByVal series As CategoricalSeries) As DataPoint() Dim firstPoint As DataPoint = Nothing Dim lastPoint As DataPoint = Nothing Dim plotArea As RadRect = Me.chart.PlotAreaClip For Each point As DataPoint In series.DataPoints If point.LayoutSlot.IntersectsWith(plotArea) Then If firstPoint Is Nothing Then firstPoint = point End If lastPoint = point End If Next Return New DataPoint() {firstPoint, lastPoint} End FunctionEnd Class
Hello Yoan,
If I understand your question correctly, you need to calculate the length of the categorical slot of the axis. Note that you can do this only if you use CategoricalAxis or DateTimeCategoricalAxis. The DateTimeContinuousAxis doesn't have notion for categories. In case you decide to use a categorical axis, you can calculate the slot size by dividing the size of the plot area by the number of categories which you can get with the Categories property of the axis.
For the continuous and numeric axes there is no reliable approach to measure the size between two ticks. One way that you can try is to use the Conversion API of the chart to get the position of the first date on axis and the next one (based on the major step). This will allow you to calculate the length between the two values which in the general case should be the distance between two adjacent ticks. Then you can use the PanOffset, ActualVisibleRangeChanged and SizeChanged events to update the margin. Here is an example in code:
Private Sub UpdateMargin()
Dim minimum = axis.ActualRange.Minimum
Dim nextValue = minimum.AddDays(1)
Dim startPoint = chart.ConvertDataToPoint(New Telerik.Charting.DataTuple(minimum, 0))
Dim endPoint = chart.ConvertDataToPoint(New Telerik.Charting.DataTuple(nextValue, 0))
Dim length = endPoint.X - startPoint.X
Marge = New Thickness(length / 2, 0, 0, 0)
End Sub
Private Sub chart_PanOffsetChanged(ByVal sender As Object, ByVal e As ChartPanOffsetChangedEventArgs)
UpdateMargin()
End SubI hope that helps.
Regards,
Martin Ivanov
Progress Telerik
Virtual Classroom, the free self-paced technical training that gets you up to speed with Telerik and Kendo UI products quickly just got a fresh new look + new and improved content including a brand new Blazor course! Check it out at https://learn.telerik.com/.
Thanks for your answer.
Unfortunately, the value of the "length" var of your code has always the same value, which is the initial value before zooming or panning.
Each time the given events are raised while zoom or pan, startPoint and endPoint values are always the same.
It's seems that the zoom or pan factor are not include in chart.ConvertDataToPoint
Thanks for your help
Yoan
Hello Yoan,
In case you are using the zoom feature, you can use the ActualVisibleRange instead of ActualRange of the axis.
Dim minimum = axis.ActualVisibleRange.MinimumRegards,
Martin Ivanov
Progress Telerik
Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.
Thanks for your answer.
Both ActualRange and ActualVisibleRange produced the same result : no update.
But finally, i arrived to do what i want using DateTimeContinuousAxis
Here is my code, hope this help someone
E
<UserControl x:Class="Example7" xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" xmlns:example="clr-namespace:POCChart" mc:Ignorable="d" d:DesignHeight="650" d:DesignWidth="800"> <UserControl.Resources> <LinearGradientBrush StartPoint="0 0" EndPoint="0 1" x:Key="handlerBrush"> <LinearGradientBrush.GradientStops> <GradientStop Offset="0.0" Color="Transparent" /> <GradientStop Offset="0.4" Color="Transparent" /> <GradientStop Offset="0.4" Color="Black" /> <GradientStop Offset="0.6" Color="Black" /> <GradientStop Offset="0.6" Color="Transparent" /> <GradientStop Offset="1.0" Color="Transparent" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> <DataTemplate x:Key="LabelTemplate" DataType="{x:Type telerik:CategoricalAxis}"> <StackPanel Orientation="Horizontal"> <Rectangle Fill="Red" Width="5" Height="5" /> <TextBlock Text="{Binding}" Margin="5 0 0 0" /> </StackPanel> </DataTemplate> </UserControl.Resources> <DockPanel> <TextBlock DockPanel.Dock="Top" x:Name="tbMarge" Text="{Binding MargeStr}" /> <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Center"> </StackPanel> <UniformGrid Rows="1"> <telerik:RadCartesianChart x:Name="chart2" > <telerik:RadCartesianChart.Behaviors> <telerik:ChartPanAndZoomBehavior /> <telerik:ChartSelectionBehavior DataPointSelectionMode="Single" HitTestMargin="2"/> <telerik:ChartTrackBallBehavior ShowIntersectionPoints="True" example:ChartViewUtilities.TrackBallGroup="g1" /> <telerik:ChartTooltipBehavior Placement="Mouse" VerticalOffset="5" HorizontalOffset="5" InitialShowDelay="100"/> </telerik:RadCartesianChart.Behaviors> <telerik:RadCartesianChart.VerticalAxis> <telerik:LinearAxis x:Name="verticalAxisBas"/> </telerik:RadCartesianChart.VerticalAxis> <telerik:RadCartesianChart.HorizontalAxis> <telerik:DateTimeContinuousAxis LabelFormat="HH:mm" LabelInterval="1" LabelOffset="0" SmartLabelsMode="None" MajorStepUnit="Hour" MajorStep="6" PlotMode="OnTicks" > </telerik:DateTimeContinuousAxis> </telerik:RadCartesianChart.HorizontalAxis> <telerik:RadCartesianChart.Series> <telerik:LineSeries> <telerik:LineSeries.StrokeShapeStyle> <Style TargetType="Path"> <Setter Property="StrokeThickness" Value="2"/> <Setter Property="Stroke" Value="DarkSlateGray"/> </Style> </telerik:LineSeries.StrokeShapeStyle> </telerik:LineSeries> <telerik:LineSeries> <telerik:LineSeries.HorizontalAxis> <telerik:DateTimeContinuousAxis LabelFormat="dd/MM/yyyy" LabelInterval="1" LabelOffset="0" SmartLabelsMode="None" MajorStepUnit="Day" MajorStep="1" PlotMode="BetweenTicks" /> </telerik:LineSeries.HorizontalAxis> <telerik:LineSeries.StrokeShapeStyle> <Style TargetType="Path"> <Setter Property="StrokeDashArray" Value="10 4"/> <Setter Property="StrokeThickness" Value="2"/> <Setter Property="Stroke" Value="White"/> </Style> </telerik:LineSeries.StrokeShapeStyle> </telerik:LineSeries> </telerik:RadCartesianChart.Series> </telerik:RadCartesianChart> </UniformGrid> </DockPanel></UserControl>Example.vb
Imports System.ComponentModelImports System.DrawingImports Telerik.ChartingImports Telerik.Windows.ControlsImports Telerik.Windows.Controls.ChartViewImports Cnr.SharedPartial Public Class Example7 Inherits UserControl Implements INotifyPropertyChanged Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged Private _marge as Thickness Public Property Marge() As Thickness Get Return _marge End Get Set(value As Thickness) _marge = value MargeStr = _marge.Left.ToString() RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Marge")) RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("MargeStr")) End Set End Property Public Property MargeStr() As String Dim closestDataPoint As CategoricalDataPoint Private start As CategoricalDataPoint Private [to] As CategoricalDataPoint Private dragStartPoint As DataTuple Private leftHandle As CartesianGridLineAnnotation Private rightHandle As CartesianGridLineAnnotation Private band As CartesianPlotBandAnnotation Private band2 As CartesianPlotBandAnnotation Public Sub New() InitializeComponent() Me.DataContext = Me dim dtS = GenerateDataSource(97, 1000) Dim dtS2 = GenerateDataSource(95, 1000) FillSeries(DirectCast(chart2.Series(0), LineSeries), dtS) FillSeries2(DirectCast(chart2.Series(1), LineSeries), dtS2) End Sub Private Sub FillSeries(series As LineSeries, dataSource As List(Of DummyModel)) Dim categoryBinding = New PropertyNameDataPointBinding With { .PropertyName = "DateModel" } Dim valueBinding = New PropertyNameDataPointBinding With { .PropertyName = "Value" } series.CategoryBinding = categoryBinding series.ValueBinding = valueBinding series.ItemsSource = dataSource End Sub Private Sub FillSeries2(series As LineSeries, dataSource As List(Of DummyModel)) Dim categoryBinding = New GenericDataPointBinding(Of DummyModel, DateTime) With { .ValueSelector = Function(d As DummyModel) d.DateModel.Date } Dim valueBinding = New PropertyNameDataPointBinding With { .PropertyName = "Value" } series.CategoryBinding = categoryBinding series.ValueBinding = valueBinding series.ItemsSource = dataSource End Sub Private Function GenerateDataSource(valuesCount As Integer, maxValue As Integer) As List(Of DummyModel) Randomize() 'Création d'une date fixe pour le début de génération des points (on enlève 1h pour bien débuter à 0h lors de la création de la 1ere valeur) Dim firstDate As Date = Date.Now.Date.AddDays(-1) Dim lastVal = New List(Of Integer) For i = 1 To (valuesCount) lastVal.Add(Int(Rnd() * maxValue)) Next Dim dataSource = New List(Of DummyModel) For Each rdm In lastVal Dim obj = New DummyModel With { .DateModel = firstDate, .Value = rdm } dataSource.Add(obj) firstDate = firstDate.AddHours(1) Next Return dataSource End Function Private Sub ChartTrackBallBehavior_TrackInfoUpdated(ByVal sender As Object, ByVal e As Telerik.Windows.Controls.ChartView.TrackBallInfoEventArgs) If e.Context.ClosestDataPoint IsNot Nothing AndAlso e.Context.ClosestDataPoint.Series.Name = "Serie1" Then closestDataPoint = CType(e.Context.ClosestDataPoint.DataPoint, CategoricalDataPoint) End If End Sub End ClassHello Yoan,
Thank you for sharing your solution.
Regards,
Martin Ivanov
Progress Telerik
Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.
