#include "winplus.h"
#pragma hdrstop
#include <math.h>
#include <stdio.h>

// =======================================================================
// Функция ищет ближайшее к данному число с заданным количеством значащих
// цифр и последней четной цифрой
static double AdjustRange(double Range,int Digits)
{
  if(Digits<1) Digits=1;
  double md,s,mult,v31,v32;
  if(Range>=0) s=1.0; else s=-1.0;
  mult=pow(10,floor(log10(Range*s)));
  md = pow((double)10,Digits - 1);
  v31=Range*s*md/mult;
  v32=ceil(v31);
  v31=floor(v31);
  if(fmod(v31,2)!=0.0) v31=v32;
  return s*v31*mult/md;
}

CSamplesFormat::CSamplesFormat(DWORD fuInitialFlags,CDCObject *lpDC,
  CGraphicsSamples *lpSamples)
{
  fuFlags=fuInitialFlags;
  bLastError=TRUE;
  szLabelX[0]=szLabelY[0]=0;
  this->lpDC=lpDC;
  this->lpSamples=lpSamples;
  lpDC->GetClientArea(&rcOut);
  pnGrid=new CPen;
  pnAxis=new CPen;
  pnFrame=new CPen;
  fntLabelX=new CFont;
  fntLabelY=new CFont;
  if((fntLabelX==NULL)||(fntLabelY==NULL)||(pnGrid==NULL)||
    (pnAxis==NULL)||(pnFrame==NULL)) return;
  fntLabelX->GetLogFont()->lfCharSet=RUSSIAN_CHARSET;
  fntLabelY->GetLogFont()->lfCharSet=RUSSIAN_CHARSET;
  fntLabelY->SetOrientation(900);
  fntLabelX->SetFaceName("Arial");
  fntLabelY->SetFaceName("Arial");
  fntLabelX->GetLogFont()->lfOutPrecision=OUT_TT_PRECIS;
  fntLabelY->GetLogFont()->lfOutPrecision=OUT_TT_PRECIS;
  pnGrid->SetStyle(PS_DOT);
  pnGrid->SetColor(0);
  pnGrid->SetWidth(1);
  pnAxis->SetWidth(3);
  pnAxis->SetColor(0);
  pnFrame->SetColor(0);
  pnFrame->SetWidth(1);
  iGridNumX=5;
  iGridNumY=5;
  iDigitsX=2;
  iDigitsY=2;
  GridSizeX=1.0;
  GridSizeY=1.0;
}

CSamplesFormat::~CSamplesFormat()
{
  if(fntLabelX) delete fntLabelX;
  if(fntLabelY) delete fntLabelY;
  if(pnGrid) delete pnGrid;
  if(pnAxis) delete pnAxis;
  if(pnFrame) delete pnFrame;
}

