Stacked Bar Chart and Legend

TeeChart VCL for Borland/CodeGear/Embarcadero RAD Studio, Delphi and C++ Builder.
Post Reply
Errol
Newbie
Newbie
Posts: 75
Joined: Thu Jul 11, 2013 12:00 am

Stacked Bar Chart and Legend

Post by Errol » Thu Feb 20, 2014 1:07 am

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.
Lithology_Chart.png
Lithology_Chart.png (16.93 KiB) Viewed 14983 times

Narcís
Site Admin
Site Admin
Posts: 14730
Joined: Mon Jun 09, 2003 4:00 am
Location: Banyoles, Catalonia
Contact:

Re: Stacked Bar Chart and Legend

Post by Narcís » Thu Feb 20, 2014 9:26 am

Hi Errol,

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
Image Image Image Image Image Image
Instructions - How to post in this forum

Errol
Newbie
Newbie
Posts: 75
Joined: Thu Jul 11, 2013 12:00 am

Re: Stacked Bar Chart and Legend

Post by Errol » Wed Feb 26, 2014 4:28 am

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;


Yeray
Site Admin
Site Admin
Posts: 9602
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Stacked Bar Chart and Legend

Post by Yeray » Wed Feb 26, 2014 4:38 pm

Hi Errol,
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?
The generation of bitmaps doesn't look as a TeeChart related issue.
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 473 times
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Errol
Newbie
Newbie
Posts: 75
Joined: Thu Jul 11, 2013 12:00 am

Re: Stacked Bar Chart and Legend

Post by Errol » Wed Feb 26, 2014 11:29 pm

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
Image_In_Grid.png
Image_In_Grid.png (5.94 KiB) Viewed 14881 times
Legend_with_border.png
Legend_with_border.png (4.48 KiB) Viewed 14895 times

Errol
Newbie
Newbie
Posts: 75
Joined: Thu Jul 11, 2013 12:00 am

Re: Stacked Bar Chart and Legend

Post by Errol » Thu Feb 27, 2014 11:33 pm

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.

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;

Yeray
Site Admin
Site Admin
Posts: 9602
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Stacked Bar Chart and Legend

Post by Yeray » Fri Feb 28, 2014 12:16 pm

Hi Errol,

I'm glad to hear you found how to make it work as you wish.
And thanks for sharing the solution!
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Errol
Newbie
Newbie
Posts: 75
Joined: Thu Jul 11, 2013 12:00 am

Re: Stacked Bar Chart and Legend

Post by Errol » Tue Mar 04, 2014 4:46 am

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:
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 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.

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;


Yeray
Site Admin
Site Admin
Posts: 9602
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Stacked Bar Chart and Legend

Post by Yeray » Tue Mar 04, 2014 12:08 pm

Hi Errol,

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;
That gives this result:
2014-03-04_1255.png
2014-03-04_1255.png (20.45 KiB) Viewed 14800 times
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:

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;
- Use the OnGetLegendText event as follows:

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,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Errol
Newbie
Newbie
Posts: 75
Joined: Thu Jul 11, 2013 12:00 am

Re: Stacked Bar Chart and Legend

Post by Errol » Tue Mar 04, 2014 9:44 pm

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

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;


Yeray
Site Admin
Site Admin
Posts: 9602
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Stacked Bar Chart and Legend

Post by Yeray » Wed Mar 05, 2014 3:02 pm

Hi Errol,
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.
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:

Code: Select all

  with Chart1.AddSeries(TBarSeries) as TBarSeries do
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.
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:

Code: Select all

    for i:=0 to 4 do
      Add(25+random*75, 'label ' + IntToStr(i));
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.
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: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.
This may be particular to the way you exactly populate the series and the format of the strings in your XLabelsSource.
Anyway, I'm glad to hear you found a way to make it work as you want.
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.
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.
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Yeray
Site Admin
Site Admin
Posts: 9602
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Stacked Bar Chart and Legend

Post by Yeray » Wed Mar 05, 2014 3:17 pm

Hi Errol,
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.
Series1_and_Legend2.png
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.
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Post Reply