Page 1 of 1

Legend positioning / sizing

Posted: Fri Nov 25, 2011 2:32 am
by 10550286
Hi

I'm trying to position a legend on the right. I've tried setting the alignment property so that I can specify via just setting the rightmost edge, but that doesn't seem to work. Is this a bug?

So instead I've been trying to do something like:
Chart1->Legend->Left = Chart1->Width - Chart1->Legend->Width - 2;

Unfortunately, the reported Chart1->Legend->Width is frequently erroneous, so the legend can shift all over the place, despite not necessarily changing size. To reproduce the erroneous width reporting, it seems related to toggling either series visibility, or the ShowInLegend property of a series (which can change the legend width, but it is still reported incorrectly).

See attached picture. I produced this by :

Code: Select all

__fastcall TFormMain::TFormMain(TComponent* Owner)
  : TForm(Owner)
{
  Chart1->AddSeries(new TLineSeries(Chart1));
  Chart1->Series[0]->AddXY(1,1);
  Chart1->Series[0]->AddXY(2,2);
  Chart1->AddSeries(new TLineSeries(Chart1));
  Chart1->Series[1]->AddXY(3,3);
  Chart1->Series[1]->AddXY(4,4);
  Chart1->Legend->Visible = true;

  Label1->Caption = "";
  Toggler = false;
}
//---------------------------------------------------------------------------
void __fastcall TFormMain::TScreenUpdateTimer(TObject *Sender)
{
  Toggler = !Toggler;

  Chart1->Series[0]->Visible = Toggler;

  //Use legend width to position the legend with respect to the right boundary
  Chart1->Legend->Left = Chart1->Width - Chart1->Legend->Width - 2;

  AnsiString Message = Label1->Caption;
  Message += AnsiString(_T("Reported Legend Width = ")) + AnsiString(Chart1->Legend->Width) + _T("\n");
  Label1->Caption = Message;
}
So, I'm struggling just to position the legend. I'd be willing to use just a work around at this point. Is there an event perhaps where, by then, the width will be correctly reported?

Re: Legend positioning / sizing

Posted: Fri Nov 25, 2011 3:31 pm
by yeray
Hi Sam,

Take care when you change some properties in the chart and you retrieve others "immediately" afterwards, because some properties of the chart aren't updated until the charts is repainted. So in this cases I'd suggest you to try forcing some chart repaints to see if this affects the behaviour.
And I think that's the case here. Calling Chart1->Draw() before using Chart1->Legend->Left it seems to work fine for me here.

Re: Legend positioning / sizing

Posted: Mon Nov 28, 2011 5:45 am
by 10550286
Keep in mind that whatever manner it is calculating the legend width, it seems to breakable - it isn't actually '-12' at any point in time - in the example I used, it is flicking between displaying the information for one or two series.
Perhaps it needs to resort to some cache value if certain other values are invalid for it to use in the width calculation, or if it is halfway through an update.
But yes, I'll try your solution.

Re: Legend positioning / sizing

Posted: Tue Nov 29, 2011 6:01 am
by 10550286
So, I find if I set the left property, it effects the width property. Which is unfortunate, because I use the width property to set the left property - leading to the obvious potential of a feedback cycle. Calling the chart repaint or the legend repaint does not guarantee a valid width after setting the left. The following code, for example, will report a legend width of 0 in my little test project every time the timer goes off.

Code: Select all

Chart1->Legend->Left = Chart1->Legend->Left + 76;
  Chart1->Repaint();
  Chart1->Legend->Repaint();
  AnsiString Message = Label1->Caption;
  Message += AnsiString(_T("Reported Legend Width = ")) + AnsiString(Chart1->Legend->Width) + _T("\n");
  Label1->Caption = Message;
The problem I have all this with is a real-time updating chart (so is repainting every second) - and hence I'm trying to set the valid legend left every second too, but it appears I don't have a means of determining what the valid left pixel is for a legend that I can't determine the width of.

Re: Legend positioning / sizing

Posted: Thu Dec 01, 2011 12:07 pm
by yeray
Hello Sam,

Note the Width property returns the difference between Right and Left properties (+1 pixel). Since you modify just one of them, you need to set the other property too or to repaint the chart for the other property to be recalculated.
- To modify the Legend Right property:

