Results 1 to 5 of 5

Thread: Chart: One major legend for multiple chart areas

  1. #1

    Thread Starter
    Lively Member
    Join Date
    Mar 2022
    Posts
    83

    Chart: One major legend for multiple chart areas

    Is there a way to trick MS Chart into creating an e.g. legend at the bottom of a chart with multiple chart areas? Firstly, there are only 3 series in each chart area, and the series colors are all the same for all chart areas. Each chart area merely shows an X-Y scatter plot. I just tried to create a legend and used the the last chart area drawn and got a marker/text for all the series over all areas. Maybe I pull the markers from only the first chart area as a trick. I looked into creating a title that's docked at the bottom of the main chart, but I need colored squares or circles to denote each series, and it's almost impossible to change the color of a unicode square or circle.

    Another thing that comes to mind is to:

    1. Loop through the series on the first chart, pull the colors, create a colored circle, then save to Resources
    2. Open the saved .jpg (or png) and then simply draw a custom string to the image using Graphics and DrawText.
    3. Delete the colored circles Resources.

    Basically, I need a trick to pull one legend from a single chart area and show it on the bottom of the multi-panel chart. FYI - I don't create legends for all the chart areas.

  2. #2
    Fanatic Member Delaney's Avatar
    Join Date
    Nov 2019
    Location
    Paris, France
    Posts
    845

    Re: Chart: One major legend for multiple chart areas

    I had this kind of problem one time. I solved it by creating my own legend box outside the chart. In my case I had to associate color to value so I used 2 labels and a groupbox and I set the groupbox where I wanted.

    here is what I used. if it can be of any help

    Code:
    'Sub to create the legend    
    Private Sub createscale(ByVal gb As GroupBox, ByVal N As Integer, ByVal value As Double)
            For i = 1 To N
                Dim lbl1, lbl2 As New Label
                With lbl1
                    .BackColor = colors.Item(i - 1)
                    .Width = 20
                    .Height = 15
                    .Location = New Point(10, 10 + 25 * i)
                End With
                With lbl2
                    .Width = 100
                    .TextAlign = ContentAlignment.MiddleRight
                    .Height = 15
                    .ForeColor = Color.Black
                    .Text = CStr(Math.Round(value / N * i, 1)) & " MPa"
                    .Location = New Point(20, 10 + 25 * i)
                End With
                gb.Controls.Add(lbl1)
                gb.Controls.Add(lbl2)
            Next
        End Sub
    The best friend of any programmer is a search engine
    "Don't wish it was easier, wish you were better. Don't wish for less problems, wish for more skills. Don't wish for less challenges, wish for more wisdom" (J. Rohn)
    “They did not know it was impossible so they did it” (Mark Twain)

  3. #3

    Thread Starter
    Lively Member
    Join Date
    Mar 2022
    Posts
    83

    Re: Chart: One major legend for multiple chart areas

    How did you get the groupbox on the chart, and what about X,Y position? Some sort of annotation?

  4. #4
    Fanatic Member Delaney's Avatar
    Join Date
    Nov 2019
    Location
    Paris, France
    Posts
    845

    Re: Chart: One major legend for multiple chart areas

    my groupbox is next to the Chart not on the Chart, the X,Y position is for the localization of each labels.

    Name:  sample.jpg