void CSamplesFormat::AutoLabelPlaces()
{
  bLastError=TRUE;
  if((lpSamples==NULL)||(lpDC==NULL)) return;
  POINT arv[4];
  POINT Origin={0,0};
  int left, top, right, bottom;
  int x,y,wi,he;
  x=lpSamples->GetZeroPoint()->x;
  y=lpSamples->GetZeroPoint()->y;
  if((fuFlags&SF_LABELX)&&(szLabelX[0]!='\0'))
  {
    // Видна ли ось абсцисс - ?
    y=lpSamples->GetZeroPoint()->y;
    if((y<=rcOut.bottom)&&(y>=rcOut.top))
    {
      lpDC->CreateFont(fntLabelX);
      lpDC->GetTextRect(szLabelX,&Origin,arv);
      left=right=arv[0].x;
      top=bottom=arv[0].y;
      for(int i=1;i<4;i++)
      {
        if(left>arv[i].x) left=arv[i].x;
        if(right<arv[i].x) right=arv[i].x;
        if(top>arv[i].y) top=arv[i].y;
        if(bottom<arv[i].y) bottom=arv[i].y;
      }
      wi=right-left;
      he=bottom-top;
      LabelXPlace.x=rcOut.right-8-wi;
      // Отступаем на высоту метки и еще на 8 от оси абсцисс
      LabelXPlace.y=y+8+he;
    } // end if видна ось абсцисс
    else
    {
      LabelXPlace.x=LabelXPlace.y=-1.0;
      fuFlags&=~SF_LABELX;
    }
  }
  else
  {
    LabelXPlace.x=LabelXPlace.y=-1.0;
    fuFlags&=~SF_LABELX;
  }
  // Те же операции для метки по оси ординат
  if((fuFlags&SF_LABELY)&&(szLabelY[0]!='\0'))
  {
    // Видна ли ось ординат - ?
    x=lpSamples->GetZeroPoint()->x;
    if((x<=rcOut.right)&&(x>=rcOut.left))
    {
      lpDC->CreateFont(fntLabelY);
      lpDC->GetTextRect(szLabelY,&Origin,arv);
      left=right=arv[0].x;
      top=bottom=arv[0].y;
      for(int i=1;i<4;i++)
      {
        if(left>arv[i].x) left=arv[i].x;
        if(right<arv[i].x) right=arv[i].x;
        if(top>arv[i].y) top=arv[i].y;
        if(bottom<arv[i].y) bottom=arv[i].y;
      }
      wi=right-left;
      he=bottom-top;
      LabelYPlace.y=rcOut.top+8+he;
      LabelYPlace.x=x-8-2*wi;
    } // end if видна ось ординат
    else
    {
      LabelYPlace.x=LabelYPlace.y=-1.0;
      fuFlags&=~SF_LABELY;
    }
  }
  else
  {
    LabelYPlace.x=LabelYPlace.y=-1.0;
    fuFlags&=~SF_LABELY;
  }
  bLastError=FALSE;
}

void CSamplesFormat::DrawLabels()
{
  AutoLabelPlaces();
  lpDC->SetBkMode(OPAQUE);
  if((fuFlags&SF_LABELX)&&(szLabelX[0]!=0))
  {
    lpDC->CreateFont(fntLabelX);
    lpDC->TextOut(LabelXPlace.x,LabelXPlace.y,szLabelX);
  }
  if((fuFlags&SF_LABELY)&&(szLabelY[0]!=0))
  {
    lpDC->CreateFont(fntLabelY);
    lpDC->TextOut(LabelYPlace.x,LabelYPlace.y,szLabelY);
  }
}

