Labels on bars?

7 posts, 0 answers
  1. Steve
    Steve avatar
    43 posts
    Member since:
    May 2014

    Posted 25 Mar Link to this post

    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.

  2. Steve
    Steve avatar
    43 posts
    Member since:
    May 2014

    Posted 25 Mar in reply to Steve Link to this post

    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


  3. UI for WinForms is Visual Studio 2017 Ready
  4. Steve
    Steve avatar
    43 posts
    Member since:
    May 2014

    Posted 25 Mar in reply to Steve Link to this post

    Ironically if the range bar stacked properly this probably wouldn't even be an issue.
  5. Steve
    Steve avatar
    43 posts
    Member since:
    May 2014

    Posted 27 Mar Link to this post

    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.
  6. Dess
    Admin
    Dess avatar
    1609 posts

    Posted 28 Mar Link to this post

    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.
  7. Steve
    Steve avatar
    43 posts
    Member since:
    May 2014

    Posted 28 Mar in reply to Dess Link to this post

    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.

  8. Dess
    Admin
    Dess avatar
    1609 posts

    Posted 29 Mar Link to this post

    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.
Back to Top
UI for WinForms is Visual Studio 2017 Ready