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

Labels on bars?

6 Answers 275 Views
ChartView
This is a migrated thread and some comments may be shown as answers.
Steve
Top achievements
Rank 1
Steve asked on 26 Mar 2016, 12:28 AM

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

Sort by
0
Steve
Top achievements
Rank 1
answered on 26 Mar 2016, 01:28 AM

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


0
Steve
Top achievements
Rank 1
answered on 26 Mar 2016, 04:51 AM
Ironically if the range bar stacked properly this probably wouldn't even be an issue.
0
Steve
Top achievements
Rank 1
answered on 28 Mar 2016, 01:58 AM
Sorry, that chart was not actually a stacked chart. I got everything working, and I am working around the stuck by only positioning the labels when the position offset is (0,0) so that I don't try to set it more than once. Prevents the issue but also means nothing gets repositioned when the chart is resized.
0
Dess | Tech Support Engineer, Principal
Telerik team
answered on 28 Mar 2016, 07:33 AM
Hello Steve,

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 Sub
Private 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 If
End Sub

Thus, 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.
 
Regards,
Dess
Telerik
Do you need help with upgrading your AJAX, WPF or WinForms project? Check the Telerik API Analyzer and share your thoughts.
0
Steve
Top achievements
Rank 1
answered on 28 Mar 2016, 02:58 PM

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.

0
Dess | Tech Support Engineer, Principal
Telerik team
answered on 29 Mar 2016, 09:34 AM
Hello Steve,

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 MyStrategy
Inherits 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 Sub
End Class
 
Public Class MySmartLabelsController
Inherits 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 Sub
End Class


As 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 CustomBarSeries
Inherits BarSeries
    Protected Overrides Function CreatePointElement(point As DataPoint) As DataPointElement
        Return New CustomBarPointElement(point)
    End Function
End Class
 
Public 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 Function
End Class
 
Public 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 Function
End Class


I hope this information helps. If you have any additional questions, please let me know.

Regards,
Dess
Telerik
Do you need help with upgrading your AJAX, WPF or WinForms project? Check the Telerik API Analyzer and share your thoughts.
Tags
ChartView
Asked by
Steve
Top achievements
Rank 1
Answers by
Steve
Top achievements
Rank 1
Dess | Tech Support Engineer, Principal
Telerik team
Share this question
or