Source file: /~heha/enas/Convac-Ätzer/RezEdit.zip/XYGraph.cpp

#include "XYGraph.h"
#include <math.h>
#include <float.h>
#include <windowsx.h>

#ifndef GetWindowLongPtr
# define GetWindowLongPtr GetWindowLong
#endif
#ifndef SetWindowLongPtr
# define SetWindowLongPtr SetWindowLong
#endif

/***********
 * XYGraph *
 ***********/
LPARAM XYGraph::WndProc(HWND Wnd,UINT msg,WPARAM wParam,LPARAM lParam) {
 XYGraph*o=(XYGraph*)GetWindowLongPtr(Wnd,0);
 if (msg==WM_NCCREATE) SetWindowLongPtr(Wnd,0,LPARAM(o=new XYGraph));
 LPARAM r=o->handleWndProc(Wnd,msg,wParam,lParam);
 if (msg==WM_NCDESTROY) delete o;
 if (r) return r;
 return DefWindowProc(Wnd,msg,wParam,lParam);
}

XYGraph::XYGraph() {
 xscales.push_back(XScale(this));	// 3 = BOTTOM, nach rechts; Mindestens 1 Skale pro Seite garantieren
 yscales.push_back(YScale(this));	// 4 = LEFT, nach oben
 hbrBack=CreateSolidBrush(RGB(0,32,0));
}
XYGraph::~XYGraph() {
 DeleteBrush(hbrBack);
}

LPARAM XYGraph::handleWndProc(HWND Wnd,UINT msg,WPARAM wParam,LPARAM lParam) {
 switch (msg) {
  case WM_SIZE: {
// Rechtecke für Skalen und Plot neu berechnen
   rect client(0,0,GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));
   rect margin;
// Skalenbreiten berechnen
   std::vector<Scale>::iterator i;
   HDC dc=GetDC(Wnd);
   HFONT o=SelectFont(dc,hFont);
   for (i=xscales.begin(); i<xscales.end(); ++i) {
    int w=i->needWidth(dc);
    if (i->f&2) margin.bottom+=w; else margin.top+=w;	// Jeweils Höhe abziehen
    i->top=0;
    i->bottom=w;
   }
   for (i=yscales.begin(); i<yscales.end(); ++i) {
    int w=i->needWidth(dc);
    if (i->f&2) margin.right+=w; else margin.left+=w;	// Jeweils Breite abziehen
    i->left=0;
    i->right=w;
   }
   for (i=xscales.begin(); i<xscales.end(); ++i) {
    int h=i->needExtra();
    if (margin.left<h) margin.left=h;	// Minimalen Rand links garantieren
    if (margin.right<h) margin.right=h;	// Minimalen Rand rechts garantieren
   }
   for (i=yscales.begin(); i<yscales.end(); ++i) {
    int h=i->needExtra();
    if (margin.top<h) margin.top=h;	// Minimalen Rand oben garantieren
    if (margin.bottom<h) margin.bottom=h;// Minimalen Rand unten garantieren
   }
// Rand (aus summierte Skalenbreiten) abrechnen
   client.left+=margin.left;
   client.top+=margin.top;
   client.right-=margin.right;
   client.bottom-=margin.bottom;
// Plotrand abrechnen, Plotbereich setzen
   rcPlot=client;
   rcPlot.inflate(-2,-2);
// Skalenbereiche setzen: Vom Plot auswachsend
   for (i=xscales.begin(); i<xscales.end(); ++i) {
    i->left=rcPlot.left;
    i->right=rcPlot.right;
    long&growside=(&client.left)[i->f&3];
    growside=i->repos(growside);
   }
   for (i=yscales.begin(); i<yscales.end(); ++i) {
    i->top=rcPlot.top;
    i->bottom=rcPlot.bottom;
    long&growside=(&client.left)[i->f&3];
    growside=i->repos(growside);
   }
   SelectFont(dc,o);
   ReleaseDC(Wnd,dc);
  }break;
  case WM_PRINTCLIENT: {
   HDC dc=(HDC)wParam;
   SelectFont(dc,hFont);
   rect rcPlot0(rcPlot);
   rcPlot0.inflate(1,1);
   DrawEdge(dc,&rcPlot0,BDR_SUNKENOUTER,BF_RECT);
   FillRect(dc,&rcPlot,hbrBack);
   std::vector<Scale>::const_iterator i;
   SetBkMode(dc,TRANSPARENT);
   for (i=xscales.begin(); i<xscales.end(); ++i) i->paint(dc);
   for (i=yscales.begin(); i<yscales.end(); ++i) i->paint(dc);
   HRGN clip=CreateRectRgnIndirect(&rcPlot);
   SelectObject(dc,clip);
   for (std::vector<Plot>::const_iterator j=plots.begin(); j<plots.end(); ++j) j->paint(dc);
   for (std::vector<Cursor>::const_iterator k=cursors.begin(); k<cursors.end(); ++j) k->paint(dc);
  }break;
  case WM_PAINT: {
   PAINTSTRUCT ps;
   HDC dc=BeginPaint(Wnd,&ps);
   SendMessage(Wnd,WM_PRINTCLIENT,(WPARAM)dc,PRF_CLIENT);
   EndPaint(Wnd,&ps);
  }break;
  case WM_SETFONT: {
   hFont=(HFONT)wParam;
   std::vector<Scale>::iterator i;
   for (i=xscales.begin(); i<xscales.end(); ++i) i->onSetFont();
   for (i=yscales.begin(); i<yscales.end(); ++i) i->onSetFont();
  }break;
  case WM_GETFONT: return (LPARAM)hFont;
  case GM_RESET: plots.clear(); break;
  case GM_ADDPLOT: plots.push_back(Plot(this,new DynDataInt)); break;
  case GM_ADDPOINT: {
   plots.back().data->add(wParam,lParam);
   SetTimer(Wnd,1,100,NULL);
  }break;
  case WM_TIMER: switch (wParam){
   case 1:{
    KillTimer(Wnd,1);
    std::vector<Scale>::iterator i;
    std::vector<Plot>::const_iterator j;
    for (i=xscales.begin(); i<xscales.end(); ++i) i->minmax.prepare();
    for (i=yscales.begin(); i<yscales.end(); ++i) i->minmax.prepare();
    for (j=plots.begin(); j<plots.end(); ++j) {
     j->data->ExpandMinMax(0,j->xScale->minmax);
     j->data->ExpandMinMax(1,j->yScale->minmax);
    }
    for (i=xscales.begin(); i<xscales.end(); ++i) {
     i->minmax.verify();
     i->autoscale();
    }
    for (i=yscales.begin(); i<yscales.end(); ++i) {
     i->minmax.verify();
     i->autoscale();
    }
    InvalidateRect(Wnd,NULL,TRUE);
   }break;
  }break;
 }
 return 0;
}

