Source file: /~heha/ewa/Ofen/prozess.zip/msvc/o1/xy.cpp

#include "xy.h"
#include <stdio.h>
#include <math.h>
#include "wutils.h"

/*********
 * Skale *
 *********/
Scale::Scale(XY*_parent,UINT _side,char*_name):
 O(0),F(1),preci(0),
 parent(_parent){
 side=_side;
 name=_name;
 format="%.2f";
 range.init(0,10);
 font[0]=new Gdiplus::Font(Gdiplus::FontFamily::GenericSansSerif(),10);
 font[1]=new Gdiplus::Font(Gdiplus::FontFamily::GenericSansSerif(),10,Gdiplus::FontStyleBold);
 brush[0]=new Gdiplus::SolidBrush(color[0]);
 brush[1]=new Gdiplus::SolidBrush(color[1]);
}

Scale::~Scale() {
 delete brush[1];
 delete brush[0];
 delete font[1];
 delete font[0];
}

static Gdiplus::Status MeasureStringU(Gdiplus::Graphics&g, const char*s,const Gdiplus::Font*fnt,const Gdiplus::PointF&org,const Gdiplus::StringFormat*sf,Gdiplus::RectF&box) {
 wchar_t buf[64];
 int l=MultiByteToWideChar(CP_UTF8,0,s,-1,buf,elemof(buf))-1;
 return g.MeasureString(buf,l,fnt,org,sf,&box);
}
static Gdiplus::Status circle(Gdiplus::Graphics&g,const Gdiplus::Pen*pen,const Gdiplus::PointF&center,float dia=10.0) {
 return g.DrawEllipse(pen,center.X-dia/2,center.Y-dia/2,dia,dia);
}
static Gdiplus::Status DrawStringU(Gdiplus::Graphics&g,const char*s,const Gdiplus::Font*fnt,const Gdiplus::PointF&org,const Gdiplus::StringFormat*sf,const Gdiplus::Brush*brush) {
 wchar_t buf[64];
 int l=MultiByteToWideChar(CP_UTF8,0,s,-1,buf,elemof(buf))-1;
// DEBUG
// Gdiplus::Pen pen(Gdiplus::Color::Aqua);
// circle(g,&pen,org);
 return g.DrawString(buf,l,fnt,org,sf,brush);
}

void Scale::tickstringformat(Gdiplus::StringFormat&sf) {
 sf.SetLineAlignment(side&1?Gdiplus::StringAlignmentCenter:side&2?Gdiplus::StringAlignmentNear:Gdiplus::StringAlignmentFar);
 sf.SetAlignment(side&1?side&2?Gdiplus::StringAlignmentNear:Gdiplus::StringAlignmentFar:Gdiplus::StringAlignmentCenter);
}
void Scale::tickposition(float v, Gdiplus::PointF p[2]) {
 if (side&1) {
  p[0].X=float(rc[2-(side&2)]); p[0].Y=scale(v);
  p[1].X=p[0].X+(side&2?ticklen:-ticklen); p[1].Y=p[0].Y;
 }else{
  p[0].Y=float(rc[3-(side&2)]); p[0].X=scale(v);
  p[1].Y=p[0].Y+(side&2?ticklen:-ticklen); p[1].X=p[0].X;
 }
}
void Scale::namestringformat(Gdiplus::StringFormat&sf) {
 if (side&1) {
//  sf.SetFormatFlags(Gdiplus::StringFormatFlagsDirectionVertical);
  sf.SetAlignment(Gdiplus::StringAlignmentCenter);
  sf.SetLineAlignment(Gdiplus::StringAlignmentNear);
 }else{
  sf.SetAlignment(Gdiplus::StringAlignmentCenter);
  sf.SetLineAlignment(side&2?Gdiplus::StringAlignmentFar:Gdiplus::StringAlignmentNear);
 }
}
void Scale::nameposition(Gdiplus::Graphics&g,Gdiplus::PointF&p) {
 if (side&1) {
  p.Y=float(rc[side&2]);	// an der Außenkante
  p.X=-(rc.top+rc.bottom)*0.5F;	// in der Mitte (X/Y vertauscht wegen Textrotation)
  g.RotateTransform(side&2?90.0F:-90.0F);
  if (side&2) {p.X=-p.X; p.Y=-p.Y;}
 }else{
  p.X=(rc.left+rc.right)*0.5F;
  p.Y=float(rc[1+(side&2)]);	// bottom/top
 }
}