Code: Select all

Chart1.Legend.ShapeBounds.Right:=Chart1.Width - 2 - 1;
- To force the chart to be repainted, you have to repaint it after changing the legend items (activating/deactivating a series, 1st repaint) and after modifying the left property (2nd repaint):

Code: Select all

uses Series;

var Toggler: Bool;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Chart1.AddSeries(TLineSeries);
  Chart1[0].AddXY(1,1);
  Chart1[0].AddXY(2,2);
  Chart1.AddSeries(TLineSeries);
  Chart1[1].AddXY(3,3);
  Chart1[1].AddXY(4,4);
  Chart1.Visible:=true;

  Label1.Caption:='';
  Toggler:=false;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Toggler:=not Toggler;

  Chart1[0].Visible:=Toggler;
  Chart1.Repaint; //1st Repaint
  Label1.Caption:=Label1.Caption+'Reported Legend Width Before = ' + IntToStr(Chart1.Legend.Width) + #10#13;
  Chart1.Legend.Left:=Chart1.Width - Chart1.Legend.Width - 2;
  Chart1.Repaint; //2nd Repaint
  Label1.Caption:=Label1.Caption+'Reported Legend Width After = ' + IntToStr(Chart1.Legend.Width) + #10#13;

{  Chart1.Legend.Left:=Chart1.Legend.Left + 76;
  Chart1.Repaint;
  Chart1.Legend.Repaint;
  Label1.Caption:=Label1.Caption+'Reported Legend Width = ' + IntToStr(Chart1.Legend.Width) + #10#13;}
end;
Sam F wrote:The following code, for example, will report a legend width of 0 in my little test project every time the timer goes off.
I'm trying to reproduce it but I can't. Here is the complete code I'm using in Delphi but I see no "0" in the label:

Code: Select all

uses Series;

var Toggler: Bool;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Chart1.AddSeries(TLineSeries);
  Chart1[0].AddXY(1,1);
  Chart1[0].AddXY(2,2);
  Chart1.AddSeries(TLineSeries);
  Chart1[1].AddXY(3,3);
  Chart1[1].AddXY(4,4);
  Chart1.Visible:=true;

  Label1.Caption:='';
  Toggler:=false;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Toggler:=not Toggler;

  Chart1[0].Visible:=Toggler;

  Chart1.Legend.Left:=Chart1.Legend.Left + 76;
  Chart1.Repaint;
  Chart1.Legend.Repaint;
  Label1.Caption:=Label1.Caption+'Reported Legend Width = ' + IntToStr(Chart1.Legend.Width) + #10#13;
end;
PS: Excuse me for using Delphi instead of C++Builder. I don't think you'll find any problem understanding/translating it but if you do, don't hesitate to let me know.

Re: Legend positioning / sizing

Posted: Fri Dec 02, 2011 1:08 am
by 10550286
Thankyou Yeray.

No problems reading delphi. Setting the ShapeBounds.Right property seems to be resolve my problems. I'll use the following code now whenever I want to set a legend left:

Code: Select all

  int LegWidth = Chart1->Legend->Width;
  Chart1->Legend->Left = NewLeft;
  Chart1->Legend->ShapeBounds.Right = Chart1->Legend->Left + LegWidth;
Because I'm using the width for setting the left, it is very important for me to maintain the validity of the width field when setting the left property, which this code seems to accomplish. Thank you!

Re: Legend positioning / sizing

Posted: Fri Dec 02, 2011 2:31 am
by 10550286
Actually, it hasn't entirely resolved my problem, but it seems to help. For some reason I still get jitter of a few pixels. Rather than chase that down I'm switching to a one-off setting of the legend left at the start of any real time updating session.

Re: Legend positioning / sizing

Posted: Fri Dec 02, 2011 2:36 am
by 10550286
Ah, sorry, I was missing the -1. Now it works for me with no jitter.

Code: Select all

  int LegWidth = Chart1->Legend->Width;
  Chart1->Legend->Left = NewLeft;
  Chart1->Legend->ShapeBounds.Right = Chart1->Legend->Left + LegWidth - 1;

Re: Legend positioning / sizing

Posted: Fri Dec 02, 2011 10:15 am
by yeray
Hi Sam,

Great! I'm glad to hear you solved it. :D