Stacked Bar Chart and Legend
Stacked Bar Chart and Legend
I wish to use a stacked bar chart to plot a well lithology column from a query from a database. The fields in the query are:
Depth Increment
Rock Type
Pattern // a number relating to one of the standard Windows patterns
Foreground // a TColor value as integer
Background // a TColor value as integer
I use a single TBarSeries with all the data from one well to plot the bar chart with patterns and colours (see attachment). How can I plot a legend showing the pattern and colours of each Rock Type and the name of the rock type? Details of the code I use are in the Stacked Bar Chart topic.
All suggestions appreciated.
Depth Increment
Rock Type
Pattern // a number relating to one of the standard Windows patterns
Foreground // a TColor value as integer
Background // a TColor value as integer
I use a single TBarSeries with all the data from one well to plot the bar chart with patterns and colours (see attachment). How can I plot a legend showing the pattern and colours of each Rock Type and the name of the rock type? Details of the code I use are in the Stacked Bar Chart topic.
All suggestions appreciated.
-
- Site Admin
- Posts: 14730
- Joined: Mon Jun 09, 2003 4:00 am
- Location: Banyoles, Catalonia
- Contact:
Re: Stacked Bar Chart and Legend
Hi Errol,
I already provided an answer for that in your thread: http://www.teechart.net/support/viewtop ... 826#p64826
I already provided an answer for that in your thread: http://www.teechart.net/support/viewtop ... 826#p64826
Best Regards,
Narcís Calvet / Development & Support Steema Software Avinguda Montilivi 33, 17003 Girona, Catalonia Tel: 34 972 218 797 http://www.steema.com |
Instructions - How to post in this forum |
Re: Stacked Bar Chart and Legend
I am trying to replicate the pattern and colours used to fill the bars of a stacked bar chart in the legend. To do this, I use the OnDraw event to fill each legend symbol with a bitmap generated from the pattern and colour, with code as shown. Brush.Color and Brush.Style get the correct values but when I save the bitmap to file and check its contents it is always a white rectangle. Any ideas?
Code: Select all
procedure TQSCollection.InsertGeologicalSeries(aCodeFld,aFld:string);
...
self.Owner.Chart.Legend.Symbol.OnDraw:=LegendDraw;
...
procedure TQSCollection.LegendDraw(Sender: TObject; Series:TChartSeries;
ValueIndex:Integer; R:TRect);
var
Bmp: TBitmap;
i: Integer;
begin
Bmp := TBitmap.Create;
self.owner.IntervalQuery.First; // kbmMemTable
for i := 0 to ValueIndex - 1 do // to move to the correct record
self.owner.IntervalQuery.Next; // there may be a more elegant way to do this
try
Bmp.Width := R.Right - R.Left;
Bmp.Height := R.Bottom - R.Top;
Bmp.Canvas.Brush.Color :=
TColor(self.owner.IntervalQuery.FieldByName('Foreground').asInteger);
Bmp.Canvas.Brush.Style :=
TBrushStyle(self.owner.IntervalQuery.FieldByName('Pattern').asInteger);
Bmp.Canvas.FillRect(R);
Bmp.SaveToFile('c:\code\image.bmp');
finally
Bmp.Free; // destroy bitmap
end;
end;
Re: Stacked Bar Chart and Legend
Hi Errol,
However, I've tested a variant of your code in a new blanc application, without TeeChart and it seems to create correct bmps for me here:
The generation of bitmaps doesn't look as a TeeChart related issue.Errol wrote:I am trying to replicate the pattern and colours used to fill the bars of a stacked bar chart in the legend. To do this, I use the OnDraw event to fill each legend symbol with a bitmap generated from the pattern and colour, with code as shown. Brush.Color and Brush.Style get the correct values but when I save the bitmap to file and check its contents it is always a white rectangle. Any ideas?
However, I've tested a variant of your code in a new blanc application, without TeeChart and it seems to create correct bmps for me here:
Code: Select all
procedure TForm1.FormCreate(Sender: TObject);
var Bmp: TBitmap;
R: TRect;
i: Integer;
begin
R:=Rect(0,0,100,100);
for i:=0 to 7 do
try
Bmp := TBitmap.Create;
Bmp.Width := R.Right - R.Left;
Bmp.Height := R.Bottom - R.Top;
Bmp.Canvas.Brush.Color := clRed;
Bmp.Canvas.Brush.Style := TBrushStyle(i);
Bmp.Canvas.FillRect(R);
Bmp.SaveToFile('C:\tmp\image' + IntToStr(i) + '.bmp');
finally
Bmp.Free;
end;
end;
- Attachments
-
- images.zip
- (3.3 KiB) Downloaded 475 times
Best Regards,
Yeray Alonso Development & Support Steema Software Av. Montilivi 33, 17003 Girona, Catalonia (SP) | |
Please read our Bug Fixing Policy |
Re: Stacked Bar Chart and Legend
Good morning Yeray
As you have shown, this code works fine in a non-TeeChart context. I have coloured cells on a grid using the same code without any problems (see attachment1). However, it does not appear to work in the self.Owner.Chart.Legend.Symbol.OnDraw:=LegendDraw procedure - I have tried using specified colours and brush styles as you have, with no success, although I can draw borders around the legend symbols (attachment2). It does seem to be a TeeChart issue, and I would greatly appreciate it if you could test it in an OnDraw context.
Regards
Errol
As you have shown, this code works fine in a non-TeeChart context. I have coloured cells on a grid using the same code without any problems (see attachment1). However, it does not appear to work in the self.Owner.Chart.Legend.Symbol.OnDraw:=LegendDraw procedure - I have tried using specified colours and brush styles as you have, with no success, although I can draw borders around the legend symbols (attachment2). It does seem to be a TeeChart issue, and I would greatly appreciate it if you could test it in an OnDraw context.
Regards
Errol
Re: Stacked Bar Chart and Legend
Good morning Yeray
By trial and error, I solved the problem of drawing legend symbols as filled patterns. The trick is to first write the pattern to a rectangle at Left = 0, Top = 0 and then draw this pattern in the legend symbol space. This procedure is a legend event, called as follows:
Self.Owner.Chart.Legend.Symbol.OnDraw:=LegendDraw;
Also set LegendStyles as follows:
self.Owner.Chart.Legend.LegendStyle := lsValues;
I have attached the code if other users are wrestling with this problem.
By trial and error, I solved the problem of drawing legend symbols as filled patterns. The trick is to first write the pattern to a rectangle at Left = 0, Top = 0 and then draw this pattern in the legend symbol space. This procedure is a legend event, called as follows:
Self.Owner.Chart.Legend.Symbol.OnDraw:=LegendDraw;
Also set LegendStyles as follows:
self.Owner.Chart.Legend.LegendStyle := lsValues;
I have attached the code if other users are wrestling with this problem.
Code: Select all
// ------ LegendDraw ---------------------------------------------
procedure LegendDraw(Sender: TObject; Series:TChartSeries;ValueIndex:Integer; aRect:TRect);
var
B: TBitmap;
i: Integer;
R: TRect;
begin
B := TBitmap.Create;
self.owner.IntervalQuery.First; // kbmMemTable
for i := 0 to ValueIndex - 1 do // code to get to the correct record
self.owner.IntervalQuery.Next;
try
R.Left := 0;
R.Top := 0;
R.Right := aRect.Right - aRect.Left;
R.Bottom := aRect.Bottom - aRect.Top;
B.Width := R.Right - R.Left;
B.Height:=R.Bottom - R.Top;
B.Canvas.Brush.Color :=
TColor(self.owner.IntervalQuery.FieldByName('Foreground').asInteger);
B.Canvas.Brush.Style :=
TBrushStyle(self.owner.IntervalQuery.FieldByName('Pattern').asInteger);
SetBkColor(B.Canvas.Handle,
TColor(self.owner.IntervalQuery.FieldByName('Background').asInteger));
B.Canvas.FillRect(R);
with self.owner.chart.Canvas do
begin
StretchDraw(aRect,B); // draw image to legend
// draw border
if self.owner.Chart.Legend.Symbol.Pen.Visible then
begin
Brush.Style:=bsClear;
AssignVisiblePen(self.owner.Chart.Legend.Symbol.Pen);
Rectangle(aRect,0);
end;
end;
finally
B.Free; // destroy bitmap
end;
end;
Re: Stacked Bar Chart and Legend
Hi Errol,
I'm glad to hear you found how to make it work as you wish.
And thanks for sharing the solution!
I'm glad to hear you found how to make it work as you wish.
And thanks for sharing the solution!
Best Regards,
Yeray Alonso Development & Support Steema Software Av. Montilivi 33, 17003 Girona, Catalonia (SP) | |
Please read our Bug Fixing Policy |
Re: Stacked Bar Chart and Legend
I wish to create a stacked bar chart using data from a database table. I have set YValues.ValueSource := 'Depth' (the depth increment) and XLabelsSource := 'Rock Type Code'. The legend label then writes the values of both the Depth and the Rock Type Code, but I want to suppress the Depth value and display only the Rock Type Code values. When I previously raised this question (in the Stacked Bar Chart thread) you suggested:
I also tried the OnGetLegendEvent but this also failed because the LegendText as a combination of Depth and Rock Type Code (e.g. 100-TUF) but it was not possible to find the separator (shown a - but displayed as a space - e.g. 100 TUF) and use this to delete all text before the separator.
I look forward to your comments.
Thanks
Errol
I have tried both of these suggestions. I set up an OnAfterDraw event to try and edit each legend. Chart1.Legend.Items is a list of the Depth values only - it does not include the Rock Type Code' which I guess is added internally, so I simply set every legend item to ''. This partially works, but the number of legend items and the legend text is not updated as the data set is changed, although the legend symbols are changed. I have listed the various chart and legend commands and the event procedures in the code. I do not understand why the Legend.Items count is not updating.You can either set the LegendStyle property or use the OnGetLegendText event to parse and remove it or Chart1.Legend.Items list as in the All Features\Welcome!\Miscellaneous\Legend\Items property
I also tried the OnGetLegendEvent but this also failed because the LegendText as a combination of Depth and Rock Type Code (e.g. 100-TUF) but it was not possible to find the separator (shown a - but displayed as a space - e.g. 100 TUF) and use this to delete all text before the separator.
I look forward to your comments.
Thanks
Errol
Code: Select all
procedure TQSCollection.InsertGeologicalSeries(aCodeFld,aFld:string);
...
self.Owner.Chart.OnAfterDraw:=ChartAfterDraw;
self.Owner.Chart.Legend.Symbol.OnDraw:=LegendDraw;
...
procedure TQSCollection.ChartAfterDraw(Sender: TObject);
var
i: Integer;
begin
for i:=0 to self.Owner.Chart.Legend.Items.Count-1 do
begin
self.owner.chart.Legend.Item[i].Text := '';
end;
procedure TQSCollection.LegendDraw(Sender: TObject; Series:TChartSeries;
ValueIndex:Integer; aRect:TRect);
var
B: TBitmap;
i: Integer;
R: TRect;
begin
B := TBitmap.Create;
self.owner.IntervalQuery.First; // kbmMemTable
for i := 0 to ValueIndex - 1 do
self.owner.IntervalQuery.Next;
try
R.Left := 0;
R.Top := 0;
R.Right := aRect.Right - aRect.Left;
R.Bottom := aRect.Bottom - aRect.Top;
B.Width := R.Right - R.Left;
B.Height := R.Bottom - R.Top;
B.Canvas.Brush.Style := TBrushStyle(self.owner.IntervalQuery.FieldByName('Pattern').asInteger);
B.Canvas.Brush.Color := TColor(self.owner.IntervalQuery.FieldByName('Foreground').asInteger);
SetBkColor(B.Canvas.Handle,TColor(self.owner.IntervalQuery.FieldByName('Background').asInteger));
B.Canvas.FillRect(R);
with self.owner.chart.Canvas do
begin
StretchDraw(aRect,B); // draw image to legend
end;
finally
B.Free; // destroy bitmap
end;
end;
Re: Stacked Bar Chart and Legend
Hi Errol,
I understand your situation can be resumed with this code:
That gives this result:
Could you please confirm this shows the problem you are trying to evade?
If so, as you've noticed, you have two options to only show the label and not the value:
- Edit the Legend.Items array. You can remove the first string (Text string) from all the items in the array. Just note you need to force a chart repaint before doing so:
- Use the OnGetLegendText event as follows:
I understand your situation can be resumed with this code:
Code: Select all
uses Series;
procedure TForm1.FormCreate(Sender: TObject);
var i: Integer;
begin
Chart1.View3D:=false;
with Chart1.AddSeries(TBarSeries) as TBarSeries do
begin
MultiBar:=mbSelfStack;
MarksOnBar:=true;
MarksLocation:=mlCenter;
ColorEachPoint:=true;
for i:=0 to 4 do
Add(25+random*75, 'label ' + IntToStr(i));
end;
end;
If so, as you've noticed, you have two options to only show the label and not the value:
- Edit the Legend.Items array. You can remove the first string (Text string) from all the items in the array. Just note you need to force a chart repaint before doing so:
Code: Select all
procedure TForm1.FormCreate(Sender: TObject);
var i: Integer;
begin
Chart1.View3D:=false;
with Chart1.AddSeries(TBarSeries) as TBarSeries do
begin
MultiBar:=mbSelfStack;
MarksOnBar:=true;
MarksLocation:=mlCenter;
ColorEachPoint:=true;
for i:=0 to 4 do
Add(25+random*75, 'label ' + IntToStr(i));
end;
Chart1.Draw;
for i:=0 to Chart1.Legend.Items.Count-1 do
Chart1.Legend.Item[i].Text:='';
end;
Code: Select all
uses Series;
procedure TForm1.FormCreate(Sender: TObject);
var i: Integer;
begin
Chart1.View3D:=false;
with Chart1.AddSeries(TBarSeries) as TBarSeries do
begin
MultiBar:=mbSelfStack;
MarksOnBar:=true;
MarksLocation:=mlCenter;
ColorEachPoint:=true;
for i:=0 to 4 do
Add(25+random*75, 'label ' + IntToStr(i));
end;
Chart1.OnGetLegendText:=GetLegendText;
end;
procedure TForm1.GetLegendText(Sender:TCustomAxisPanel; LegendStyle:TLegendStyle; Index:Integer; var LegendText:String);
begin
if Index>-1 then
LegendText:=Chart1[0].Labels[Index];
end;
Best Regards,
Yeray Alonso Development & Support Steema Software Av. Montilivi 33, 17003 Girona, Catalonia (SP) | |
Please read our Bug Fixing Policy |
Re: Stacked Bar Chart and Legend
Hi Yeray
Thanks for your suggestions. However my situation is a little different from your example. I use a single series while you appear to use a series for each bar in the chart. I also have a number of fields in my data set, and I believe the field used in the Marks is specified by XLabelsSource, and this is somehow auto-added to the legend text.
Possibly because of these differences, your GetLegendText suggestion did not work - it removed both legend components, the Depth and the Rock Type Code. However, I was successful when I used the attached code - I observed that Delphi flyover hints reported the LegendText as '100'#6'TUF' (although the Watch tool reported it as 100-TUF). Clearly ASCII character 6 is used as a delimiter in the TeeChart legend in this case.
By the way, the Legend.Items suggestion also failed, but I think this is because there are some issues with refreshing the data at this point in my code.
Thanks and regards
Errol
Thanks for your suggestions. However my situation is a little different from your example. I use a single series while you appear to use a series for each bar in the chart. I also have a number of fields in my data set, and I believe the field used in the Marks is specified by XLabelsSource, and this is somehow auto-added to the legend text.
Possibly because of these differences, your GetLegendText suggestion did not work - it removed both legend components, the Depth and the Rock Type Code. However, I was successful when I used the attached code - I observed that Delphi flyover hints reported the LegendText as '100'#6'TUF' (although the Watch tool reported it as 100-TUF). Clearly ASCII character 6 is used as a delimiter in the TeeChart legend in this case.
By the way, the Legend.Items suggestion also failed, but I think this is because there are some issues with refreshing the data at this point in my code.
Thanks and regards
Errol
Code: Select all
procedure TQSCollection.GetLegendText(Sender: TCustomAxisPanel;
LegendStyle: TLegendStyle; Index: Integer; var LegendText: string);
var
iPos: integer;
begin
iPos := Pos(Chr(6),LegendText);
if (iPos = 0) then
LegendText := ''
else
Delete(LegendText,1,iPos);
end;
Re: Stacked Bar Chart and Legend
Hi Errol,
Anyway, I'm glad to hear you found a way to make it work as you want.
No, if you look at the code in my last post, you'll see I'm creating a unique TBarSeries in all the examples. Concretely, the line of code where the series is created is this one, and as you'll see it's out of any loop:Errol wrote:Thanks for your suggestions. However my situation is a little different from your example. I use a single series while you appear to use a series for each bar in the chart.
Code: Select all
with Chart1.AddSeries(TBarSeries) as TBarSeries do
I can't assure it without the complete project here to know what are you exactly doing, but yes, you can populate the labels array through XLabelsSource. However, the series and the legend should behave the same way either populating the labels array through this XLabelsSource property than through the Add() method as I did in the examples above:Errol wrote:I also have a number of fields in my data set, and I believe the field used in the Marks is specified by XLabelsSource, and this is somehow auto-added to the legend text.
Code: Select all
for i:=0 to 4 do
Add(25+random*75, 'label ' + IntToStr(i));
To assure what differences between your project and the examples I posted above are making my suggestions invalid in your project, we should compare your project and mine. I understand your project may be huge, but to know what exact property or combination of settings is making the difference, we'd need you to minimize your project to the minimum expression that still reproduces this or any problem in general.Errol wrote:Possibly because of these differences, your GetLegendText suggestion did not work - it removed both legend components, the Depth and the Rock Type Code.
This may be particular to the way you exactly populate the series and the format of the strings in your XLabelsSource.Errol wrote:However, I was successful when I used the attached code - I observed that Delphi flyover hints reported the LegendText as '100'#6'TUF' (although the Watch tool reported it as 100-TUF). Clearly ASCII character 6 is used as a delimiter in the TeeChart legend in this case.
Anyway, I'm glad to hear you found a way to make it work as you want.
Note after each data update, the chart needs to be repainted so the Legend.Items list can also be internally updated. So you may need to force a chart repaint between updating the data and modifying the Legend.Items list.Errol wrote:By the way, the Legend.Items suggestion also failed, but I think this is because there are some issues with refreshing the data at this point in my code.
Best Regards,
Yeray Alonso Development & Support Steema Software Av. Montilivi 33, 17003 Girona, Catalonia (SP) | |
Please read our Bug Fixing Policy |
Re: Stacked Bar Chart and Legend
Hi Errol,
Again, this looks as a different issue to the issue discussed here. It's related, but since it may grow on complexity, I'll better split it to a new thread for better reading and understanding. I'll reply you there.Errol wrote:Thanks for your help to date. Now I have a really tricky question. I have created two series, one representing the rock types down a well, and the other an list of unique rock types, sorted alphabetically. Using these, I can display two stacked bar charts (see attachment). I would like to show the first stacked bar chart but display the legend appropriate to the other. I can hide one of the charts easily enough by setting the Y-axis value to 0 for all data points. I have the colors correct for the 2nd series (by using a Legend.Symbol.OnDraw event) but cannot change the text (GetLegendText seems to always gets the text for the first series) and I cannot suppress the extra legend symbols. Ideas welcome.
Best Regards,
Yeray Alonso Development & Support Steema Software Av. Montilivi 33, 17003 Girona, Catalonia (SP) | |
Please read our Bug Fixing Policy |