This is a migrated thread and some comments may be shown as answers.

Center Labels on DateTimeContinuousAxis

13 Answers 228 Views
ChartView
This is a migrated thread and some comments may be shown as answers.
Yoan
Top achievements
Rank 1
Iron
Yoan asked on 08 Feb 2021, 10:03 AM

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

Sort by
0
Martin Ivanov
Telerik team
answered on 10 Feb 2021, 03:12 PM

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/.

0
Yoan
Top achievements
Rank 1
Iron
answered on 24 Feb 2021, 02:24 PM

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

0
Martin Ivanov
Telerik team
answered on 01 Mar 2021, 12:50 PM

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/.

0
Yoan
Top achievements
Rank 1
Iron
answered on 05 Mar 2021, 09:51 AM

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

0
Yoan
Top achievements
Rank 1
Iron
answered on 05 Mar 2021, 09:52 AM
0
Yoan
Top achievements
Rank 1
Iron
answered on 05 Mar 2021, 09:57 AM

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: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.ComponentModel
Imports Telerik.Charting
Imports Telerik.Windows.Controls.ChartView
 
Class 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 Function
 
End Class

 

 

 

0
Martin Ivanov
Telerik team
answered on 09 Mar 2021, 02:51 PM

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 Sub

I 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/.

0
Yoan
Top achievements
Rank 1
Iron
answered on 23 Mar 2021, 02:40 PM

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

0
Martin Ivanov
Telerik team
answered on 24 Mar 2021, 01:57 PM

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.Minimum

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.

0
Yoan
Top achievements
Rank 1
Iron
answered on 29 Mar 2021, 02:26 PM

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:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             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>
0
Yoan
Top achievements
Rank 1
Iron
answered on 29 Mar 2021, 02:27 PM

Example.vb

Imports System.ComponentModel
Imports System.Drawing
Imports Telerik.Charting
Imports Telerik.Windows.Controls
Imports Telerik.Windows.Controls.ChartView
Imports Cnr.Shared
 
Partial 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 Class
0
Yoan
Top achievements
Rank 1
Iron
answered on 29 Mar 2021, 02:28 PM
Thanks for your Help
0
Martin Ivanov
Telerik team
answered on 29 Mar 2021, 02:42 PM

Hello 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.

Tags
ChartView
Asked by
Yoan
Top achievements
Rank 1
Iron
Answers by
Martin Ivanov
Telerik team
Yoan
Top achievements
Rank 1
Iron
Share this question
or