unit Axisu;{ Methods for plotting axes to chromatogram window }
{
  (C) 2000 - 2001 Jindrich Jindrich, Pavel Pisa, PiKRON Ltd.

  Originators of the CHROMuLAN project:

  Jindrich Jindrich - http://www.jindrich.com
                      http://orgchem.natur.cuni.cz/Chromulan/
                      software developer, project coordinator
  Pavel Pisa        - http://cmp.felk.cvut.cz/~pisa
                      embeded software developer
  PiKRON Ltd.       - http://www.pikron.com
                      project initiator, sponsor, instrument developer

  The CHROMuLAN project is distributed under the GNU General Public Licence.
  See file COPYING for details.

  Originators reserve the right to use and publish sources
  under different conditions too. If third party contributors
  do not accept this condition, they can delete this statement
  and only GNU license will apply.
}
{$I DEFINE.PAS}
interface

uses
  SysUtils,
  UtlType, WinUtl, UlanType, Spectrum, Plotu,
  {$IFDEF DEBUG}
  DebugFrm,
  {$ENDIF}
  Msgu, Math, Language, ExeLogu;

function CreateAxisPlotting(var ApexPoints:TAcqData;
  const AUserLimit: TUserViewLimit; const AScreenDisp: TScreenDisp;
  var APlotting:TPlotting):boolean;

{function CreateAxisPlotting(var ApexPoints: TAcqData; const AScreenDisp: TScreenDisp;
  var APlotting: TPlotting): boolean;
}
const
  XMaxLen:integer = 6;
  XMaxDec:integer = 3;
  XMinDec:integer = 0;
  XMaxNonEOrder = 3;
  XMinNonEOrder = -3;


  YMaxLen:integer = 7;
  YMaxDec:integer = 5;
  YMinDec:integer = 2;
  YMaxNonEOrder = 1;
  YMinNonEOrder = -5;
implementation

type

  TAxisOpt=record
    {primary:}
    UserViewLimit:TUserViewLimit;
      { Rozsah grafu }
    ScreenSize:TScreenPoint;
      { Rozsah okna kam se kresli osy x=width, y=height }
    ScreenOrigin:TScreenPoint;
      { Left top corner of screen window coordinates }
    {calculated:}
    UserFirstSec: TUserPoint;
      { First axis dash value, >= UserViewLimit.Min }
    UserViewSize: TUserPoint;
    UserSecSize: TUserPoint;
    ScreenSecSize: TScreenPoint;
    SecCount: TScreenPoint;
      { Count of grid sections }
    SecondYAxis:boolean;
    PRX, PRY:byte;
      { What percentage of axis length is the length of the section
        dash (=1% by default) }
  end;

const

  DefXAxisName: PChar = 'Time';
  DefYAxisName: PChar = 'AU';

(*
rectangle
procedure COutText(st:string);{clear text area, outtext}
var UserFirstSec.Y,x,y:integer;
begin
  x:=textwidth(st);
  y:=textheight(st);
  UserFirstSec.X := APlotting.GetX;
  UserFirstSec.Y := gety;
  with viewport do begin
    setviewport(x1+UserFirstSec.X,y1+UserFirstSec.Y,x1+UserFirstSec.X+x,y1+UserFirstSec.Y+y,true);
    clearviewport;
  end;
  with viewport do
    setviewport(x1,y1,x2,y2,true);
  MoveTo(UserFirstSec.X,UserFirstSec.Y);
  outtext(st);
end;
*)

procedure RoundUserPars(var AUserFirstSec: single; var AUserViewSize: single;
  var AUserSecSize: single; var ASecCount:integer);
{
vstup:
  AUserFirstSec, (hodnota prvniho dilku na ose (puvodne leva hranice grafu))
  AUserViewSize, (rozsah grafu, osy)
  ASecCount;(pocet dilku, na nez se deli osa(minimalne 3),
    muze byt zmeneno, je urcite zmeneno kdyz < 3)

vystup:
  zaokrouhli se AUserFirstSec (podle ASecCount a AUserViewSize),
  AUserSecSize (velikost jednoho dilku osy)
  ASecCount  pocet dilku na ose
}

const
  ars=3;
  allowedSecSizes: array[1..ars] of byte = (1, 2, 5);{(1, 2, 4, 5, 8);}
   {povolene velikosti dilku}

var
  r, {rg, }r1, {r2,} mg, m: real;
  i, {j, k, }l, lg: integer;
{  en: boolean;}
  OUserFirstSec{, OUserSecSize}: real; {original AUserFirstSec and OUserSecSize}