void Scale::onPaint(Gdiplus::Graphics&g) {
 using namespace Gdiplus;
 StringFormat sf; tickstringformat(sf);
 Pen pen(color[0]);
 for each(Label lbl in ticks) {
  PointF p[2]; tickposition(lbl.position,p);
  g.DrawLines(&pen,p,2);
  DrawStringU(g,lbl.string,font[0],p[1],&sf,brush[0]);
 }
 if (name) {
  namestringformat(sf);
  PointF p; nameposition(g,p);
  DrawStringU(g,name,font[1],p,&sf,brush[1]);
  g.ResetTransform();
 }
}
// Der Unterschied zwischen „DirectionVertical“ und „Rotation“ fällt erst bei Chinesisch/Japanisch auf.

void Scale::onSize(Gdiplus::Graphics&g) {
 calcFO();
 distributeTicks(g);
}

static char getlog10(float v) {return (char)::floorf(::log10f(v));}
static float getpow10(char c) {return ::powf(10,c);}
static char normal10(float v, float&f) {
 char nk=v?-getlog10(fabsf(v)):0;	// Liefert Nachkommastellen
 f=getpow10(nk);		// Liefert Faktor (10^nk) mit dem v multipliziert wurde
 return nk;
}
char round125(float&v,bool up) {	// Schrittweite auf ein Vielfaches von 1, 2 oder 5 auf- oder abrunden
 float f;
 char nk=normal10(v,f);
 v*=f;				// Liefert Wert zwischen 1 und <10
 bool neg=v<0; if (neg) {v=-v; up=!up;}
 if (up) {
  if (v>5) {v=10;nk--;}
  else if (v>2) v=5;
  else if (v>1) v=2;
 }else{
  if (v>=5) v=5;
  else if (v>=2) v=2;
  else v=1;
 }
 v/=f;
 if (neg) v=-v;
 return nk;		// liefert Nachkommastellen für printf
}
static float roundToNext(float v,float step) {
 bool neg=step<0; if (neg) {v=-v; step=-step;}	// falls Schrittweite negativ
 v/=step;		// Schritte
 v=::ceilf(v);		// Ganzzahlige Schritte
 v*=step;
 return neg?-v:v;
}
float round2(float v,float l,bool up) {
 float f;
 normal10(l,f);
 f*=10;			// Länge zweistellig (10..99)
 v*=f;
 v=(up?ceilf:floorf)(v);	// gröbere Stückelung wäre hier zu tun
 v/=f;
 return v;
}

void Scale::distributeTicks(Gdiplus::Graphics&g) {
 float step=(range.e-range.a)/numberExtent.Height;	// Y, linear, analog: Nötige Schrittweite
 setmax(step,preci);
 char nk=round125(step,step>=0);
 float first=roundToNext(range.a,step);
 ticks=labels;		// Kopier-Aktion
 addTick(g,range.a,-1);	// Enden der Skale immer beschriften (mit voller Präzision??)
 addTick(g,range.e,-1);
 if (nk<0) nk=0;
 for (;range.inside(first);first+=step) addTick(g,first,nk);
}
bool Scale::addTick(Gdiplus::Graphics&g,float v,char nk) {
 char s[32];
 if (nk<0) nk=0;
 int l;
 if (strchr(format,'*')) l=sprintf(s,format,nk,v);
 else l=sprintf(s,format,v);
 Label lbl;
 Gdiplus::StringFormat sf; tickstringformat(sf);
 Gdiplus::PointF pt[2]; tickposition(v,pt);
 MeasureStringU(g,s,font[0],pt[1],&sf,lbl.box);
 for each (Label label in ticks) {
  if (label.box.IntersectsWith(lbl.box)) return false;
 }
 lbl.string=newcopy(s,l+1);
 lbl.position=v;
 ticks.push_back(lbl);
 return true;
}

void Scale::needSpace(Gdiplus::Graphics&g,NEED&need) {
// TODO: Iteriere über alle Labels, füge Achsenbezeichnung hinzu
 Gdiplus::PointF pt;
 Gdiplus::RectF bbox;
 g.MeasureString(L"0",1,font[0],pt,&bbox);
 numberExtent.Width=bbox.Width;
 numberExtent.Height=bbox.Height;
 need.l=need.r=(int)bbox.Width>>1;	// Hälfte
 need.h=(int)bbox.Height;
 if (name && *name) need.h<<=1;	// 2×
 need.h+=ticklen;
}


/***********
 * XY-Graf *
 ***********/

XY::XY(HWND Wnd):wnd(Wnd) {
 hPlotLegend=0;
 hScaleLegend=0;
 hCursorLegend=0;
 hMiniature=0;
 Gdiplus::GdiplusStartupInput gpsi;
 Gdiplus::GdiplusStartup(&gdiplusToken,&gpsi,0);
 xscales.push_back(new Scale(this,::BOTTOMX,"Zeit"));
 yscales.push_back(new Scale(this,::LEFTY,"Wert"));
}

XY::~XY() {
 for each(Scale*xs in xscales) delete xs;
 for each(Scale*ys in yscales) delete ys;
 Gdiplus::GdiplusShutdown(gdiplusToken);
}

void XY::onSize(int cx, int cy) {
 Gdiplus::Graphics g(wnd);
 SetRect(&rcPlot,0,0,cx,cy);
// Pass 1: Größe der Plot-Ränder bestimmen
 Rect extra;
 SetRectEmpty(&extra);
 std::vector<int> heights(xscales.size()+yscales.size());	// zum Speichern der Höhen
 int i=0;
 for each(Scale*xs in xscales) {
  Scale::NEED need;
  xs->needSpace(g,need);	// Größe ermitteln
  heights[i++]=need.h;		// Höhe/Breite retten für später
  if (xs->side&2) rcPlot.bottom-=need.h; else rcPlot.top+=need.h;
  setmax(extra.left,need.l);
  setmax(extra.right,need.r);
 }
 for each(Scale*ys in yscales) {
  Scale::NEED need;
  ys->needSpace(g,need);
  heights[i++]=need.h;
  if (ys->side&2) rcPlot.right-=need.h; else rcPlot.left+=need.h;
  setmax(extra.top,need.l);
  setmax(extra.bottom,need.r);
 }
 setmax(rcPlot.left,extra.left);
 setmax(rcPlot.top,extra.top);
 setmin(rcPlot.right,cx-extra.right);
 setmin(rcPlot.bottom,cy-extra.bottom);
 
// Pass 2: Skalen platzieren
 i=0;
 Rect grow;		// Ein Rechteck was von innen nach außen wachsen wird
 CopyRect(&grow,&rcPlot);
 for each(Scale*xs in xscales) {
  int h=heights[i++];
  if (xs->side&2) {	// unten
   SetRect(&xs->rc,rcPlot.left,grow.bottom,rcPlot.right,grow.bottom+h);
   grow.bottom+=h;
  }else{		// oben
   SetRect(&xs->rc,rcPlot.left,grow.top-h,rcPlot.right,grow.top);
   grow.top-=h;
  }
  xs->onSize(g);	// nach Größenänderung Skalierungsfaktoren anpassen
 }
 for each(Scale*ys in yscales) {
  int w=heights[i++];
  if (ys->side&2) {	// rechts
   SetRect(&ys->rc,grow.right,rcPlot.top,grow.right+w,rcPlot.bottom);
   grow.right+=w;
  }else{		// links
   SetRect(&ys->rc,grow.left-w,rcPlot.top,grow.left,rcPlot.bottom);
   grow.left-=w;
  }
  ys->onSize(g);
 }
// Pass 3: Plots neu zeichnen
 for each(Plot*pl in plots) pl->onSize(g);
 InvalidateRect(wnd,NULL,TRUE);
}

