How to get reasonable X axis markers

TeeChart VCL for Borland/CodeGear/Embarcadero RAD Studio, Delphi and C++ Builder.
Post Reply
UliBru
Newbie
Newbie
Posts: 9
Joined: Tue Aug 30, 2016 12:00 am

How to get reasonable X axis markers

Post by UliBru » Sun Jun 11, 2017 6:30 am

Hi,

I need to create charts with a logarithmic x axis. Furthermore it is necessary to zoom into the charts.
This leads to strange x axis markers and ticks.
Some examples:
htklirr_linkst0u42.png
htklirr_linkst0u42.png (13.63 KiB) Viewed 15582 times
hmtklirr_linkskiuq5.png
hmtklirr_linkskiuq5.png (17.88 KiB) Viewed 15587 times
tmtklirr_rechtsb8upw.png
tmtklirr_rechtsb8upw.png (18.28 KiB) Viewed 15581 times
So how to control the appearance of major, minor ticks and text with different zoom states?
Is there a function for this or is it necessary to draw the x axis by a custom function?
What is a good strategy to get nice and reasonable text markers for a zoomed picture?

Thanks for help

- Uli

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

Re: How to get reasonable X axis markers

Post by Yeray » Mon Jun 12, 2017 8:15 am

Hello,

This seems to work as expected for me here:

Code: Select all

uses Series;

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

  Chart1.Axes.Bottom.Logarithmic:=True;
  with Chart1.AddSeries(TLineSeries) as TLineSeries do
  begin
    Pointer.Visible:=True;
    Pointer.Size:=2;

    for i:=1 to 11 do
        AddXY(exp(i), i);
  end;
end;
However, the bottom axis labels overlap if you put more values into that series. This depends on the chart width but I get overlapping axis labels with 23 values.
I've added it to the public tracker:
http://bugs.teechart.net/show_bug.cgi?id=1879

I haven't found extra problems when zooming the chart though.
It would be helpful of you could arrange a simple example we could run as-is to reproduce the problem here.
Thanks in advance.
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

UliBru
Newbie
Newbie
Posts: 9
Joined: Tue Aug 30, 2016 12:00 am

Re: How to get reasonable X axis markers

Post by UliBru » Mon Jun 12, 2017 12:03 pm

Hello Yeroay,

thanks, I'll prep some example code.
In the meantime I have also detected that the appearance of the x axis labes also depends on the window size. So resizing the application window changes the labels.
And this also leads to a problem as even if the desktop window may nicely show up the labels the export of the chart with smaller sizes for documentation will result in pictures with modified labels. And this result is unpredictable from the user point of view.
Beside this there must also be a bug. I will prep some pictures.

Best regards
Uli

UliBru
Newbie
Newbie
Posts: 9
Joined: Tue Aug 30, 2016 12:00 am

Re: How to get reasonable X axis markers

Post by UliBru » Wed Jun 14, 2017 9:21 am

Hello Yeray,

I have done some further tests.
The devil is given by Chart.BottomAxis.Increment. By default the value is 0 and we get x axis labels at positions 1, 10, 100, 1000 ...
If the value is changed to 1 then we get x axis labes at 1000, 2000, 3000 ... But if the chart is zoomed the labels can overlap.
Dragging a window corner to resize the window even shows up the x axis labels jumping between different visualizations.

So a solution is to always use Chart.BottomAxis.Increment := 0. As this is a default value where it makes sense to NEVER TOUCH it!

Now I have still one question (based on a minor tick number of 8):
Is it possible to create x axis labes e.g. in sequence 1, 5, 10, 50, 100, 500, 1000, 5000, 10000 ... ??

And finally there is still a bug with zoom:
So if you e.g. create a chart with logarithmic x axis from 1 to 10000 and you zoom into the chart from 150 to 10000 the minor ticks below 1000 get lost.
See the picture, where 8 minor ticks are missed. So there is no chance to estimate the x axis position according to a given curve point:
AmplitudeTest.png
AmplitudeTest.png (6.38 KiB) Viewed 15553 times
Best regards
Uli

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