void CSamplesFormat::DrawGrid()
{
  double sx, sy;
  double zx, zy;
  double minx, maxx, miny, maxy;
  BOOL bmtXVisible,bmtYVisible;
  if(lpSamples==NULL) return;
  lpDC->SetBkMode(OPAQUE);
  zx=lpSamples->GetZeroPoint()->x;
  zy=lpSamples->GetZeroPoint()->y;
  lpSamples->GetMinMaxX(&minx,&maxx);
  lpSamples->GetMinMaxY(&miny,&maxy);
  // Весь диапазон изменения абсциссы и ординаты к этому моменту уже
  // приведен к размерам окна вывода. Однако, он (скорее всего:)
  // представляется бесконечной периодической дробью, что неудобно для
  // меток. Потому мы:
  //  - переходим к исходным единицам;
  //  - в исходных единицах находим число, возможно более близкое к тре-
  //    буемому шагу и содержащее нужное количество значащих цифр;
  //  - переходим к экранным единицам.
  if(fuFlags&SF_AUTOGRIDX)
  {
    sx=(maxx-minx)/iGridNumX/lpSamples->GetZoom()->x;
    sx=AdjustRange(sx,iDigitsX);
    GridSizeX=sx*lpSamples->GetZoom()->x;
  }
  else
    sx=GridSizeX/lpSamples->GetZoom()->x;
  if(fuFlags&SF_AUTOGRIDY)
  {
    sy=(maxy-miny)/iGridNumY/lpSamples->GetZoom()->y;
    sy=AdjustRange(sy,iDigitsY);
    GridSizeY=sy*lpSamples->GetZoom()->y;
  }
  else
    sy=GridSizeY/lpSamples->GetZoom()->y;
  // ---------------------------------------------------------------------
  // Рисуем оси
  int nAxis=0;
  lpDC->CreatePen(pnAxis);
  // Признаки видимости меток имеет смысл вычислить заранее.
  // Если ось абсцисс видна:
  if((zy>=rcOut.top)&&(zy<=rcOut.bottom))
  {
    if(fuFlags&SF_AXISX)
    {
      lpDC->MoveTo(rcOut.left,zy);
      lpDC->LineTo(rcOut.right,zy);
    }
    bmtXVisible=TRUE;
    // Одну ось нарисовали...
    nAxis++;
  }
  else
    bmtXVisible=FALSE;
  // Если ось ординат видна:
  if((zx>=rcOut.left)&&(zx<=rcOut.right))
  {
    if(fuFlags&SF_AXISY)
    {
      lpDC->MoveTo(zx,rcOut.top);
      lpDC->LineTo(zx,rcOut.bottom);
    }
    bmtYVisible=TRUE;
    // Вторую ось нарисовали...
    nAxis++;
  }
  else
    bmtYVisible=FALSE;
  lpDC->CreateFont(fntLabelX);
  // ---------------------------------------------------------------------
  // Если видно начало координат, то ставим около него метку
  if(nAxis==2)
  {
    char szZeroMark[]="0";
    SIZE szz;
    lpDC->GetTextExtentPoint(szZeroMark,&szz);
    lpDC->TextOut(zx-szz.cx-3,zy+3,szZeroMark);
  }
  // ---------------------------------------------------------------------
  // Рисуем сетки или шкалы
  // Сетку рисуем тонкими линиями, шкалу - толстыми
  if(fuFlags&SF_GRIDX)
    lpDC->CreatePen(pnGrid);
  // Иначе использум то же перо, что и для осей
  else
    lpDC->CreatePen(pnAxis);
  // Начальные и конечные координаты меток шкалы
  int m1, m2;
  BOOL bmVisible=TRUE;
  SIZE szm;
  m1=zy-2;
  m2=zy+3;
  if(m1<rcOut.top) m1=rcOut.top;
  if(m2>rcOut.bottom) m2=rcOut.bottom;
  if(m1>=m2) bmVisible=FALSE;
  // ---------------------------------------------------------------------
  // Рисуем вертикальную сетку в обе стороны от нуля:
  // В положительную сторону...
  double LeftLimit = (double)rcOut.left+.5;
  double RightLimit = (double)rcOut.right-.5;
  double TopLimit = (double)rcOut.top+.5;
  double BottomLimit = (double)rcOut.bottom-.5;
  double x=zx+GridSizeX;
  double xorg=sx;
  while(x<RightLimit)
  {
    if(fuFlags&SF_GRIDX)
    {
      lpDC->MoveTo(x,rcOut.top);
      lpDC->LineTo(x,rcOut.bottom);
    }
    else
    {
      if(bmVisible)
      {
        lpDC->MoveTo(x,m1);
        lpDC->LineTo(x,m2);
      }
    }
    if(bmtXVisible&&(fuFlags&SF_MARKX))
    {
      sprintf(szBuffer,"%g",xorg);
      lpDC->GetTextExtentPoint(szBuffer,&szm);
      lpDC->TextOut(x-szm.cx/2,zy+4,szBuffer);
    }
    x+=GridSizeX;
    xorg+=sx;
  }
  // ...и в отрицательную:
  x=zx-GridSizeX;
  xorg=-sx;
  while(x>LeftLimit)
  {
    if(fuFlags&SF_GRIDX)
    {
      lpDC->MoveTo(x,rcOut.top);
      lpDC->LineTo(x,rcOut.bottom);
    }
    else
    {
      if(bmVisible)
      {
        lpDC->MoveTo(x,m1);
        lpDC->LineTo(x,m2);
      }
    }
    if(bmtXVisible&&(fuFlags&SF_MARKX))
    {
      sprintf(szBuffer,"%g",xorg);
      lpDC->GetTextExtentPoint(szBuffer,&szm);
      lpDC->TextOut(x-szm.cx/2,zy+4,szBuffer);
    }
    x-=GridSizeX;
    xorg-=sx;
  }
  // Повторяем эти действия для горизонтальной сетки
  // Метки значений рисуем вертикальным шрифтом
  lpDC->CreateFont(fntLabelY);
  bmVisible=TRUE;
  m1=zx-2;
  m2=zx+3;
  if(m1<rcOut.left) m1=rcOut.left;
  if(m2>rcOut.right) m2=rcOut.right;
  if(m1>=m2) bmVisible=FALSE;
  if(fuFlags&SF_GRIDY)
    lpDC->CreatePen(pnGrid);
  else
    lpDC->CreatePen(pnAxis);
  // ---------------------------------------------------------------------
  // Рисуем вертикальную сетку в обе стороны от нуля:
  // В положительную сторону...
  double y=zy+GridSizeY;
  double yorg=sy;
  while(y<BottomLimit)
  {
    if(fuFlags&SF_GRIDY)
    {
      lpDC->MoveTo(rcOut.left,y);
      lpDC->LineTo(rcOut.right,y);
    }
    else
    {
      if(bmVisible)
      {
        lpDC->MoveTo(m1,y);
        lpDC->LineTo(m2,y);
      }
    }
    if(bmtYVisible&&(fuFlags&SF_MARKY))
    {
      sprintf(szBuffer,"%g",yorg);
      lpDC->GetTextExtentPoint(szBuffer,&szm);
      lpDC->TextOut(zx-szm.cy-4,y+szm.cx/2,szBuffer);
    }
    y+=GridSizeY;
    yorg+=sy;
  }
  // ...и в отрицательную:
  y=zy-GridSizeY;
  yorg=-sy;
  while(y>TopLimit)
  {
    if(fuFlags&SF_GRIDY)
    {
      lpDC->MoveTo(rcOut.left,y);
      lpDC->LineTo(rcOut.right,y);
    }
    else
    {
      if(bmVisible)
      {
        lpDC->MoveTo(m1,y);
        lpDC->LineTo(m2,y);
      }
    }
    if(bmtYVisible&&(fuFlags&SF_MARKY))
    {
      sprintf(szBuffer,"%g",yorg);
      lpDC->GetTextExtentPoint(szBuffer,&szm);
      lpDC->TextOut(zx-szm.cy-4,y+szm.cx/2,szBuffer);
    }
    y-=GridSizeY;
    yorg-=sy;
  }
  // ---------------------------------------------------------------------
  // Рисуем рамку тонкой линией
  lpDC->CreatePen(pnFrame);
  lpDC->HollowRectangle(&rcOut);
}

