Page 1 of 2

multiple legend

Posted: Wed Feb 18, 2009 9:35 am
by 10545622
Hi,

I would like to have 2 legends for one graph. The first one with the series name "Serie 1", "Series 2" and the second one for "Serie 3", "Series 4".
It seems that it's only possible to have all the series of a graph in a legend when we choose the lsSeries property.
Or, is it possible (and how) to have one legend but a line to separate two sets of series ?

Best regards

Posted: Wed Feb 18, 2009 11:16 am
by yeray
Hi stsl,

Yes, you could have two legends using a custom legend. Please take a look at the demo at All features/Miscellaneous/Legend/Multiple Legends.

If you prefer to draw a line, you should draw it directly to the canvas:

Code: Select all

procedure TForm1.Chart1AfterDraw(Sender: TObject);
begin
  With Chart1.Legend.ShapeBounds do
  begin
    With Chart1.Canvas do
    begin
      DoHorizLine(Left,Right,Bottom-17);
    end;
  end;
end;

multiple legend

Posted: Wed Feb 18, 2009 12:16 pm
by 10545622
I know that there is a multiple legend demo. But in your demo, if you use the lsSeries mode, 2 legends with all the series will be painted. In my chart, i have, for example 3 series. I would like one legend with the first serie (serie name) and the other one legend with the 2 last series.
I tried to do that :

Code: Select all

procedure TForm1.FormCreate(Sender: TObject);
begin

  Series1.FillSampleValues(100);
  Chart.Legend.LegendStyle := lsSeries;

  Series1.ShowInLegend := False;
  Series2.ShowInLegend := False;
  Series3.ShowInLegend := False;

end;

procedure TForm1.ChartAfterDraw(Sender: TObject);
begin
  if cbAdditionalLegend.Checked then
  begin
    Chart.Legend.Top := 125;
    
	Series1.ShowInLegend := False;
    
	Series2.ShowInLegend := True;
    Series3.ShowInLegend := True;

    

    Chart.Legend.DrawLegend;
    Chart.Legend.CustomPosition := False;
  end;
end;
It works but when i checked a serie (i use checkbox) the 2 legends are both painted with serie2 and serie3 intead of one legend with serie1 and the otherone with serie1 ans serie2.


[/img]

Posted: Wed Feb 18, 2009 12:31 pm
by 10545622
I made a mistake in my sample code. In the form create :

Code: Select all


Series1.ShowInLegend := True;

Series2.ShowInLegend := False; 
Series3.ShowInLegend := False;


Posted: Wed Feb 18, 2009 12:37 pm
by 10545622
I found a solution of my problem. I added a OnBeforeDrawChart :

Code: Select all

procedure TForm1.ChartBeforeDrawChart(Sender: TObject);
begin
  Series1.ShowInLegend := True;

  Series2.ShowInLegend := False;
  Series3.ShowInLegend := False;
 
end;


Posted: Wed Feb 18, 2009 3:16 pm
by 10545622
Now, i would like to have one legend with series and a second legend with serie groups. I modified my ChartAfterDraw procedure like this :

Code: Select all

procedure TForm1.ChartAfterDraw(Sender: TObject);
begin
  if cbadditionallegend.Checked then
  begin
    Chart.Legend.Top := 125;

    Chart.Legend.CheckBoxes:=True;
    Chart.Legend.LegendStyle := lsSeriesGroups;

    Chart.Legend.DrawLegend;
    Chart.Legend.CustomPosition := False;
  end;
end;

The two legends are properly drawn but it's now impossible to check or uncheck the series in the first legend and the groups in the second legend.

Where is my mistake ?

Best regards

Posted: Thu Feb 19, 2009 10:32 am
by yeray
Hi stsl,

Yes, I've seen the problem. It seems that, having multiple series, only the last one is clickable. I''ve added the issue to the wish list to be fixed in further releases (TV52013878).

In the meanwhile, here you have a workaround example that works for me here:

Code: Select all

const CheckBoxesSeparation = 17;
CheckBoxesSize = 12;
FirstItemTop = 56;
CheckBoxesMargin = 4;


implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var i:Integer;
begin
  Chart1.View3D := false;

  for i:=0 to Chart1.SeriesCount-1 do
    Chart1[i].FillSampleValues(25);

  with Chart1.SeriesGroups do
  begin
    Chart1.SeriesList.AddGroup('Group A');
    Items[0].Add(Chart1[0]);
    Items[0].Add(Chart1[1]);

    Chart1.SeriesList.AddGroup('Group B');
    Items[1].Add(Chart1[2]);
    Items[1].Add(Chart1[3]);
  end;

  Chart1.Legend.CheckBoxes:=True;
end;

procedure TForm1.Chart1AfterDraw(Sender: TObject);
begin
  Chart1.Legend.Top := 125;
  Chart1.Legend.LegendStyle := lsSeriesGroups;
  Chart1.Legend.DrawLegend;
end;

procedure TForm1.Chart1BeforeDrawChart(Sender: TObject);
begin
  Chart1.Legend.CustomPosition := False;
  Chart1.Legend.LegendStyle := lsSeries;