Re: How to get reasonable X axis markers

Post by Yeray » Thu Jun 15, 2017 8:24 am

Hello Uli,
UliBru wrote:I have done some further tests.
The devil is given by Chart.BottomAxis.Increment. By default the value is 0 and we get x axis labels at positions 1, 10, 100, 1000 ...
If the value is changed to 1 then we get x axis labes at 1000, 2000, 3000 ... But if the chart is zoomed the labels can overlap.
Dragging a window corner to resize the window even shows up the x axis labels jumping between different visualizations.

So a solution is to always use Chart.BottomAxis.Increment := 0. As this is a default value where it makes sense to NEVER TOUCH it!

Now I have still one question (based on a minor tick number of 8):
Is it possible to create x axis labes e.g. in sequence 1, 5, 10, 50, 100, 500, 1000, 5000, 10000 ... ??
Sounds as the request in #890 but you you could try using custom labels as follows:

Code: Select all

uses Series, Math;

procedure TForm1.FormCreate(Sender: TObject);
var i: Integer;
begin
  Chart1.Align:=alClient;
  Chart1.View3D:=False;
  Chart1.Legend.Hide;

  with Chart1.AddSeries(TLineSeries) as TLineSeries do
  begin
    Pointer.Show;
    Pointer.Size:=2;

    for i:=0 to 10 do
       AddXY(exp(i), i);
  end;

  Chart1.Axes.Bottom.Logarithmic:=True;
  Chart1.Axes.Bottom.Increment:=1;

  Chart1.Axes.Bottom.Items.Clear;
  for i:=0 to 5 do
  begin
    Chart1.Axes.Bottom.Items.Add(power(10, i), FormatFloat('#,##0.##', power(10, i)));
    Chart1.Axes.Bottom.Items.Add(power(10, i)*5, FormatFloat('#,##0.##', power(10, i)*5));
  end;

end;
Project2_2017-06-15_09-53-10.png
Project2_2017-06-15_09-53-10.png (17.4 KiB) Viewed 15545 times
UliBru wrote:And finally there is still a bug with zoom:
So if you e.g. create a chart with logarithmic x axis from 1 to 10000 and you zoom into the chart from 150 to 10000 the minor ticks below 1000 get lost.
See the picture, where 8 minor ticks are missed. So there is no chance to estimate the x axis position according to a given curve point:
This sounds as the ticket #1388. Feel free to add your mail to the Cc list to be automatically notified when an update arrives.
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

UliBru
Newbie
Newbie
Posts: 9
Joined: Tue Aug 30, 2016 12:00 am

Re: How to get reasonable X axis markers

Post by UliBru » Sun Jun 18, 2017 3:50 pm

Yeray,

thanks.

1)
I'll study on using the custom labels. At first look it is ok, but in combination with zooming this may be tricky. Because I may need to design custom labels for each zoom situation.

2)
I have cc'd to the bug list regarding the minor ticks display bug

BR
Uli

UliBru
Newbie
Newbie
Posts: 9
Joined: Tue Aug 30, 2016 12:00 am

Re: How to get reasonable X axis markers

Post by UliBru » Tue Jun 20, 2017 2:25 pm

Hello Yeray,

after struggling around with switching linear and logarithmic x-axis including zooming I have now come to the following solution:

Code: Select all

procedure TForm1.Chart1AfterDraw(Sender: TObject);
var
  i, j: integer;
  maxx, minx, Position: Double;
  Chart: TChart;
begin
  Chart := TChart(Sender);
  maxx := Chart.BottomAxis.Maximum;
  minx := Chart.BottomAxis.Minimum;
  if maxx = minx then
    exit;
  if Chart.BottomAxis.Logarithmic then
  begin
    for i := -1 to 5 do
      for j := 2 to 9 do
      begin
        Position := Power(10, i) * j;
        if (Position >= minx) and (Position <= maxx) then
          DrawMinorTick(Chart, Power(10, i) * j);
      end;
  end;