/*********
 * Scale *
 *********/
template<class T>void MinMax<T>::prepare() {	// Für Bereichssuche vorbereiten
 min=HUGE_VAL;
 max=-HUGE_VAL;
}
template<>void MinMax<int>::prepare() {
 min= 0x7FFFFFFF;
 max=-0x7FFFFFFF;
}
template<class T>void MinMax<T>::expand(T v) {	// Bereichssuche
 if (!_finite(v)) return;
 if (min>v) min=v;
 if (max<v) max=v;
}
template<>void MinMax<int>::expand(int v) {	// Bereichssuche
 if (unsigned(v-0x7FFFFFFF)<3) return;		// +INF, -INF und NaN als Input ignorieren
 if (min>v) min=v;
 if (max<v) max=v;
}
template<class T>void MinMax<T>::verify() {	// Bereichssuche beenden (mit LabVIEW-Vorgabe)
 if (min>max) {
  min=0;
  max=10;
 }else if (min==max) {
  if (min<0) max=0;
  else if (min>0) min=0;
  else max=10;
 }
}

Scale::Scale(XYGraph*o,int side,const char*n):owner(o),f(side),name(n),ticklen(5),nk(0),factor(1),offset(0) {
 format="%.*f";
 setscale(0,10);
 nameColor=RGB(0,0,0);	// TODO: GetSysColor mit privaten Indizes?
 tickColor=RGB(0,0,0);
 majorGridColor=RGB(153,153,0);
 minorGridColor=RGB(76,76,0);
 tickPen=CreatePen(PS_SOLID,0,tickColor);
 majorGridPen=CreatePen(PS_SOLID,0,majorGridColor);
 minorGridPen=CreatePen(PS_SOLID,0,minorGridColor);
 f|=0xE00;		// eigener Stift
}
Scale::~Scale() {
 if (f&0x100) DeleteFont(nameFont);
// if (f&0x200) DeletePen(tickPen);
// if (f&0x400) DeletePen(majorGridPen);
// if (f&0x800) DeletePen(minorGridPen);
}

