-
May 23rd, 2022, 01:06 PM
#1
Thread Starter
Lively Member
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.
-
May 24th, 2022, 03:16 AM
#2
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)
-
May 24th, 2022, 11:20 AM
#3
Thread Starter
Lively Member
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?
-
May 24th, 2022, 03:27 PM
#4
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.
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)
-
May 25th, 2022, 09:41 AM
#5
Thread Starter
Lively Member
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
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|