Views: 146
Size:  38.4 KB
    The best friend of any programmer is a search engine
    "Don't wish it was easier, wish you were better. Don't wish for less problems, wish for more skills. Don't wish for less challenges, wish for more wisdom" (J. Rohn)
    “They did not know it was impossible so they did it” (Mark Twain)

  5. #5

    Thread Starter
    Lively Member
    Join Date
    Mar 2022
    Posts
    83

    Re: Chart: One major legend for multiple chart areas

    Thanks - look great, and very nice trick. However, I found the code below which will write a single legend with colored lines and series number at the bottom of chart with multiple chart areas. Line colors are ectracted form the color assigned to each series.

    First, place this immediately after the chart drawing code:

    Code:
    AddHandler Chart1.PostPaint, AddressOf Chart1_BottomPostPaint
    Then place this subroutine somewhere in the same class:

    Code:
     Private Sub Chart1_BottomPostPaint(ByVal sender As Object, ByVal e As ChartPaintEventArgs)
    
            If TypeOf e.ChartElement Is Legend Then
                Dim c As Chart = CType(sender, Chart)
                Dim g As Graphics = e.ChartGraphics.Graphics
    
                'The legend
                Dim l As Legend = c.Legends(0)
                'Absolute dimensions of the legend (New legend will be based on this.. won't be exact.)
                Dim pos As RectangleF = e.ChartGraphics.GetAbsoluteRectangle(l.Position.ToRectangleF)
                Dim numrows As Single = Math.Max(Math.Floor(pos.Height / l.Font.Height), 1)
                'Absolute dimensions of one legend "cell"
                Dim itemHeight As Single = pos.Height / numrows
                Dim numcols As Integer
                If c.Series.Count = 1 Then numcols = 1
                If c.Series.Count = 2 Then numcols = 2
                If c.Series.Count = 4 Then numcols = 4
                If c.Series.Count = 3 OrElse c.Series.Count > 4 Then numcols = 3
                Dim itemwidth As Single
                If c.Series.Count = 1 Then itemwidth = pos.Width
                If c.Series.Count = 2 Then itemwidth = pos.Width / 2
                If c.Series.Count = 4 Then itemwidth = pos.Width / 4
                If c.Series.Count = 3 OrElse c.Series.Count > 4 Then itemwidth = pos.Width / 3
                'itemWidth *= 0.9
                'Padding between line and text (horizontal) and each item (vertical)
                Dim horizontalPadding As Single = 10
                Dim verticalPadding As Single = 1
                Dim reducfactor As Single = 0.75
                Dim legendFont As Font = l.Font
                'Dim legendFont As New Font("Arial", 10)
    
                'Draw a white box on top of the default legend to hide it
                g.FillRectangle(Brushes.White, pos)
                For i As Integer = 0 To c.Series.Count - 1
    
                    Dim s As Series = c.Series(i)
                    Dim p As New Pen(s.Color, CSng(Math.Min(s.BorderWidth, itemHeight))) 'Line no thicker than the item height.
                    Dim ds As ChartDashStyle = s.BorderDashStyle
                    If ds = ChartDashStyle.Solid Then
                        p.DashStyle = DashStyle.Solid
                        reducfactor = 0.7
                    End If
                    If ds = ChartDashStyle.Dash Then
                        p.DashStyle = DashStyle.Dash
                        reducfactor = 0.7
                    End If
                    If ds = ChartDashStyle.DashDot Then
                        p.DashStyle = DashStyle.DashDot
                        reducfactor = 0.76
                    End If
                    If ds = ChartDashStyle.DashDotDot Then
                        p.DashStyle = DashStyle.DashDotDot
                        reducfactor = 0.7
                    End If
                    Dim row As Integer
                    If c.Series.Count = 4 Then row = Math.Ceiling((i + 1) / 4)
                    If c.Series.Count <> 4 Then row = Math.Ceiling((i + 1) / 3)
                    Dim col As Integer
                    If c.Series.Count = 1 Then col = 0
                    If c.Series.Count = 2 Then col = i Mod 2
                    If c.Series.Count = 4 Then col = i Mod 4
                    If c.Series.Count = 3 OrElse c.Series.Count > 4 Then col = i Mod 3
                    'Line
                    Dim posx As Single = CSng((pos.X + (horizontalPadding + itemwidth * (col - 1)) + itemwidth / 2))
                    Dim posY As Single = CSng((pos.Y + (verticalPadding + itemHeight * (row - 1)) + itemHeight / 2))
                    Dim startPoint As PointF = New PointF(posx + itemwidth * reducfactor, posY)
                    Dim endPoint As PointF = New PointF(CSng(posx + itemwidth), posY)
                    g.DrawLine(p, startPoint, endPoint)
    
                    'Text
                    posx = posx + itemwidth
                    startPoint = New PointF(posx, posY - l.Font.Height / 2)
                    g.DrawString(s.Name, legendFont, Brushes.Black, startPoint.X + horizontalPadding, startPoint.Y)
                Next
            End If
    
     End Sub
    The code above will cover up the default legend and draw a custom legend. Note that all the series (in all chart areas) will be drawn, so if you want, for example only markers and text for a unique set of series (assuming the marker color and text is the same on every chart area) from one chart, you could create a legend for only the last chart area (in the code loop), ascribe a legend name like "LastLegend"


    Code:
    If k = numchartareas Then
       Dim Legend1 As New Legend
       Legend1.IsDockedInsideChartArea = True
       Legend1.Name = "LastLegend"
       Legend1.Position.Auto = True
       chart1.Legends.Add(Legend1)
       chart1.Legends("LastLegend").DockedToChartArea = ca.Name
       chart1.Legends("LastLegend").Docking = Docking.Bottom
       chart1.Legends("LastLegend").Alignment = StringAlignment.Center
       chart1.Legends("LastLegend").InsideChartArea = ""
       If HighresCharts = 0 Then chart1.Legends("LastLegend").Font = New Font(GraphLegendFont, GraphLegendFontSize, FontStyle.Bold)
       If HighresCharts = 1 Then chart1.Legends("LastLegend").Font = New Font(GraphLegendFont, GraphLegendFontSize * 4, FontStyle.Bold)
    End If
    and then in the painting subroutine, exit early if the chart area is not the last one. (fyi-the paint method is automatically called after each chart area is drawn, so you need a way to only draw the custom legend after the last chart area is drawn - which is done by the code snippet above.

    If you need a custom legend that only has one colored marker and text string for each unique series, the following code worked fine for drawing 3 sets of marker/text combinations in the custom legend when each chart area contained an X-Y scatter plot with the same markers and colors. Hence there were many total series in the chart, but I only needed colors and text for the 3 unique (repeated) series. I used a public integer value of 3 for the variable NumGroups, which only drew the 3 marker/text combinations. The legend text for each group was contained in the string array GroupNames(). There are some larger pixel drawing values like 200, which were required because the chart was large and had many chart areas:

    Code:
    Private Sub Chart1_BottomPostPaint(ByVal sender As Object, ByVal e As ChartPaintEventArgs)
    
            If TypeOf e.ChartElement Is Legend Then
                Dim c As Chart = CType(sender, Chart)
                If c.Legends(0).Name <> "LastLegend" Then Exit Sub
                Dim g As Graphics = e.ChartGraphics.Graphics
                'The legend
                Dim l As Legend = c.Legends(0)
    
                'Absolute dimensions of the legend (New legend will be based on this.. won't be exact.)
                Dim pos As RectangleF = e.ChartGraphics.GetAbsoluteRectangle(l.Position.ToRectangleF)
                Dim numrows As Single = Math.Max(Math.Floor(pos.Height / 200), 1)
                'Absolute dimensions of one legend "cell"
                Dim itemHeight As Single = pos.Height / numrows
                Dim numcols As Integer
                Dim numseries As Integer = NumGroups
                If numseries = 1 Then numcols = 1
                If numseries = 2 Then numcols = 2
                If numseries = 4 Then numcols = 4
                If numseries = 3 OrElse numseries > 4 Then numcols = 3
                Dim itemwidth As Single
                If numseries = 1 Then itemwidth = pos.Width
                If numseries = 2 Then itemwidth = pos.Width / 2
                If numseries = 4 Then itemwidth = pos.Width / 4
                If numseries = 3 OrElse numseries > 4 Then itemwidth = pos.Width / 3
                'itemWidth *= 0.9
                'Padding between line and text (horizontal) and each item (vertical)
                Dim horizontalPadding As Single = 10
                Dim verticalPadding As Single = 1
                Dim reducfactor As Single = 1 '0.75
                'Dim legendFont As Font = l.Font
                Dim legendFont As New Font("Arial", 200)
    
                'Draw a white box on top of the default legend to hide it
                g.FillRectangle(Brushes.White, pos)
                For i As Integer = 0 To numseries - 1
                    Dim s As Series = c.Series(i)
                    Dim p As New Pen(s.Color, CSng(Math.Min(s.BorderWidth, itemHeight))) 'Line no thicker than the item height.
                    Dim row As Integer
                    If numseries = 4 Then row = Math.Ceiling((i + 1) / 4)
                    If numseries <> 4 Then row = Math.Ceiling((i + 1) / 3)
                    Dim col As Integer
                    If numseries = 1 Then col = 0
                    If numseries = 2 Then col = i Mod 2
                    If numseries = 4 Then col = i Mod 4
                    If numseries = 3 OrElse numseries > 4 Then col = i Mod 3
                    'Line
                    Dim posx As Single = CSng((pos.X + (horizontalPadding + itemwidth * (col - 1)) + itemwidth / 2))
                    Dim posY As Single = CSng((pos.Y + (verticalPadding + itemHeight * (row - 1)) + itemHeight / 2))
                    Dim startPoint As PointF = New PointF(posx + itemwidth * reducfactor, posY)
                    Dim endPoint As PointF = New PointF(CSng(posx + itemwidth), posY)
    
                    Dim mybrush As Brush = New SolidBrush(s.Color)
                    g.FillRectangle(mybrush, startPoint.X, startPoint.Y + 100, 200, 200)
    
                    'Text
                    posx = posx + itemwidth
                    startPoint = New PointF(posx, posY - l.Font.Height / 2)
                    g.DrawString(GroupNames(i + 1), legendFont, Brushes.Black, startPoint.X + 200, startPoint.Y + 100)
                Next
            End If
    
        End Sub
    Last edited by pel11; May 25th, 2022 at 09:49 AM.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width