void Scale::onSetFont() {
 if (f&0x100) DeleteFont(nameFont);
 nameFont=owner->hFont;
 if (!(f&1)) {		// 90°-Schrift generieren
  LOGFONT lf;
  GetObject(nameFont,sizeof lf,&lf);
  lf.lfEscapement=lf.lfOrientation=f&2?2700:900;
  nameFont=CreateFontIndirect(&lf);
  f|=0x100;
 }
}
int Scale::repos(int xy) {
 int w=width_height();
 (&left)[f&3^2]=xy;	// links: Rechte Seite, oben: Unterseite, rechts: Linke Seite, unten: Oberseite setzen
 if (f&2) return (&right)[f&1]=xy+w;	// rechts/unten: Nach außen setzen
 else return (&left)[f&1]=xy-w;		// links/oben: Auch nach außen setzen
}

void Scale::onSize() {
 if (f&0x10) autoscale();
}

static double st125(double v,double&e,int&nk) {	// Rundet einen (positiven!) Wert herauf zu einer dezimalen Mantisse 1, 2 oder 5
 int l=int(floor(log10(v)));	// "Glatter" Logarithmuswert: 99->1, 100->2 usw.
 nk=l<0?-l:0;
 double p=pow(10,l);
 int m=int(ceil(v/p));	// Mantisse zum Logarithmus, 1<=m<10, aufrundend
 if (m>5) m=10;
 else if (m>2) m=5;	// aufrunden in o.a. Stufung
 int me=int(floor(e/p));// Mantisse für Endwert, abrundend (kann negativ sein!)
 if (m==2) me&=~1;	// gerade und abrunden
 else if (m==5) me-=(me+500000000)%5;	// Teilbarkeit durch 5 sicherstellen und abrunden
 e=me*p;		// Skalenendwert liefern
 return m*p;		// Schrittweite liefern
}

void Scale::measureValue(HDC dc,float v) {
 SIZE sz;
 char sbuf[32];
 int l=sprintf(sbuf,format,nk,v);
 GetTextExtentPoint32(dc,sbuf,l,&sz);
 sz.cx+=4;		// etwas Luft
 if (szNumb.cx<sz.cx) szNumb.cx=sz.cx;
 if (szNumb.cy<sz.cy) szNumb.cy=sz.cy;
}

int Scale::needWidth(HDC dc) {
 szNumb.cx=0; szNumb.cy=0;
 measureValue(dc,minmax.min);
 measureValue(dc,minmax.max);
 int w=0;
 if (f&0x40) {		// sichtbar?
  if (f&1) {		// X-Achse?
   w=ticklen+1+szNumb.cy;	// Breite in Y-Richtung: Skalenstrich + Texthöhe
  }else{		// Y-Achse?
   w=ticklen+1+szNumb.cx;	// Breite in X-Richtung: Skalenstrich + Textbreite
  }
  if (f&0x20) w+=szNumb.cy;	// dazu Skalenbeschriftung (gleiche Schriftart)
 }
 return w;
}
int Scale::needExtra() const{
 if (!(f&0x40)) return 0;
 if (f&1) return szNumb.cx>>1;	// X-Achse: Halbe Textbreite
 return szNumb.cy>>1;		// Y-Achse: Halbe Texthöhe
}

void Scale::autoscale() {
 int k;
 if (f&1) {
  k=owner->rcPlot.width()/szNumb.cx;	//max. Anzahl Skalenwerte nebeneinander
 }else{
  k=owner->rcPlot.height()/szNumb.cy;	//max. Anzahl Skalenwerte übereinander
 }
 if (k) {
  double e=minmax.max;
  double step=st125(minmax.diff()/k,e,nk);
  ticks.resize(int((e-minmax.min)/step)+1);
  std::vector<float>::iterator i;
  for (i=ticks.end(); i!=ticks.begin();) {
   *--i=e; e-=step;	// von hinten nach vorn
  }
 }
}