end;

procedure TForm1.Chart1BeforeDrawAxes(Sender: TObject);
var
  i: integer;
  maxx, minx: Double;
  Chart: TChart;
begin
  Chart := TChart(Sender);
  maxx := Chart.BottomAxis.Maximum;
  minx := Chart.BottomAxis.Minimum;
  if maxx = minx then
    exit;
  Chart.Axes.Bottom.Items.Clear;
  if Chart.BottomAxis.Logarithmic then
  begin
    Chart.BottomAxis.MinorTickCount := 0;
    if (minx > 0.1) and (maxx < 1) or (minx > 1) and (maxx < 10) or (minx > 10) and (maxx < 100) or (minx > 100) and
      (maxx < 1000) or (minx > 1000) and (maxx < 10000) or (minx > 10000) and (maxx < 100000) or (minx > 100000) and
      (maxx < 1000000) then
    begin
      Chart.Axes.Bottom.Items.Clear;
      Chart.Axes.Bottom.Items.Add(minx, Format('%1.0f', [minx]));
      Chart.Axes.Bottom.Items.Add(maxx, Format('%1.0f', [maxx]));
    end
    else
    begin
      for i := -1 to 5 do
      begin
        Chart.Axes.Bottom.Items.Add(Power(10, i), Format('%1.0f', [Power(10, i)]));
//        Chart.Axes.Bottom.Items.Add(Power(10, i) * 5, Format('%1.0f', [Power(10, i) * 5]));
      end;
      if (minx <> 0.1) and (minx <> 1) and (minx <> 10) and (minx <> 100) and (minx <> 1000) and (minx <> 10000) and
        (minx <> 100000) then
        Chart.Axes.Bottom.Items.Add(minx, Format('%1.0f', [minx]));
      if (maxx <> 0.1) and (maxx <> 1) and (maxx <> 10) and (maxx <> 100) and (maxx <> 1000) and (maxx <> 10000) and
        (maxx <> 100000) then
        Chart.Axes.Bottom.Items.Add(maxx, Format('%1.0f', [maxx]));
    end;
  end
  else // linear axis
  begin
    Chart.Axes.Bottom.Items.Automatic := true;
    Chart.BottomAxis.MinorTickCount := 3;
  end;
end;
So the solution shows the labels in Power10 steps. If you zoom in and a power10 label is still there it will be shown. In addition the chart now also displays the lowest and highest x value. So if you zoom more into the chart at least min and max x value are displayed as label.
Furthermore the minor ticks are custom drawn and thus are correct for each zoom state. The reported bug is at least also solved by this approach.
Also the export e.g. to PNG now properly works.

So maybe you can benefit a little from my example.

Best regards
Uli

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

Re: How to get reasonable X axis markers

Post by Yeray » Wed Jun 21, 2017 8:11 am

Hello Uli,

Thanks for the code! I've added a link to it in the #1388 ticket.
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

UliBru
Newbie
Newbie
Posts: 9
Joined: Tue Aug 30, 2016 12:00 am

Re: How to get reasonable X axis markers

Post by UliBru » Wed Jun 21, 2017 2:56 pm

Hello Yeray,

I have refined the code for correctness, speed and clarity.
It contains some specialties as a user can zoom into between the major ticks. The minor ticks are still shown, the chart ends show labels to inform about the x axis values.
If there is a major label too close to a chart end the according end label is not displayed.

Code: Select all

procedure TForm1.Chart1AfterDraw(Sender: TObject);
var
  i: integer;
  maxx, minx, tickpos, tickposx: Double;
  Chart: TChart;

  procedure DrawMinorTick(value: Double);
  var
    xpos, ypos: integer;
  begin
    Chart.Canvas.Pen.Color := Chart.BottomAxis.MinorTicks.Color;
    Chart.Canvas.Pen.Width := Chart.BottomAxis.MinorTicks.Width;
    Chart.Canvas.Pen.Style := Chart.BottomAxis.MinorTicks.Style;
    xpos := Chart.BottomAxis.CalcPosValue(value);
    ypos := Chart.BottomAxis.PosAxis + 1;
    Chart.Canvas.Line(xpos, ypos, xpos, ypos + Chart.BottomAxis.MinorTickLength);
  end;