void XY::onPaint(Gdiplus::Graphics&g) {
 Rect rcClient;
 GetClientRect(wnd,&rcClient);
 g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
 g.DrawLine(new Gdiplus::Pen(Gdiplus::Color()),rcClient.left,rcClient.top,rcClient.right,rcClient.bottom);
 Gdiplus::Pen pen(Gdiplus::Color(255,0,0,255),3);
 g.DrawLine(&pen,rcPlot.left,rcPlot.bottom,rcPlot.right,rcPlot.top);
#if 0
 {using namespace Gdiplus;
 static const wchar_t vText[]= L"Vertikaler Text: الباب 你好";
 Font*font = new Font(FontFamily::GenericSansSerif(),14);
 PointF pt(40,20);
 StringFormat sf;
 sf.SetLineAlignment(StringAlignmentCenter);
 Brush*brush = new SolidBrush(Color(255, 0, 0, 255));
 Pen*pen=new Pen(Color(0,255,0));
 RectF bbox;
 g.MeasureString(vText,-1,font,pt,&sf,&bbox);
 sf.SetFormatFlags(StringFormatFlagsDirectionVertical);	// Nur für Chinesisch relevant!
 circle(g,pen,pt,bbox.Height);
 g.DrawString(vText,-1,font,pt,&sf,brush);
 sf.SetFormatFlags(0);
 g.RotateTransform(90);
 g.MeasureString(vText,-1,font,pt,&sf,&bbox);
 pt.X+=bbox.Height;
 PointF pt2=pt;
 g.TransformPoints(CoordinateSpaceWorld,CoordinateSpacePage,&pt2,1);
 circle(g,pen,pt2,bbox.Height);
 g.DrawString(L"Von links lesbar: الباب 你好",-1,font,pt2,&sf,brush);		// von links lesbar (auch das Chinesisch)
 g.RotateTransform(180);
 pt.X+=bbox.Height;
 pt2=pt;
 g.TransformPoints(CoordinateSpaceWorld,CoordinateSpacePage,&pt2,1);
 sf.SetAlignment(StringAlignmentFar);
 circle(g,pen,pt2,bbox.Height);
 g.DrawString(L"Von rechts lesbar: الباب 你好",-1,font,pt2,&sf,brush);		// von rechts lesbar
 delete font;
 delete brush;
 delete pen;
 }
#else
 Gdiplus::Font fnt(Gdiplus::GenericSansSerifFontFamily,10);
 Gdiplus::PointF org(20,20);
 Gdiplus::SolidBrush br(Gdiplus::Color(128,0,255));
 g.DrawString(L"Hier",-1,&fnt,org,&br);
#endif
// MoveToEx(ps.hdc,rcPlot.left,rcPlot.bottom,0);
// LineTo(ps.hdc,rcPlot.right,rcPlot.top);
 for each(Scale*xs in xscales) xs->onPaint(g);	// alle X-Skalen
// for(auto ys:yscales) ys->onPaint(ps);	//MSVC2017
 for each (Scale*ys in yscales) ys->onPaint(g);	// alle Y-Skalen
 for each (Plot*pl in plots) pl->onPaint(g);
}

Scale* XY::getXScale(char&idx) {
 if ((byte)idx>=xscales.size()) {
  idx=xscales.size();
  xscales.push_back(new Scale(this,idx&1?TOPX:BOTTOMX));	// abwechselnd unten und oben dazu
 }
 return xscales[idx];
}

Scale* XY::getYScale(char&idx) {
 if ((byte)idx>=yscales.size()) {
  idx=yscales.size();
  yscales.push_back(new Scale(this,idx&1?RIGHTY:LEFTY));	// abwechselnd links und rechts dazu
 }
 return yscales[idx];
}