void XScale::paint(HDC dc) const{
 SetTextAlign(dc,f&2?TA_TOP|TA_CENTER:TA_BOTTOM|TA_CENTER);
 SetTextColor(dc,tickColor);
 std::vector<float>::const_iterator i;
 for(i=ticks.begin();i<ticks.end();++i) {
  POINT pt[2];
  char sbuf[32];
  int y=trafo(*i);
  int l=sprintf(sbuf,format,nk,*i);
  pt[0].x=y;
  pt[0].y=f&2?top+ticklen+1:bottom-ticklen-1;
  TextOut(dc,pt[0].x,pt[0].y,sbuf,l);	// Text immer waagerecht
  pt[0].y=f&2?top:bottom-ticklen;
  pt[1].x=y;
  pt[1].y=f&2?top+ticklen:bottom;
  SelectPen(dc,tickPen);
  Polyline(dc,pt,2);
  if (y==owner->rcPlot.left) goto nopaint;
  if (y==owner->rcPlot.right-1) goto nopaint;
  pt[0].y=owner->rcPlot.top;
  pt[1].y=owner->rcPlot.bottom;
  SelectPen(dc,majorGridPen);
  Polyline(dc,pt,2);
// TODO: MinorTicks
nopaint:;
 }
 SetTextColor(dc,nameColor);
 SelectFont(dc,nameFont);
 SetTextAlign(dc,f&2?TA_BOTTOM|TA_CENTER:TA_TOP|TA_CENTER);
 int x=(left+right)>>1;
 int y=f&2?bottom:top;
 TextOut(dc,x,y,name,lstrlen(name));
}

void YScale::paint(HDC dc) const{
 SetTextAlign(dc,f&2?TA_LEFT|TA_BOTTOM:TA_RIGHT|TA_BOTTOM);
 SetTextColor(dc,tickColor);
 std::vector<float>::const_iterator i;
 for(i=ticks.begin();i<ticks.end();++i) {
  POINT pt[2];
  char sbuf[32];
  int y=trafo(*i);
  int l=sprintf(sbuf,format,nk,*i);
  pt[0].x=f&2?left+ticklen+3:right-ticklen-3;
  pt[0].y=y+(szNumb.cy>>1)+1;		// Vertikale Zentrierung (TA_VCENTER gibt's nicht bei Win32)
  TextOut(dc,pt[0].x,pt[0].y,sbuf,l);	// Text immer waagerecht
  pt[0].x=f&2?left:right-ticklen;
  pt[0].y=y;
  pt[1].x=f&2?left+ticklen:right;
  pt[1].y=y;
  SelectPen(dc,tickPen);
  Polyline(dc,pt,2);
  if (y==owner->rcPlot.top) goto nopaint;
  if (y==owner->rcPlot.bottom-1) goto nopaint;
  pt[0].x=owner->rcPlot.left;
  pt[1].x=owner->rcPlot.right;
  SelectPen(dc,majorGridPen);
  Polyline(dc,pt,2);
// TODO: MinorTicks
nopaint:;
 }
 SetTextColor(dc,nameColor);
 SelectFont(dc,nameFont);
 SetTextAlign(dc,TA_TOP|TA_CENTER);
 int x=f&2?right:left;
 int y=(top+bottom)>>1;
 TextOut(dc,x,y,name,lstrlen(name));
}

// Erst mal nur linear
int Scale::trafo(float v) const{
 int hw=height_width()-1;
 int y=int((v-minmax.min)*hw/minmax.diff());
 if (f&4) return (&right)[f&1^1]-1-y;	// nach links/oben wachsend
 return y+(&left)[f&1^1];	// nach rechts/unten wachsend
}

/********
 * Plot *
 ********/
Plot::Plot(XYGraph*o,DynData*d):owner(o),data(d) {
 xScale=&owner->xscales[0];	// Theoretisch: Skale suchen und ggf. erzeugen
 yScale=&owner->yscales[0];
 static const COLORREF DefColor[]={RGB(255,255,255),RGB(0,255,0),RGB(255,255,0),RGB(128,128,255)};
 lineColor=DefColor[owner->plots.size()];
 pen=CreatePen(PS_SOLID,3,lineColor);
 join=2;
}
Plot::~Plot() {
// DeletePen(pen);
}

// Clipping wird vorher gesetzt!
// TODO: Marker, Füllen
void Plot::paint(HDC dc) const{
 int n=data->getLength();
 std::vector<POINT> pt(n);
 for (int i=0;i<n;++i) {
  Pair<float>p;
  data->getDouble(i,p);
  pt[i].x=xScale->trafo(p.x);
  pt[i].y=yScale->trafo(p.y);
 }
 SelectPen(dc,pen);
 Polyline(dc,&pt[0],n);
}

/**********
 * Cursor *
 **********/
Cursor::Cursor(Plot*o):plot(o) {
 pen=CreatePen(PS_DASH,1,RGB(255,0,255));
}
Cursor::~Cursor() {
 DeletePen(pen);
}
// Erst mal nur senkrechter Cursor
void Cursor::paint(HDC dc) const{
 int x=plot->xScale->trafo(value);
 POINT pt[2]={{x,plot->owner->rcPlot.top},{x,plot->owner->rcPlot.bottom}};
 SelectPen(dc,pen);
 Polyline(dc,pt,2);
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded