Synchronizing Axes

TeeChart VCL for Borland/CodeGear/Embarcadero RAD Studio, Delphi and C++ Builder.
Post Reply
Errol
Newbie
Newbie
Posts: 35
Joined: Mon Jul 02, 2018 12:00 am

Synchronizing Axes

Post by Errol » Thu Aug 16, 2018 7:04 am

My standard chart consists on an independent axis (e.g. Depth on the left axis) and two dependent axes (e.g. Pressure and Temperature) plotted on the two horizontal axes. The dependent axes are usually set to Automatic scaling to handle the pressure and temperature data range.
To make the graphs look nicer, I would like to be able to synchronize the top axis with the bottom axis - e.g. go to each major tick on the bottom axis, calculate the corresponding value on the top axis and write the axis label accordingly.
My code works only if both axes are set to Automatic. I cannot get the axes to synchronize at all if one or both axes are set to Logarithmic (note I use custom labels for Logarithmic axes), and I am not succeeding with Inverted.
I have attached a test program and would appreciate any assistance you can offer.
Thanks and regards
Errol
Attachments
DeviatedPT.zip
(78.84 KiB) Downloaded 670 times

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

Re: Synchronizing Axes

Post by Yeray » Fri Aug 17, 2018 9:11 am

Hello,

I'm not sure if this is a test project or you final project, but it starts to have quite a lot of options and combinations. If you need help in one combination (ie synchronizing top and bottom axes when they are logarithmic) we need to focus on it, so please arrange a simple example project we can run as-is to reproduce the problem here.
Once we find a way to make that simple example to work as expected, then you'll be able to investigate what's the best way to insert it into your logic.
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: 35
Joined: Mon Jul 02, 2018 12:00 am

Re: Synchronizing Axes

Post by Errol » Sun Aug 19, 2018 10:50 pm

Hi Yeray
Thank you for your reply. The project is a test project which reflects (some of) the complexity of the main project. I was hoping to pick up some design principles which I could apply to the main project.
I have attached a simplified project, concentrating solely on synchronizing the top and bottom axes. These axes synchronize only if Auto-scaling is selected on one or both axes. When I invert an axis, the corresponding series vanish. I have wrestled with this for some time without success.
I look forward to any suggestions you might offer.
Thanks and regards
Errol
Attachments
Sync_Axes.zip
(62.33 KiB) Downloaded 678 times

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

Re: Synchronizing Axes

Post by Yeray » Mon Aug 20, 2018 10:43 am

Hello,

When the "Synchronize dependent axes" checkbox is checked, you are setting the Top axis with custom labels (SyncTopAxis method) and immediately after it, you are calling ApplyChartLogLabels(True) which resets the Top axis to use Automatic labels.

Then, at SyncTopAxis, instead of using the Bottom Minimum&Maximum values, you could try using the last&first Bottom Axis Items to populate the Top Axis Labels. Ie:

Code: Select all

  BMin:=DBChart1.Axes.Bottom.Items[DBChart1.Axes.Bottom.Items.Count-1].Value;
  BMax:=DBChart1.Axes.Bottom.Items[0].Value;

  TPos:=BMin;
  while (BMax >= TPos) do
  begin
    DBChart1.Axes.Top.Items.Add(TPos, FloatToStr(TPos));  //convert bottom axis values to top axis values
    TPos:=TPos+BInc;
  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: 35
Joined: Mon Jul 02, 2018 12:00 am

Re: Synchronizing Axes

Post by Errol » Wed Aug 22, 2018 1:12 am

Thanks for the reply. I have fixed the reset caused by ApplyLogLabels by calling ApplyLogLabels(False). However, I do not understand your proposed solution using bottom axis positions and values to populate the top axis. In the example in the Sync_Axes project, the position range of the bottom axis is from 25 to 110, while the top axis is from 0 to 350. When I try your code, the top axis labels are bunched up at the left.
In my revised SyncTopAxis code, I obtain the position of each label on the bottom axis and convert it to the correct position on the top axis using simple ratio calculations. I do not understand why the top axis labels are not drawn at exactly the position of the bottom axis labels.

Code: Select all

// ========== TfrmPTChart.SyncTopAxis ==========================================
// Uses bottom axis increments to locate top axis labels
procedure TfrmPTChart.SyncTopAxis;
var
  i, iMin, iMax, Factor: integer;
  d: double;
  Tval, TPos, TMin, TMax, TDiff, BMin, BMax, BDiff, BInc: double;

