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

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

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

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.