end;

procedure TForm1.Chart1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var i: Integer;
begin
  for i:= 0 to Chart1.SeriesCount-1 do
    if ((X > Chart1.Legend.Left + CheckBoxesMargin) and
      (X < Chart1.Legend.Left + CheckBoxesMargin + CheckBoxesSize)) then
      if ((Y > FirstItemTop + (i*CheckBoxesSeparation)) and
        (Y < FirstItemTop + (i*CheckBoxesSeparation) + CheckBoxesSize)) then
        Chart1[i].Active := not Chart1[i].Active;
end;

Posted: Thu Feb 19, 2009 2:19 pm
by 10545622
Hi,

You said that only the last one is clickable. All my legends are not clickable.
So, your sample code solved the problem for the first legend but not for the second one.
How is it possible to track the mouse position for the second legend ?

Best regards

Posted: Thu Feb 19, 2009 2:29 pm
by 10545622
and an other question :

How i can get the real values of the constantes CheckBoxesSeparation,
CheckBoxesSize, FirstItemTop and CheckBoxesMargin ?

Regards

Posted: Thu Feb 19, 2009 4:15 pm
by yeray
Hi stsl,

1) It's strange, Ive tried with extralegend tool and with more than 2 legends and always the last drawn legend is clickable.

Well, you always will be able to add another "for" to check if the click was on the checkbox. This time you could do as follows:

Code: Select all

const CheckBoxesSeparation = 17;
CheckBoxesSize = 12;
FirstItemTopLegend1 = 56;
FirstItemTopLegend2 = 131;
CheckBoxesMargin = 4;

//...

procedure TForm1.Chart1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var i, j: Integer;
begin
  for i:=0 to Chart1.SeriesCount-1 do
    if ((X > Chart1.Legend.Left + CheckBoxesMargin) and
      (X < Chart1.Legend.Left + CheckBoxesMargin + CheckBoxesSize)) then
      if ((Y > FirstItemTopLegend1 + (i*CheckBoxesSeparation)) and
        (Y < FirstItemTopLegend1 + (i*CheckBoxesSeparation) + CheckBoxesSize)) then
        Chart1[i].Active := not Chart1[i].Active;

  for i:=0 to Chart1.SeriesGroups.Count-1 do
    if ((X > Chart1.Legend.Left + CheckBoxesMargin) and
      (X < Chart1.Legend.Left + CheckBoxesMargin + CheckBoxesSize)) then
      if ((Y > FirstItemTopLegend2 + (i*CheckBoxesSeparation)) and
        (Y < FirstItemTopLegend2 + (i*CheckBoxesSeparation) + CheckBoxesSize)) then
        for j:=0 to Chart1.SeriesGroups[i].Series.Count-1 do
          Chart1.SeriesGroups[i].Series[j].Active := not Chart1.SeriesGroups[i].Series[j].Active;
end;
2) I've found the constant values with a simple trick. Add the following code to your OnMouseDown or OnMouseMove event:

Code: Select all

caption := 'X: ' + floattostr(X) + '  Y: ' + floattostr(Y);

Posted: Fri Feb 20, 2009 7:39 am
by 10545622
Hi,

It tried your solution but it didn't work. Could i post (and how) a sample project to you ?

How is it possible to get the position of the legend at runtime (to fix firstItemTopLegend1 and 2) ? The user can customize the position of the legend ar runtime with the editor.

Best regards

Posted: Fri Feb 20, 2009 11:10 am
by yeray
Hi stsl,

I've made some changes to the example to be mora adaptable to different chart sizes.

Code: Select all

var
  Form1: TForm1;
  CheckBoxesSeparation, CheckBoxesSize, FirstItemTopLegend1, Leged2Top, CheckBoxesLeftMargin, CheckBoxesTopMargin: Integer;

implementation

uses Types;

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var i:Integer;
begin
  Chart1.View3D := false;

  for i:=0 to Chart1.SeriesCount-1 do
    Chart1[i].FillSampleValues(25);

  with Chart1.SeriesGroups do
  begin
    Chart1.SeriesList.AddGroup('Group A');
    Items[0].Add(Chart1[0]);
    Items[0].Add(Chart1[1]);

    Chart1.SeriesList.AddGroup('Group B');
    Items[1].Add(Chart1[2]);
    Items[1].Add(Chart1[3]);
  end;

  Chart1.Legend.CheckBoxes:=True;

  CheckBoxesSeparation := 17;
  CheckBoxesSize := 12;
  CheckBoxesLeftMargin := 4;
  CheckBoxesTopMargin := 5;
  Leged2Top := 125;
end;

procedure TForm1.Chart1AfterDraw(Sender: TObject);
begin
  Chart1.Legend.Top := Leged2Top;
  Chart1.Legend.LegendStyle := lsSeriesGroups;
  Chart1.Legend.DrawLegend;
end;

procedure TForm1.Chart1BeforeDrawChart(Sender: TObject);
begin
  Chart1.Legend.CustomPosition := False;
  Chart1.Legend.LegendStyle := lsSeries;