begin
  Chart := TChart(Sender);
  maxx := Chart.BottomAxis.Maximum;
  minx := Chart.BottomAxis.Minimum;
  if maxx = minx then // chart may be empty
    exit;
  if Chart.BottomAxis.Logarithmic then
  begin
    tickpos := 0.01;
    repeat
      for i := 2 to 9 do // = 8 minor ticks
      begin
        tickposx := tickpos * i;
        if tickposx < minx then
          continue;
        if tickposx > maxx then
          break;
        DrawMinorTick(tickposx);
      end;
      tickpos := tickpos * 10;
    until tickposx > maxx;
  end;
end;

procedure TForm1.Chart1BeforeDrawAxes(Sender: TObject);
var
  maxx, minx, xpos, minlabelpos, maxlabelpos: Double;
  inbetween: Boolean;
  Chart: TChart;

  procedure AddXLabel(xpos: Double);
  begin
    if (xpos >= 1) then
      Chart.Axes.Bottom.Items.Add(xpos, Format('%1.0f', [xpos]))
    else if (xpos >= 0.1) then
      Chart.Axes.Bottom.Items.Add(xpos, Format('%1.1f', [xpos]))
    else
      Chart.Axes.Bottom.Items.Add(xpos, Format('%1.2f', [xpos]));
  end;

  procedure AddMinMaxXLabel(xpos: Double);
  begin
    if (xpos >= 10) then
      Chart.Axes.Bottom.Items.Add(xpos, Format('%1.0f', [xpos]))
    else if (xpos >= 1) then
      Chart.Axes.Bottom.Items.Add(xpos, Format('%1.1f', [xpos]))
    else
      Chart.Axes.Bottom.Items.Add(xpos, Format('%1.2f', [xpos]));
  end;

begin
  Chart := TChart(Sender);
  maxx := Chart.BottomAxis.Maximum;
  minx := Chart.BottomAxis.Minimum;
  if maxx = minx then // chart may be empty
    exit;
  Chart.Axes.Bottom.Items.Clear;
  if Chart.BottomAxis.Logarithmic then
  begin
    Chart.BottomAxis.MinorTickCount := 0; // clear automatic minor ticks, custom draw in OnBeforeAxes

    // check for absence of major ticks
    xpos := 0.01;
    inbetween := false;
    repeat
      if ((minx > xpos) and (maxx < xpos * 10)) then
        inbetween := true;
      xpos := xpos * 10;
    until xpos > maxx;
    if inbetween then // no major ticks
    begin
      AddMinMaxXLabel(minx);
      AddMinMaxXLabel(maxx);
    end
    else
    begin
      // draw major ticks + labels
      minlabelpos := 1000000;
      maxlabelpos := 0;
      xpos := 0.01;
      repeat
        if (xpos >= minx) then
        begin
          if xpos > maxx then
            break;
          AddXLabel(xpos);
          // AddXLabel(Position*5);
          if xpos < minlabelpos then // memorize minlabel
            minlabelpos := xpos;
          if xpos > maxlabelpos then // memorize maxlabel
            maxlabelpos := xpos;
        end;
        xpos := xpos * 10;
      until xpos > maxx;
      // possibly draw labels at chart ends
      if minx <= 0.9 * minlabelpos then
        AddMinMaxXLabel(minx);
      if maxx >= 1.2 * maxlabelpos then
        AddMinMaxXLabel(maxx);
    end;
  end
  else // linear axis
  begin
    Chart.Axes.Bottom.Items.Automatic := true;
    Chart.BottomAxis.MinorTickCount := 3;
  end;
end;
Have fun :)

Uli

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

Re: How to get reasonable X axis markers

Post by Yeray » Thu Jun 22, 2017 6:24 am

Thanks for sharing, Uli!
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