#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);
}
Vorgefundene Kodierung: UTF-8 | 0
|