begin
  DBChart1.Axes.Top.Items.Clear;
  if DBChart1.Axes.Top.Automatic then
  begin
    TMin := DBChart1.Axes.Top.Minimum;
    TMax := DBChart1.Axes.Top.Maximum;
  end
  else
  begin
    TMin := 0;        // arbitrary values
    TMax := 350;
  end;
  TDiff := TMax - Tmin;

  if DBChart1.Axes.Bottom.Automatic then
  begin
    BMin := DBChart1.Axes.Bottom.Minimum;
    BMax := DBChart1.Axes.Bottom.Maximum;
    BInc := DBChart1.Axes.Bottom.CalcIncrement;
  end
  else
  begin
    DBChart1.Axes.Bottom.Minimum := 25;
    DBChart1.Axes.Bottom.Maximum := 110;
    DBChart1.Axes.Bottom.Increment := 20;
    BMin := 25;
    BMax := 110;
    BInc := 20;
  end;

  BDiff := BMax - BMin;

  iMax := DBChart1.Axes.Bottom.Items.Count;
  for i := 0 to iMax - 1 do
  begin
    Tval := Round((DBChart1.Axes.Bottom.Items[iMax - i - 1].Value - BMin)/BDiff*TDiff + TMin);
    TPos := (DBChart1.Axes.Bottom.Items[iMax - i - 1].Value - BMin)/BDiff*TDiff + TMin;
    DBChart1.Axes.Top.Items.Add(TPos, FloatToStr(Tval));  // adding custom label
  end;
end;



Errol
Newbie
Newbie
Posts: 35
Joined: Mon Jul 02, 2018 12:00 am

Re: Synchronizing Axes

Post by Errol » Wed Aug 22, 2018 4:09 am

I apologize for my last post - the error was mine, by assigning different Axis minimum and maximum values at different parts of the code, now corrected. I still have a problem whenever I change the bottom axis Automatic status with Synchronize checked. The code draws the top axis labels using the previous bottom axis labels, and then redraws the bottom axis labels, so they are not synchronized. I clearly have events not firing in the correct order - please advise.
Attachments
Sync_Axes.zip
(58.94 KiB) Downloaded 676 times

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

Re: Synchronizing Axes

Post by Yeray » Wed Aug 22, 2018 11:19 am

Hello,

I wouldn't call SyncTopAxis at OnBeforeDrawAxes or any other event being fired during the paint process unless necessary.
Instead, you could try to call it at the end of cbxAutoBottomClick and cbxAutoTopClick.

On the other hand, SyncTopAxis uses some axis variables that need the chart to be at advanced stages of the drawing routine to get the correct values, so you should force a chart repaint at the start of that method. At the same time, you are changing the Bottom axis scale when it's not Automatic, and this needs another repaint in order to have the correct internal Items later, so you should force another repaint here. Here the complete method that seems to work for me:

Code: Select all

procedure TfrmPTChart.SyncTopAxis;
var
  i, iMin, iMax, Factor: integer;
  d: double;
  Tval, TPos, TMin, TMax, TDiff, BMin, BMax, BDiff, BInc: double;

begin
  DBChart1.Draw;

  Factor := 1;

  if DBChart1.Axes.Top.Automatic then
  begin
    TMin := DBChart1.Axes.Top.Minimum;
    TMax := DBChart1.Axes.Top.Maximum;
  end
  else
  begin
    TMin := TopMin;        // arbitrary values
    TMax := TopMax;
  end;
  TDiff := TMax - Tmin;

  if DBChart1.Axes.Bottom.Automatic then
  begin
    BMin := DBChart1.Axes.Bottom.Minimum;
    BMax := DBChart1.Axes.Bottom.Maximum;
    BInc := DBChart1.Axes.Bottom.CalcIncrement;
  end
  else
  begin
    DBChart1.Axes.Bottom.Minimum := BotMin;
    DBChart1.Axes.Bottom.Maximum := BotMax;
    DBChart1.Axes.Bottom.Increment := 20;
    BMin := DBChart1.Axes.Bottom.Minimum;
    BMax := DBChart1.Axes.Bottom.Maximum;
    BInc := 20;
    DBChart1.Draw;
  end;

  BDiff := BMax - BMin;

  DBChart1.Axes.Top.Items.Clear;
  iMax := DBChart1.Axes.Bottom.Items.Count;
  for i := 0 to iMax - 1 do
  begin
    Tval := Round((DBChart1.Axes.Bottom.Items[iMax - i - 1].Value - BMin)/BDiff*TDiff + TMin);
    TPos := (DBChart1.Axes.Bottom.Items[iMax - i - 1].Value - BMin)/BDiff*TDiff + TMin;
    DBChart1.Axes.Top.Items.Add(TPos, FloatToStr(Tval));  // adding custom label
  end;
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: 35
Joined: Mon Jul 02, 2018 12:00 am