begin
{  en := false;}
  OUserFirstSec := AUserFirstSec;
{  OUserSecSize := AUserViewSize;}
  if (ASecCount < 3) or (ASecCount > 100) then begin
    ASecCount := 10;
  end;

  r := AUserViewSize / ASecCount;
  GetManLog(r, m, l);{m mantisa of the z range, l order of the z range}
  GetManLog(AUserFirstSec, mg, lg);{mg mantisa of th z start value,
    lg order of the z start value}
  {i :=1;}
  {$HINTS OFF}
  r1 := 0;{ just to satisfy compiler }
  {$HINTS ON}

  for i := 1 to ars do begin
    if i = ars then begin
      l := succ(l);
      r1 := allowedSecSizes[1];
      break;
    end;
    r1 := allowedSecSizes[i];
    if abs(m) <= (r1*1.2) then begin
      break;{en:=true}
    end;
  end;
  {
  while not en do begin
    r1 := allowedSecSizes[i];
    if abs(m) <= r1 then
      en:=true
    else begin
      i:=succ(i);
      if i = succ(ars) then begin
        en := true;
        l := succ(l);
        i := 1;
      end;
    end;
    r1 := allowedSecSizes[i];
  end;
  }

  {
  else begin
    r1:=abs(graphscale);
    if m>r1 then
      l:=succ(l);
  end
  }
  ;
  AUserSecSize := r1 * IntPower(10, l){exp10(l)};
  r1 := IntPower(10, lg - l){exp10(lg-l)};
  AUserFirstSec := IntPower(10, l){exp10(l)} * int(mg*r1) - AUserSecSize;
  while AUserFirstSec < OUserFirstSec do
    AUserFirstSec := AUserFirstSec + AUserSecSize;
  ASecCount := round((AUserViewSize -
    (AUserFirstSec - OUserFirstSec)) / AUserSecSize)
  ;
    {don't get the axis start out of graphic range}

{
  AUserViewSize:=AUserSecSize*ASecCount;
  while AUserFirstSec + AUserViewSize > OUserSecSize + OUserFirstSec do
    AUserViewSize := AUserViewSize - AUserSecSize;
}
{$IFDEF DEBUG}
  DebLog('RoundUserPars: UsrFrstSec:' + FloatToStr(AUserFirstSec) + ' ViewSize:' +
    FloatToStr(AUserViewSize) + ' -> SecSize:' + FloattoStr(AUserSecSize) + ' SecCnt:' +
    IntToStr(ASecCount));
{$ENDIF}
end;


function CreateAxisPlotting(var ApexPoints:TAcqData;
  const AUserLimit: TUserViewLimit; const AScreenDisp: TScreenDisp;
  var APlotting:TPlotting):boolean;

{UserFirstSec.X,UserFirstSec.Y:real;xname,yname:pathstr}
{kresli osy x,y, prusecik v poloze UserFirstSec.X,UserFirstSec.Y, v aktivnim viewportu,
 podle ScreenAxisOpt a UserAxisOpt;SecCount.X,SecCount.Y useky-delka kolmych carek na ose x-prx procent
 celkove delky osy y,podobne pry; popisy  jmen osy,hodnoty pruseciku,
 max hodnot os}

var
  AxisOpt:TAxisOpt;
  th:TScreenY;
  {tw:TScreenX;}
  {v0.62}
  {tmp: single;}
  {/v0.62}

  procedure GetAxisOpt;
  begin
    {primary:}
    AxisOpt.UserViewLimit := AUserLimit;{ApexPoints.SpectrumOpt.UserLimit;}
    AxisOpt.ScreenSize.X := AScreenDisp.Right - AScreenDisp.Left + RectInclusive;{AScreenDisp.Width;}
    AxisOpt.ScreenSize.Y := AScreenDisp.Bottom - AScreenDisp.Top + RectInclusive;{AScreenDisp.Height;}
    AxisOpt.ScreenOrigin.X := AScreenDisp.Left;
    AxisOpt.ScreenOrigin.Y := AScreenDisp.Top;

    {calculated:}
    AxisOpt.UserFirstSec := AxisOpt.UserViewLimit.Min;
      {.. will be adjusted (rounded) by RoundUserPars}

    AxisOpt.UserViewSize.X := {v0.62}abs{/v0.62}(AxisOpt.UserViewLimit.Max.X -
      AxisOpt.UserViewLimit.Min.X);
    AxisOpt.UserViewSize.Y := {v0.62}abs{/v0.62}(AxisOpt.UserViewLimit.Max.Y -
      AxisOpt.UserViewLimit.Min.Y);
      {.. just for faster calculations}

    AxisOpt.UserSecSize.X := 0;
    AxisOpt.UserSecSize.Y := 0;
      {.. will be set by RoundUserPars}

    AxisOpt.ScreenSecSize.X := 0;
    AxisOpt.ScreenSecSize.Y := 0;
      {.. will be set by RoundUserPars}

    AxisOpt.SecCount.X := 0;
    AxisOpt.SecCount.Y := 0;
      {.. will be set by RoundUserPars}

    AxisOpt.PRX := 1;
    AxisOpt.PRY := 1;
      {.. defaults mylib}

    RoundUserPars(AxisOpt.UserFirstSec.X, AxisOpt.UserViewSize.X,
      AxisOpt.UserSecSize.X, AxisOpt.SecCount.X);

    RoundUserPars(AxisOpt.UserFirstSec.Y, AxisOpt.UserViewSize.Y,
      AxisOpt.UserSecSize.Y, AxisOpt.SecCount.Y);

    ApexPoints.UserSizeToScreenSize(AxisOpt.UserSecSize, AScreenDisp, AxisOpt.ScreenSecSize);
  end;


  procedure WriteBelowXAxis(AText:string; var ALastTextRightEdge:TScreenX;
    X:TScreenX; CanWriteOver:boolean);
  {psani textu pod osu x zleva, kontrola na prepsani,
   posl. pozice: ALastTextRightEdge; }
  var
    {w,h,
    dx, dy,}
    tw,{th,}
    y:integer;
    {v0.09}
    tx:integer;
    ttw:integer;
    {/v0.09}
  begin
    {v0.09}
    ttw := APlotting.TextWidth(trim(AText));
    {/v0.09}
    AText:= trim(AText)+ ' ';
    y := APlotting.GetY;
    tw := APlotting.TextWidth(AText);
    {th := APlotting.TextHeight(AText);}
    {v0.09}
    tx := x - (ttw div 2);
    {/v0.09 tx := x;}

    if tx < 0 then
      tx := 0;
    if (tx + tw) >= (AxisOpt.ScreenOrigin.X + AxisOpt.ScreenSize.X) then
      tx := AxisOpt.ScreenOrigin.X + AxisOpt.ScreenSize.X - 1;
    if tx < 0 then
      tx := 0;

    if y < 0 then
      y := 0;
    {if y > dy - th then
      y := dy - th;}
    if tx < ALastTextRightEdge then begin
      if not CanWriteOver then
        exit
      else
        AText:=' '+AText;
    end;
    ALastTextRightEdge := tx + tw;
    APlotting.MoveTo(tx,y);
    APlotting.ClearTextOut(tx, y, AText);
  end;

  procedure AxisNumToStr(r:real; Len, Dec:integer; var st:string);
  {var {i,j,l,k:integer; st1:string;}
  begin
     st := FloatToStrF(r, ffFixed, len, dec);
     {
     r:=r*100;
     if r > 0 then
       r:=r + 0.5
     else
       r:=r - 0.5;
     r := int(r) / 100;
     str(r:5:2, st);}
  end;

  procedure CutAxisStr(var st:string);
  {var i,j:integer;}
  begin
    if pos('E',st)>0 then
      st:=copy(st,1,length(st)-4);
  end;

  procedure WriteNextToYAxis(AText:string; var ALastTextUpperEdge:TScreenY;
    Y:TScreenY; CanWriteOver:boolean);
  {psani textu podel osy y odspoda, kontrola na prepsani, posl. pozice: ALastTextUpperEdge}
  var
    tw, x: TScreenX;
    {th: TScreenY;}
  begin
    AText := AText;
    x := APlotting.GetX;
    tw := APlotting.TextWidth(AText);
    {th := APlotting.TextHeight(AText);}
    if x < 0 then
      x := 0;
    if (x + tw) >= (AxisOpt.ScreenOrigin.X + AxisOpt.ScreenSize.X) then
      x := (AxisOpt.ScreenOrigin.X + AxisOpt.ScreenSize.X - tw);
    if x < 0 then
      x := 0;

    if y < 0 then
      y:=0;
    if y > (AxisOpt.ScreenOrigin.X + AxisOpt.ScreenSize.Y - 1) then
      y := AxisOpt.ScreenOrigin.X + AxisOpt.ScreenSize.Y - 1;
    if (y + th) > ALastTextUpperEdge then begin
      if not CanWriteOver then
        exit;
    end;
    ALastTextUpperEdge := y;
    APlotting.MoveTo(x,y);
    APlotting.ClearTextOut(x, y, AText);{textout}
  end;

  procedure UserXYToScreenXY(x:TUserX; y:TUserY; var gx:TScreenX; var gy:TScreenY);
  var
    userPoint:TuserPoint;
    screenPoint:TScreenPoint;
  begin
    userPoint.X := x;
    userPoint.Y := y;
    gx := 0;
    gy := 0;
    if not ApexPoints.UserToScreen(userPoint, AScreenDisp, screenPoint) then
      exit;
    gx := screenPoint.x;
    gy := screenPoint.y;
  end;

  procedure DrawXAxis;
  var
    x:TUserX;
    y:TUserY;

    i:TScreenX;
    j:TScreenY;
    m, iy1, iy2:TScreenY;
    LastTextRightEdge:TScreenX;
    secman,uman:real;
    seclog,ulog, curdec:integer;
    UserValueOrder:real;
    st:string;
  begin
    GetManLog(AxisOpt.UserViewLimit.Max.X, uman, ulog);
    if (ulog <= XMaxNonEOrder) and (ulog >= XMinNonEOrder) then
      ulog := 0;
    {if (ulog = 1) or (ulog = -1) then
      ulog := 0;}
    GetManLog(AxisOpt.UserSecSize.X, secman, seclog);
    {curdec := XMaxDec;}
    if seclog < 0 then begin
      curdec := -seclog;
    end else begin
      curdec := 0;
    end;
     if curdec > XMaxDec then
      curdec := XMaxDec;
    if curdec < XMinDec then
      curdec := XMinDec;

    UserValueOrder := IntPower(10, ulog){exp10(ulog)};
    LastTextRightEdge := 0;

    m := round(AxisOpt.ScreenSize.Y / 100 * AxisOpt.PRX);
    x := AxisOpt.UserFirstSec.X;
    y := AxisOpt.UserViewLimit.Min.Y;{UserFirstSec.Y;}

    iy1 := AxisOpt.ScreenOrigin.Y + AxisOpt.ScreenSize.Y  + m;
    iy2 := AxisOpt.ScreenOrigin.Y + AxisOpt.ScreenSize.Y - 1;

    while x <= AxisOpt.UserViewLimit.Max.X do begin
      {draw sections on X axis:}
      UserXYToScreenXY(x,y,i,j);
      APlotting.Line(i,iy1,i,iy2);
      if x = AxisOpt.UserFirstSec.X then begin
        AxisNumToStr(AxisOpt.UserFirstSec.X / UserValueOrder, XMaxLen, curDec, st);
        APlotting.MoveTo(i, AxisOpt.ScreenOrigin.Y + AxisOpt.ScreenSize.Y
          + 2* m);
        WriteBelowXAxis(st, LastTextRightEdge, i, true);
      end;

      if x > AxisOpt.UserFirstSec.X then {if x<>(UserViewSize.X+UserViewLimit.Min.X) then}
      begin
        AxisNumToStr(x{re+ AxisOpt.UserFirstSec.X)}/UserValueOrder, XMaxLen, curDec, st);
        CutAxisStr(st);
        APlotting.MoveTo(i, AxisOpt.ScreenOrigin.Y + AxisOpt.ScreenSize.Y + 2*m);
        {re:= re + r;}
        WriteBelowXAxis(st,LastTextRightEdge,i,false);
      end;
      x := x + AxisOpt.UserSecSize.X;
    end;

    x := AxisOpt.UserViewLimit.Max.X - AxisOpt.UserSecSize.X;
    UserXYToScreenXY(x,y,i,j);
    APlotting.SetLineStyle(0,0,0);
    if ulog <> 0 then begin
      str(ulog:3,st);
      st:=' (*10E'+trim(st)+')';
    end else
      st:='';
    st:={xname+}st + ' ';
    i := AxisOpt.ScreenOrigin.X + AxisOpt.ScreenSize.X - APlotting.TextWidth(st);
    APlotting.MoveTo(i, AxisOpt.ScreenOrigin.Y + AxisOpt.ScreenSize.Y + th + m);
    WriteBelowXAxis(st, LastTextRightEdge, i, true);
  end;

  procedure DrawYAxis;
  var
    {textx:TScreenX;}

    x,tx: TUserX;
    y:TUserY;

    tofs,m,i,ix1,ix2:TScreenX;
    j{,th}:TScreenY;

    ulog, seclog:integer;
    uman, secman:real;
    curdec:integer;

    LastTextUpperEdge:TScreenY;
    UserValueOrder:real;
    st:string;
    cw:TScreenX;
  begin
    GetManLog(AxisOpt.UserViewLimit.Max.Y, uman, ulog);
    if (ulog <= YMaxNonEOrder) and (ulog >= YMinNonEOrder) then
      ulog := 0;
    UserValueOrder := IntPower(10, ulog){exp10(ulog)};
    GetManLog(AxisOpt.UserSecSize.Y, secman, seclog);
    curdec := YMaxDec;
    if seclog < 0 then begin
      curdec := -seclog;
      if curdec > YMaxDec then
        curdec := YMaxDec;
      if curdec < YMinDec then
        curdec := YMinDec;
    end;


    LastTextUpperEdge := AxisOpt.ScreenSize.Y + AxisOpt.ScreenOrigin.Y;


    m := round(AxisOpt.ScreenSize.X / 100 * AxisOpt.PRY);
    x := AxisOpt.UserViewLimit.Min.X;
    tx := x + AxisOpt.UserSecSize.X;
      { to be sure the x will be inside the region }

    ix1 := AxisOpt.ScreenOrigin.X - m;
    ix2 := AxisOpt.ScreenOrigin.X;

    {th := APlotting.TextHeight(' ');}
    cw := APlotting.TextWidth(' ');
    {if ix2 > ScreenSize.X then
      ix2 := ScreenSize.X;}
    {
    GetManLog(AxisOpt.UserSecSize.Y, re, lg);
    re := round(re) * exp10(lg);
    r := re;
    if pry > 10 then
      APlotting.SetLineStyle(1, 0, 0);
    }

    y := AxisOpt.UserFirstSec.Y;
    while y <= AxisOpt.UserViewLimit.Max.Y do begin
      UserXYToScreenXY(tx, y, i, j);
      APlotting.Line(ix1, j, ix2, j);
      j := j - th div 2;

      if y = AxisOpt.UserFirstSec.Y then begin
        AxisNumToStr(AxisOpt.UserFirstSec.Y / UserValueOrder, YMaxLen, curdec{YMaxDec}, st);
        if y < 0 then
          tofs := 0{cw}
        else
          tofs := 0;

        APlotting.MoveTo(AxisOpt.ScreenOrigin.X - m - cw - tofs -
          APlotting.TextWidth(st), j);
        WriteNextToYAxis(st, LastTextUpperEdge, j, true);
      end;

      if y > AxisOpt.UserFirstSec.Y then begin
        if y <> (AxisOpt.UserViewSize.Y + AxisOpt.UserViewLimit.Min.Y) then
        begin
          AxisNumToStr(y / UserValueOrder, YMaxLen, curDec, st);
          CutAxisStr(st);
          if y < 0 then
            tofs := 0{cw}
          else
            tofs := 0;
          APlotting.MoveTo(AxisOpt.ScreenOrigin.X - m - cw - tofs -
            APlotting.TextWidth(st), j);
          WriteNextToYAxis(st, LastTextUpperEdge, j, false);
        end;
      end;
      y := y + AxisOpt.UserSecSize.Y;
    end;
    {y := y - AxisOpt.UserSecSize.Y;
    {y := AxisOpt.UserViewLimit.Max.Y;
    UserXYToScreenXY(x,y,i,j);
    APlotting.SetLineStyle(0, 0, 0);}

    i := AxisOpt.ScreenOrigin.X;
    j := AxisOpt.ScreenOrigin.Y;
    if ulog <> 0 then begin
      str(ulog, st);
      st:=' (*10E' + trim(st) + ')';
    end else
      st:='';
    st := {yname + }st + ' ';
    i := i - APlotting.TextWidth(st) - m;
    APlotting.MoveTo(i, j);
    WriteNextToYAxis(st, LastTextUpperEdge, j, true);
  end;

begin
  CreateAxisPlotting := false;
  APlotting.Clear;
   {xname := 'X';
  yname := 'Y';}
  th := APlotting.TextHeight('M');
  {tw := APlotting.TextWidth(' ');}
  {rlen := 8;
  tofs := 3;}

  GetAxisOpt;

  if ((AxisOpt.UserSecSize.X <= 0) or
      (AxisOpt.UserViewSize.Y <= 0) or
      (AxisOpt.UserSecSize.Y <= 0) or
      (AxisOpt.UserViewSize.X <= 0)) then
  begin
    ShowMessage(GetTxt({#}'Incorrect graph. parameters (in CreateAxisPlotting)'), smError,0);
    exit;
  end;
  APlotting.Rectangle(
    AxisOpt.ScreenOrigin.X,
    AxisOpt.ScreenOrigin.Y,
    AxisOpt.ScreenOrigin.X + AxisOpt.ScreenSize.X - RectInclusive,
    AxisOpt.ScreenOrigin.Y + AxisOpt.ScreenSize.Y - RectInclusive
  );

  DrawXAxis;
  DrawYAxis;
  CreateAxisPlotting := true;
end;

end.