end;

procedure TForm1.Chart1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var i, j: Integer;
begin
  Chart1.Canvas.Pen.Color := clRed;

  for i:=0 to Chart1.SeriesCount-1 do
  begin
    if ((X > Chart1.Legend.Left + CheckBoxesLeftMargin) and
      (X < Chart1.Legend.Left + CheckBoxesLeftMargin + CheckBoxesSize)) then
      if ((Y > FirstItemTopLegend1 + (i*CheckBoxesSeparation) + CheckBoxesTopMargin) and
        (Y < FirstItemTopLegend1 + (i*CheckBoxesSeparation) + CheckBoxesSize + CheckBoxesTopMargin)) then
        Chart1[i].Active := not Chart1[i].Active;

  //  Draw rectangles to test positions
  //  Chart1.Canvas.Rectangle(Chart1.Legend.Left + CheckBoxesLeftMargin, FirstItemTopLegend1 + (i*CheckBoxesSeparation) + CheckBoxesTopMargin, Chart1.Legend.Left + CheckBoxesLeftMargin + CheckBoxesSize, FirstItemTopLegend1 + (i*CheckBoxesSeparation) + CheckBoxesSize + CheckBoxesTopMargin);
  end;

  for i:=0 to Chart1.SeriesGroups.Count-1 do
  begin
    if ((X > Chart1.Legend.Left + CheckBoxesLeftMargin) and
      (X < Chart1.Legend.Left + CheckBoxesLeftMargin + CheckBoxesSize)) then
      if ((Y > Chart1.Legend.Top + (i*CheckBoxesSeparation) + CheckBoxesTopMargin) and
        (Y < Chart1.Legend.Top + (i*CheckBoxesSeparation) + CheckBoxesSize + CheckBoxesTopMargin)) then
        for j:=0 to Chart1.SeriesGroups[i].Series.Count-1 do
          Chart1.SeriesGroups[i].Series[j].Active := not Chart1.SeriesGroups[i].Series[j].Active;

  //  Draw rectangles to test positions
  //  Chart1.Canvas.Rectangle(Chart1.Legend.Left + CheckBoxesLeftMargin, Chart1.Legend.Top + (i*CheckBoxesSeparation) + CheckBoxesTopMargin, Chart1.Legend.Left + CheckBoxesLeftMargin + CheckBoxesSize, Chart1.Legend.Top + (i*CheckBoxesSeparation) + CheckBoxesSize + CheckBoxesTopMargin);
  end;

//  Show X and Y of the pixel clicked 
//  caption := 'X: ' + floattostr(X) + '  Y: ' + floattostr(Y);
end;

procedure TForm1.Chart1GetLegendRect(Sender: TCustomChart;
  var Rect: TRect);
begin
  if Rect.Top <> Leged2Top then
    FirstItemTopLegend1 := Rect.Top;
end;
Yes, you can either post your files at news://www.steema.net/steema.public.attachments newsgroup or at our upload page.

Posted: Fri Feb 20, 2009 12:33 pm
by 10545622
Thank you for your sample.
I uploaded a file with your upload page (TChartLegend.zip). In this sample, i can check/unchek the second legend (group) but for the first legend (Series), i have to click at the right of the check box (and not on the check box) to uncheck/check the series. Click on the Gantt values button to add the grouped series and check the additionallegend check box to see the ExtraLegend tool.

In your sample, do you use an ExtraLegend tool or a multiple legend ?
It would be better if i could download your complete sample.

Best regards

Posted: Fri Feb 20, 2009 3:39 pm
by yeray
Hi stsl,

In my sample I used multiple legends, but with extralegend tool it looks more simple because you can access to legend positions when you want because it's not re-written. So we won't need OnBeforeDraw, OnAfterDraw and OnGetLegendRect events.

In your project, the legend works fine for me all the time. I mean, simply adding an extralegendtool, the normal legend remains clickable. Then we only need to find the checkboxes of the extralegend and see if the click is in its area.

The following code works for me in your project:

Code: Select all

procedure TForm1.ChartMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  i, j: Integer;
begin
    for i:= 0 to Chart.SeriesGroups.Count - 1 do
    begin
      if (X > ExtraLegend.Legend.Left) and (X < ExtraLegend.Legend.Left + ExtraLegend.Legend.Width) then
      begin
        if (Y > ExtraLegend.Legend.Top + CheckBoxesMargin + (i * CheckBoxesSeparation)) and (Y < ExtraLegend.Legend.Top + CheckBoxesMargin + (i*CheckBoxesSeparation) + CheckBoxesSize) then
        begin
          outputdebugstring('Clic groups good');
          if Chart.SeriesGroups[i].Active = gaYes then
            Chart.SeriesGroups[i].Active := gaNo
          else
            Chart.SeriesGroups[i].Active := gaYes;

          Exit;
        end;
      end;
    end;
end;
PS: I also recommend you to change the value of CheckBoxesMargin to 5.

Posted: Fri Feb 20, 2009 3:44 pm
by 10545622
Congratulations, it works very well...
Best regards