Re: Synchronizing Axes

Post by Errol » Fri Aug 31, 2018 12:14 pm

Good evening
I have implemented your suggestions and have the top and bottom axes synchronizing nicely when changing the autoscaling of each axis. However I am baffled by the Invert behaviour - the relevant series disappear (nothing to do with axis synchronization). Clearly doing something wrong again. Also I do not know how to proceed with the logarithmic option - I hope to be able to avoid using an event - simply step through the logarithmic labels on the bottom axis and calculate the labels on the top axis, but it is not working for me.
I have attached a revised test program.
Thanks and regards
Errol
Attachments
Sync_Axes.zip
(65.61 KiB) Downloaded 683 times

Errol
Newbie
Newbie
Posts: 35
Joined: Mon Jul 02, 2018 12:00 am

Re: Synchronizing Axes

Post by Errol » Sat Sep 01, 2018 9:25 am

Good evening
I am pleased to report that I have enabled axis synchronization with changes of auto-scaling, axis inversion, and logarithmic axes. I have attached some relevant code below as there is some tricky code when dealing with log axes, if other people are interested. This does not apply to my previous test project, but I feel the intention is clear enough.

Code: Select all

// ========== TPBQuickGraph.SyncDep2Axis =======================================
// Uses 1st dependent axis increments to locate 2nd dependent axis labels
procedure TPBQuickGraph.SyncDep2Axis;
var
  i, iMin, iMax, Factor: integer;
  d: double;
  TVal, TPos, TMin, TMax, BVal, BMin, BMax, BInc: double;

begin
  Factor := 1;

  if not SyncAxes_1 or not bLineGraph then
    Exit;

  Chart.Draw;

  TMax := Chart.Axes.Top.Maximum;
  TMin := Chart.Axes.Top.Minimum;
  BMax := Chart.Axes.Bottom.Maximum;
  BMin := Chart.Axes.Bottom.Minimum;
  BInc := Chart.Axes.Bottom.CalcIncrement;

  if (fDependent1Auto and not fDependent2Auto) then
  begin
    Chart.Axes.Bottom.Automatic := False;
    Chart.Axes.Bottom.Maximum := BMax;
    Chart.Axes.Bottom.Minimum := BMin;
  end
  else
  if fDependent2Auto and not fDependent1Auto then
  begin
    Chart.Axes.Top.Automatic := False;
    Chart.Axes.Top.Maximum := TMax;
    Chart.Axes.Top.Minimum := TMin;
  end;
    
  Chart.Axes.Top.Items.Clear;

// this seems to work when inverting axes
  if (fInvertDep1 <> fInvertDep2) then
    Chart.Draw;

  iMax := Chart.Axes.Bottom.Items.Count;

  for i := 0 to iMax - 1 do
  begin
    BVal := Chart.Axes.Bottom.Items[iMax - i - 1].Value;
    
    if (fLogDep1 and not fLogDep2) then
      TPos :=  ((log10(BVal) - log10(BMin))/(log10(BMax) - log10(BMin)))*(TMax-TMin) + TMin
    else
    if (not fLogDep1 and fLogDep2) then
      TPos := power(10,(BVal - BMin)/(BMax-BMin)*(log10(TMax) - log10(TMin)) + log10(TMin))
    else
    if (fLogDep1 and fLogDep2) then
      TPos := power(10,((log10(BVal)-log10(BMin))/(log10(BMax)-log10(BMin))*(log10(TMax)-log10(TMin))+log10(TMin)))
    else
      TPos := (Chart.Axes.Bottom.Items[iMax - i - 1].Value - BMin)/(BMax-BMin)*(TMax-TMin) + TMin;

    TVal := Round(TPos);
    Chart.Axes.Top.Items.Add(TPos, FloatToStr(TVal));  // adding custom label
  end;
end;

Thank and regards
Errol

Post Reply