Gdiplus::Color XY::getPlotColor(char&idx) {
 if ((byte)idx>plots.size()) idx=plots.size();
 static const Gdiplus::Color defColors[]={Gdiplus::Color::Green,Gdiplus::Color::Blue,Gdiplus::Color::Red,Gdiplus::Color::Brown};
 return defColors[idx%elemof(defColors)];
}

void XY::repositionAll() {
 Rect r;
 ::GetClientRect(wnd,&r);
 onSize(r.right,r.bottom);
 InvalidateRect(wnd,0,TRUE);
}

void XY::beginChange(BYTE) {needReposition=false;}

void XY::endChange(BYTE what) {
 if (needReposition) {
  KillTimer(wnd,123);
  timer_running=false;
  repositionAll();
 }else{
  if (!timer_running) {
   SetTimer(wnd,123,1000,0);
   timer_running=true;
  }
  InvalidateRect(wnd,&rcPlot,TRUE);
 }
}

// Nachgucken ob Skalen schrumpfen dürfen
void XY::shrinkScales() {
 KillTimer(wnd,123);
 timer_running=false;
 bool needReposition=false;
 Range needrange;
 for each (Scale*xs in xscales) {
  for each (Plot*pl in xs->plots) {
   needrange.expand(pl->rangeX);
  }
  if (xs->range.set2(needrange,Range::SHRINK)&Range::SHRINK) needReposition=true;
 }
 needrange.reset();
 for each (Scale*ys in yscales) {
  for each (Plot*pl in ys->plots) {
   needrange.expand(pl->rangeY);
  }
  if (ys->range.set2(needrange,Range::SHRINK)&Range::SHRINK) needReposition=true;
 }
 if (needReposition) repositionAll();
}

LRESULT XY::wndproc(UINT msg, WPARAM wParam, LPARAM lParam) {
 switch (msg) {
  case WM_SIZE: onSize(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)); return 0;
  case WM_PAINT: {
   PAINTSTRUCT ps;
   Gdiplus::Graphics g(BeginPaint(wnd,&ps));
   onPaint(g); 
   EndPaint(wnd,&ps);
  }return 0;
  case WM_PRINTCLIENT: {
   Gdiplus::Graphics g((HDC)wParam);
   onPaint(g);
  }return 0;
  case WM_TIMER: if (wParam==123) shrinkScales(); break;
  case PM_SETPLOT: {
   Plot*plot=new Plot(this,*(PLOT*)lParam);
   char idx=(char)wParam;
   if ((byte)idx<plots.size()) {delete plots[idx]; plots[idx]=plot;}
   else {idx=plots.size(); plots.push_back(plot);}
   return idx;
  }
  case PM_ADDSAMPLES: {
   FLEXDATA data={(void*)lParam};
   beginChange(PLOTDATA);
   for each (Plot*pl in plots) pl->putdata(data);
   endChange(PLOTDATA);
  }return 0;
//  case PM_ADDVALUE:
//  case PM_ADDVALUES:
//  case PM_ADDPAIR:
//  case PM_ADDPAIRS:
//  case PM_ADDTUPLE:
//  case PM_ADDSIGNAL:
//  case PM_ADDSIGNALS:
 }
 return DefWindowProc(wnd,msg,wParam,lParam);
}

LRESULT CALLBACK XY::wndproc(HWND Wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
 XY*self=(XY*)GetWindowLong(Wnd,0);
 if (!self) {
  self=new XY(Wnd);
  SetWindowLong(Wnd,0,(LONG_PTR)self);
 }
 LRESULT ret=self->wndproc(msg,wParam,lParam);
 if (msg==WM_NCDESTROY) delete self;
 return ret;
}

BOOL XY::init() {
 static const WNDCLASS wc={
  CS_DBLCLKS|CS_HREDRAW|CS_VREDRAW|CS_PARENTDC,
  wndproc,
  0,
  sizeof(XY*),
  0,
  0,
  0,
  HBRUSH(COLOR_WINDOW+1),
  0,TEXT("plot")
 };
 return RegisterClass(&wc);
}

BOOL PlotInit() {return XY::init();}
Detected encoding: UTF-80