I am trying to display labels on horizontally stacked bars. I want the labels inside each bar, but I can't figure out to accomplish this.
See attachment "labels-on-bars" for how I want it to look, using our previous charting tool. The other attachments "regular-labels" are just turning on showlabels, which has them positioned where I want vertically but not horizontally, and smart labels, which is just a jumbled mess of silliness.
I tried different "strategies" for the smart labels and I also tried deriving my own, but there was no clear indication of what to do in "CalculateLocations".
I've hooked into the LabelFormatting, and I can get the position of each bar, but the BarLabelElement only seems to have a position offset and no way to set the position in absolute coordinates.
6 Answers, 1 is accepted
Further investigation revealed that the labels are centered on the "full" bar when stacked, rather than just the stacked portion of the bar. So, that's lame.
I was able to calculate the proper position of the stacked portion of the bar and found the label's layout slot and was able to determine the offset to the centers. It actually worked... EXCEPT it totally kills everything. It ends up in some infinite updating loop.
How can I implement this in CalculateLocations?
Private Sub OnLabelFormattingHack(sender As Object, e As Telerik.WinControls.UI.ChartViewLabelFormattingEventArgs)
Dim DataPoint As Telerik.Charting.DataPoint = DirectCast(e.LabelElement.Parent, Telerik.WinControls.UI.DataPointElement).DataPoint
If TypeOf DataPoint Is Telerik.Charting.CategoricalDataPoint Then
Dim LabelElement As Telerik.WinControls.UI.BarLabelElement = DirectCast(e.LabelElement, Telerik.WinControls.UI.BarLabelElement)
Dim SeriesIndex As Integer = DataPoint.Parent.Index
If SeriesIndex > 0 Then
Dim PreviousSeries As Telerik.WinControls.UI.BarSeries = DirectCast(MyBase.Series(SeriesIndex - 1), Telerik.WinControls.UI.BarSeries)
Dim PreviousBar As Telerik.Charting.CategoricalDataPoint = DirectCast(PreviousSeries.DataPoints(DataPoint.Index), Telerik.Charting.CategoricalDataPoint)
Dim PreviousSlot As Telerik.Charting.RadRect = PreviousBar.LayoutSlot
Dim FullBarSlot As Telerik.Charting.RadRect = DataPoint.LayoutSlot
Dim AdjustedBarSlot As Telerik.Charting.RadRect = New Telerik.Charting.RadRect(PreviousSlot.Right, FullBarSlot.Y, FullBarSlot.Right - PreviousSlot.Right, FullBarSlot.Height)
Dim LabelSlot As Telerik.Charting.RadRect = LabelElement.GetLayoutSlot()
LabelElement.PositionOffset = New Drawing.PointF(CSng(AdjustedBarSlot.Center.X - LabelSlot.Center.X), CSng(AdjustedBarSlot.Center.Y - LabelSlot.Center.Y))
End If
End If
End Sub
Thank you for writing.
By default, the BarLabelElements are centered within the bar when the CombineMode is ChartSeriesCombineMode.Stack (please refer to the attached screenshot). If you need to manipulate the default label's position, as you have already found out, it is necessary to adjust the PositionOffset property for the label in the LabelFormatting event considering the DataPoint.LayoutSlot. Here is demonstrated a sample approach:
Sub New() InitializeComponent() AddHandler Me.RadChartView1.LabelFormatting, AddressOf OnLabelFormatting Dim barSeries As New Telerik.WinControls.UI.BarSeries("Performance", "RepresentativeName") barSeries.Name = "Q1" barSeries.DataPoints.Add(New CategoricalDataPoint(177, "Harley")) barSeries.DataPoints.Add(New CategoricalDataPoint(128, "White")) barSeries.DataPoints.Add(New CategoricalDataPoint(143, "Smith")) barSeries.DataPoints.Add(New CategoricalDataPoint(111, "Jones")) barSeries.DataPoints.Add(New CategoricalDataPoint(118, "Marshall")) barSeries.CombineMode = ChartSeriesCombineMode.Stack barSeries.ShowLabels = True Me.RadChartView1.Series.Add(barSeries) Dim barSeries2 As New Telerik.WinControls.UI.BarSeries("Performance", "RepresentativeName") barSeries2.Name = "Q2" barSeries2.DataPoints.Add(New CategoricalDataPoint(153, "Harley")) barSeries2.DataPoints.Add(New CategoricalDataPoint(141, "White")) barSeries2.DataPoints.Add(New CategoricalDataPoint(130, "Smith")) barSeries2.DataPoints.Add(New CategoricalDataPoint(88, "Jones")) barSeries2.DataPoints.Add(New CategoricalDataPoint(109, "Marshall")) barSeries2.CombineMode = ChartSeriesCombineMode.Stack barSeries2.ShowLabels = True Me.RadChartView1.Series.Add(barSeries2)End SubPrivate Sub OnLabelFormatting(sender As Object, e As Telerik.WinControls.UI.ChartViewLabelFormattingEventArgs) Dim DataPoint As Telerik.Charting.DataPoint = DirectCast(e.LabelElement.Parent, Telerik.WinControls.UI.DataPointElement).DataPoint If TypeOf DataPoint Is Telerik.Charting.CategoricalDataPoint Then Dim LabelElement As Telerik.WinControls.UI.BarLabelElement = DirectCast(e.LabelElement, Telerik.WinControls.UI.BarLabelElement) Dim SeriesIndex As Integer = DataPoint.Parent.Index If SeriesIndex > 0 Then LabelElement.PositionOffset = New PointF(DataPoint.LayoutSlot.Width / 2 + 10, -1 * DataPoint.LayoutSlot.Height / 2 - 10) End If End IfEnd SubThus, when resizing the chart, the label keeps its position. The attached gif file illustrates the achieved result.
I hope this information helps. Should you have further questions I would be glad to help.
Dess
Telerik
I'm not sure you understand my current problem. Setting the label offset is causing some kind of infinite update loop where it just keeps calling the event over and over again. The only way to break the loop is to check whether the offset has been set,which causes me to not be able to catch the resizingand update the position offset.
My other question, was how can I implement my own positioning strategy because that seems like it should be a more effective means of controlling the positions. I tried implementing my own and just running basically this same code in CalculatePositions, but it would cause the chart to crash before it even got to my code. I also dried deriving from the vertical strategy and it would crash as well, even if I didn't override a single thing.
One final question I hadn't thought of before, is I would also like to control the label size but I did not see any means for setting the size. There was a size property but it was 0,0, and setting it did not appear to restrict the label's boundaries.
Thank you for writing back.
It is normal that the LabelFormatting event is fired a lot of times as it is responsible for the correct label's style. By using the provided code snippet I have noticed that the label elements are constantly moving horizontally as their LayoutSlot is changed when adjusting the PositionOffset property. By calling the GetLayoutSlot method of the BarLabelElement, the LabelFormatting events firing increases as well.
RadChartView provides a built-in mechanism for resolving labels overlapping with the SmartLabelsController. You can add the controller to the Controllers collection of RadChartView and it will optimize the arrangement of the labels in a way that there will be less overlaps. It is possible to create a custom SmartLabelsStrategy and position the labels manually. The following example shows a sample approach how you can position the labels in the top part of the chart:
Dim controler = New MySmartLabelsController()controler.Strategy = New MyStrategy()Me.RadChartView1.Controllers.Add(controler)Public Class MyStrategyInherits SmartLabelsStrategyBase Public Overrides Sub CalculateLocations(series As ChartSeriesCollection, plotArea As Rectangle) Dim labels As New List(Of LabelElement)() Dim overlaps As New List(Of Integer)() Dim x As Integer = 70 Dim y As Integer = 30 Dim spacing As Integer = 6 For Each chartSeries As ChartSeries In series If Not chartSeries.ShowLabels OrElse Not chartSeries.IsVisible Then Continue For End If For Each point As DataPointElement In chartSeries.Children Dim label As LabelElement = DirectCast(point.Children(0), LabelElement) Dim labelRect As Rectangle = ChartRenderer.ToRectangle(label.GetLayoutSlot()) Dim newRect = New Rectangle(x, y, labelRect.Width, labelRect.Height) x += spacing + labelRect.Width If x + spacing + labelRect.Width > plotArea.Width - 100 Then y += spacing + labelRect.Height x = 70 End If label.SmartRectangle = newRect labels.Add(label) Next Next End SubEnd ClassPublic Class MySmartLabelsControllerInherits SmartLabelsController Public Overrides Sub CalculateLabelsPositions(series As ChartSeriesCollection, plotArea As Rectangle) If Me.Strategy IsNot Nothing Then Me.Strategy.CalculateLocations(series, plotArea) End If End SubEnd ClassAs to the labels' size, note that they are auto sized to fit their content considering the font. If you need to enlarge the label, it is necessary to increase its LayoutSlot which requires creating a custom BarLabelElement:
Public Class CustomBarSeriesInherits BarSeries Protected Overrides Function CreatePointElement(point As DataPoint) As DataPointElement Return New CustomBarPointElement(point) End FunctionEnd ClassPublic Class CustomBarPointElement Inherits BarPointElement Public Overrides ReadOnly Property ThemeRole As String Get Return "Bar" End Get End Property Public Sub New(dataPoint As DataPoint) MyBase.New(dataPoint) End Sub Protected Overrides Function CreateLabelElement(point As DataPointElement) As LabelElement Return New CustomBarLabelElement(point) End FunctionEnd ClassPublic Class CustomBarLabelElement Inherits BarLabelElement Public Sub New(dataPointElement As DataPointElement) MyBase.New(dataPointElement) End Sub Public Overrides Function GetLayoutSlot() As RadRect Dim rect As RadRect = MyBase.GetLayoutSlot() Return New RadRect(rect.X, rect.Y, rect.Width + 30, rect.Height + 20) End FunctionEnd ClassI hope this information helps. If you have any additional questions, please let me know.
Regards,
Dess
Telerik