void CSamplesFormat::SetGridSizes(double GridSizeX,double GridSizeY)
{
  // Переводим исходные координаты в ЭКРАННЫЕ
  this->GridSizeX=fabs(GridSizeX*lpSamples->GetZoom()->x);
  this->GridSizeY=fabs(GridSizeY*lpSamples->GetZoom()->y);
}

void CSamplesFormat::Draw()
{
  DrawGrid();
  DrawLabels();
}

void CSamplesFormat::UseCustomMinMaxX(double MinX, double MaxX, BOOL bUse)
{
  if(lpSamples) lpSamples->UseCustomMinMaxX(MinX, MaxX, bUse);
}

void CSamplesFormat::UseCustomMinMaxY(double MinY, double MaxY, BOOL bUse)
{
  if(lpSamples) lpSamples->UseCustomMinMaxY(MinY, MaxY, bUse);
}

void CSamplesFormat::SetLabelNames(char *szLabelX, char *szLabelY)
{
  strncpy(this->szLabelX,szLabelX,MAX_LABEL_LENGTH);
  strncpy(this->szLabelY,szLabelY,MAX_LABEL_LENGTH);
  this->szLabelX[MAX_LABEL_LENGTH-1]=0;
  this->szLabelY[MAX_LABEL_LENGTH-1]=0;
}

void CSamplesFormat::SetOutputRect(RECT *rcOut)
{
  this->rcOut.left=rcOut->left;
  this->rcOut.top=rcOut->top;
  this->rcOut.right=rcOut->right;
  this->rcOut.bottom=rcOut->bottom;
  if(fuFlags&SF_AUTOSCALEFONT)
  {
    int fntHeight = (rcOut->bottom-rcOut->top) / 30;
    if(fntHeight<10) 
      fntHeight=10;
    else 
      if(fntHeight>300) 
        fntHeight=300;
    fntLabelX->SetHeight(fntHeight);
    fntLabelY->SetHeight(fntHeight);
  }
}

