Quelltext /~heha/ewa/Reluktanzmotor/cdesk-201007.zip/plotxy.h

#pragma once
#include <windows.h>
#include <vector>
#include <assert.h>
#include "gdix.h"
#include "compat.h"
#include <gdiplus.h>

/****************
 * Skalierungen *
 ****************/

// Virtuelle Basisklasse
class Scale{
public:
  virtual void make(Float,Float,Int,Int)=0;	// Zweipunktkalibrierung
  virtual Int scale(Float) const=0;
  virtual Float unscale(Int) const=0;
};

// Nicht skalieren
class NoScale:public Scale{
  virtual void make(Float,Float,Int,Int) {}
  virtual Int scale(Float x) const {return (Int)x;}
  virtual Float unscale(Int X) const {return (Float)X;}
};

// Linear skalieren
class LinScale:public Scale{
  Float f;
  Int O;
  virtual void make(Float,Float,Int,Int);
  virtual Int scale(Float x) const {return Int(x*f)+O;}
  virtual Float unscale(Int X) const {return Float(X-O)/f;}
public:
  LinScale(Float s=1,Int o=0):f(s),O(o) {}
};

// Logarithmisch skalieren
struct LogScale:public Scale{
  Float f;
  Int O;
  bool neg;
  virtual void make(Float,Float,Int,Int);
  virtual Int scale(Float) const;
  virtual Float unscale(Int) const;
public:
  LogScale(Float s=1,Int o=0,bool n=false):f(s),O(o),neg(n) {}
};

/**********************************
 * (Auto-)Range (aus app-plot.js) *
 **********************************/

template<class T> struct Range{
  T a,e;
  Range(T _a=0, T _e=0):a(_a),e(_e) {}
  void init(T _a=0, T _e=0) {a=_a; e=_e;}
  Range&init(const Range<T>r) {return operator=(r);}	// identisch mit Copy-Konstruktor
  bool expand(T _a, T _e) {
    bool r=false;
    if (my::isfinite(_a) && a>_a) {a=_a; r=true;}
    if (my::isfinite(_e) && e<_e) {e=_e; r=true;}
    return r;
  }
  bool expand(T a) {return expand(a,a);}
  bool expand(Range<T>r) {return expand(r.a,r.e);}
  void reset() {init(FINF,-FINF);}
  void prepare() {reset();}
  T delta() const {return e-a;}
  operator T() const {return delta();}	// Typecast zu Elementtyp
  bool regular() const {return delta()>=0;}
  operator bool() const {return regular();}	// Typecast zu bool: Mindestens 1× expand() mit finiten Elementen seit prepare() ausgeführt
  bool hasSpan() const {return delta()>0;}
  bool hasSpan(T aufloes) const {assert(aufloes>0); return delta()>=aufloes;}
  bool operator==(Range<T>r) const{return equals(r.a,r.e);}
  bool operator!=(Range<T>r) const{return!operator==(r);}
  bool equals(T _a, T _e) const{return a==_a && e==_e;}
  bool operator>=(Range<T>r) const {return inside(r.a,r.e);}
  bool inside(T _a, T _e) const{return a<=_a && _e<=e;}	// Enthaltensein oder gleich
  bool inside(T a) const{return inside(a,a);}
  bool operator>(Range<T>r) const {return real_inside(r.a,r.e);}	// Echtes Enthaltensein
  bool real_inside(T _a, T _e) const{return a<_a && _e<e;}
  bool real_inside(T a) const{return real_inside(a,a);}
  bool operator<=(Range<T>r) const {return super(r.a,r.e);}	// Obermenge
  bool super(T _a, T _e) const{return _a<=a && e<=_e;}
  bool operator<(Range<T>r) const {return real_super(r.a,r.e);}	// Echte Obermenge
  bool real_super(T _a, T _e) const{return _a<a && e<_e;}
// Ein (regulärer) Range kann zu einer anderen durchaus weder enthalten noch Obermenge sein.
// Die relationalen Operatoren sind daher nicht komplementär.
};

class Tick;

class DisplayRange:public Range<Float>{
//  Axis*axis;
//  DisplayRange(Axis*ax);// :axis(ax) {
//  if (ax->isDigital()) e=1;	// Voreinstellung digital 0..1
// }
public:
  DisplayRange():Range<Float>(0,10) {}
  void reset(bool log) {init(log?0.1F:0,10);}	// Hier niemals Unendlich-Werte!
  bool init(Float,Float);
  bool init(const Range<Float>&);
  bool operator=(const Range<Float>&r) {return init(r);}
  int genLinTicks(std::vector<Tick>&,DWORD flags,int steps,Float aufloes=0);
  int genLogTicks(std::vector<Tick>&,DWORD flags,int steps);
  void usefulRange(int minimumOfTicks,Float aufloes=0);
};

TCHAR toUnitPrefix(Float&x,Float aufloes=0,WORD allow=0xFFFF);	// LOBYTE = yokto..milli, HIBYTE = kilo..Yotta
Float roundToMultiple(Float x, Float mult);	// mult<0: abrunden, mult>0: aufrunden, mult==0: Nicht erlaubt
Float roundToLog(Float x, Float base);		// base<0: abrunden, base>0: aufrunden, base==0: Nicht erlaubt

/**********************************
 * Getter/Setter für Daten-Arrays *
 **********************************/

struct DATA{
//  size_t size;	// Anzahl der Datenpunkte
//  Float precis;	// Genauigkeit (zur Festlegung des maximalen Zooms)
//  Range<Float> range;	// Der Datenanbieter MUSS den Bereich kennen!
//  const TCHAR*name,*unit;
//  virtual Float get(size_t) const=0;	// Den eigentlichen Datenzugriff muss der Aufrufer in einer abgeleiteten Klasse bereitstellen
 //virtual Float get3(unsigned,Float[3]) const=0;
//  Float operator[](size_t i) const {return get(i);}	// Operatorform
  enum ACTION{
//    query=0,	// Float wird ignoriert
    can_set=1,
//    offset=2,
    can_insert=4,
    can_append=8,
//    ins_after=8,
    can_erase=16,	// Float wird ignoriert
//    get1=32,	// Float ist Float* (Zeiger auf 1 Float)
//    get3=64,	// Float ist Float* (Zeiger auf 3 Float: Mittelwert, Minimum, Maximum)
  };
//  virtual ACTION change(size_t,Float*,ACTION) {return query;}
  virtual int getCaps(int=-1) const {return 0;}	// Was kann die Datenquelle?
  virtual int size() const =0;	// Anzahl der Datenpunkte
  virtual Float precis() const {return 0;}
  virtual void getRange(Range<Float>&r) const {r.reset(); for(int i=0; i<size(); i++) r.expand(get(i));}
  virtual Float get(int i) const =0;
  virtual void set(int i, Float v) {}
  virtual void insert(int i,Float value) {}
  virtual void erase(int i) {}
  virtual Float*lock() {return 0;}
  virtual void unlock() {}
  virtual const TCHAR*name() const {return 0;}
  virtual const TCHAR*unit() const {return 0;}
};	// vielleicht besser nur _ein_ Funktionszeiger?? Mit t0, dt, precis?

struct XYDATA{
  enum M{
    M_getCaps,M_size,M_precisX,M_precisY,M_getRangeX,M_getRangeY,M_get,M_set,M_insert,M_erase,M_lock,M_unlock,M_name,M_unit,
  };
  int (_cdecl*XYDATA::vfunc) (M,...);
  int getCaps(int i=-1) {return vfunc(M_getCaps,i);}
  int size() {return vfunc(M_size);}
  void getRangeX(Range<Float>&r) {vfunc(M_getRangeX,r);}
  void get(int i,Gdiplus::PointF&p) {vfunc(M_get,p);}
  static int _cdecl deffunc(M msg,...);
  XYDATA():vfunc(deffunc) {}
//    va_list va;
//    va_start(va,msg);
//    int r=0;
//    switch (msg) {
//      case M_getRangeX: 
//    }
//    va_end(va);
//    return r;
//  }
};

struct TRACEATTR{
  DATA*xydata[2];
  int itrace;		// Nummer des Traces, -1 = anhängen
  int iplotarea;	// Gewünschter Plotbereich, -1 = neuer Plotbereich
  int ixaxis;		// Gewünschte X-Achse (meist 0 = die einzige)
  int iyaxis;		// Gewünschte Y-Achse (im Plotbereich), -1 = neue Achse
  const TCHAR*name;	// Namen können auch von xydata kommen (?)
  COLORREF tcolor,fcolor,mcolor;	// Farbe für Trace,Polygon und Marker
  TCHAR marker;		// Punktmarkierung
  Int linewidth;	// Linienbreite
  int linestyle;	// Strichelung
  int fillto;		// Flächenfüll-Kode (siehe LabVIEW)
  DWORD flags;
  enum{
    JOINLINEAR=0x01,
    JOINLATE=0x02,
    JOINVP=0x10,	// Dicke der Linie variabel, umgekehrt proportional zur Länge
    DIGITAL=0x40,
    VISIBLE=0x80,
    CHECK_XRANGE=0x100,	// Wenn sich X-Daten geändert haben, oder bei Erstellung von Trace
    CHECK_YRANGE=0x200,	// Wenn sich Y-Daten geändert haben, ...
    EDITABLE=0x10000,	// Was davon (x/y) hängt von xdata/ydata ab, gibt die Möglichkeiten vor u.a. für Hittest
  };
  Font::Data fdmark;	// Schrift für Marker
 // TODO: Trigger?
};

struct AXISLABEL {
  const TCHAR*t;	// Angezeigter Text
  Float p;		// Zugehörige Achsenposition, niemals Inf/NaN
  int importance;	// Wichtigkeit, kleinere Werte führen zum Weglassen des Labels
  AXISLABEL(const TCHAR*_t,Float _p,int i=0):t(_t),p(_p),importance(i) {}
  AXISLABEL() {}
};

class Tick:public AXISLABEL{
public:
  Rect ta;		// Text-Areal (hehaDrawText(DT_SINGLELINE|DT_NOPREFIX)
  Tick(const TCHAR*t,Float p,int i=0):AXISLABEL(t,p,i) {}
  Tick():AXISLABEL() {}	// Der Defaultkonstruktor tut nichts, damit std::vector.resize() keine Arbeit macht
};

enum{
  XY_ADDTRACE=WM_USER+1,	// lParam = TRACEATTR*, inout itrace, iplotarea, ixaxis, iyaxis
  XY_DELTRACE,			// wParam = itrace
  XY_ADDAXIS,
  XY_DELAXIS,
  XY_CHANGETRACE,		// lParam = TRACEATTR*, in itrace, inout iplotarea, ixaxis, iyaxis, Farben usw.
  XY_UPDATETRACE,		// wParam = itrace
};

struct AXISATTR{
  DWORD side;
  int iplotarea;// wird von PlotXY::arrange() zugewiesen == gesetzt; bei X-Achsen oben: erste, unten: letzte PlotArea
  int iaxis;	// Nummer der Achse innerhalb der Plotarea, 0 = innerste, -1 = äußerste, side&2 bestimmt die Rechteck-Seite
  DWORD shrink_ms;	// Millisekunden für Auto-Shrink
  const TCHAR*name,*unit,*fmt;
  Float precis;
  Font::Data fdtick,fdtitle;
};

class Axis;	// Diese 3 Klassen müssen sich gegenseitig sehen
class XAxis;
class YAxis;
class Trace;
class PlotXY;

class Axis:public AXISATTR{
  friend class XAxis;
  friend class YAxis;
  friend class Trace;
  friend class Plotarea;
  friend class PlotXY;
  friend class DisplayRange;
  enum {
// Einzelbits:
    VERTICAL = 1,
    BOTTOMRIGHT = 2,
    REVERSED = 4,
    LOG = 8,
    TIME = 0x10,		// Sonderformatierung
    DIGITAL = 0x11,	// nur Y-Achse: Beide Seiten sowie feste Höhe, immer mit SEPARATE
//  ABSTIME = 0x18,	// Sonderformatierung
    SEPARATE = 0x20,	// nur Y-Achse: Untereinander, nicht nebeneinander anordnen
    VISIBLE = 0x80,
    AUTOEXPAND = 0x100,	// Skale passt sich (sofort) an Messdaten an
    AUTOSHRINK = 0x200,	// Skale passt sich (verzögert) an Messdaten an
    INTEGRALENDS = 0x400,	// Enden auf nächsten Tick ab/aufrunden
    SHOWENDS = 0x800,	// Wahre Skalenenden anzeigen (Vorrang vor Autoticks)
    ZEROCENTER = 0x1000,	// Null in der Mitte (nicht mit LOG oder ABSTIME vereinbar)
    COMMONZERO = 0x2000,	// Gemeinsame Nulllinie mit anderen Skalen derselben Plotarea (knifflig ohne ZEROCENTER!!)
    INCLUDEZERO = 0x4000,	// Null ist stets in der Skale (nie bei LOG)
    INVAL_RANGE = 0x10000,
    INVAL_TICKS = 0x20000,
  // Kombinationen:
    BOTTOM=2,	// X-Achse unten von links nach rechts
    LEFT=5,	// Y-Achse links von unten nach oben
    RIGHT=7,	// Y-Achse rechts von unten nach oben
  };
  int shrink_countdown;
//  BYTE ftick_korr;	// schriftgrößenabhängiger Korrekturwert von TA_BASELINE auf (fehlendes) TA_MIDDLE (nur Y-Achsen)
  Rect rect;		// Rechteck passend zum Plotbereich
  Margin padding;	// Die Skale ist im Rechteck (ein wenig) eingerückt je nach Linienbreite und Markergröße
// TCHAR*titl,*fmt;
//  std::vector<Label*>labels;	// zu zeichnende Labels
  typedef std::vector<Tick> ticks_t;
  ticks_t ticks;
  typedef std::vector<AXISLABEL> labels_t;
  labels_t userlabels;	// Vorgabe-Labels
  typedef std::vector<Trace*> traces_t;
  traces_t traces;	// Zugeordnete Kurvenzüge
  bool anyTrace(bool (Trace::*testfunc)()const,bool runwhile=false) const;
  bool allTraces(bool (Trace::*testfunc)()const) const {return anyTrace(testfunc,true);}
  Color cfore,ctitl;	// Hintergrundfarbe, Vordergrundfarbe, Titelfarbe
  Pen ptick;			// Stift für Skalenstriche
  Font ftick,ftitl;		// Schrift für Achse und Achsentitel
  PlotXY*plot;
  Scale*scale;
  char scaleInf(Float v,Int&V) const;
  enum {
    CINF=127,CNAN=-128		// Ergebnis von scaleInf: Bewertung des Float-Arguments
  };
  Range<Float> dataRange;	// Bereich für ALLE (sichtbaren) Traces, exklusive ±Inf und NaN
  DisplayRange dispRange;	// ggf. durch Zoom verändert, sonst dataRange umfassend
  Axis(PlotXY*,const AXISATTR&);
  void buildLabels();
//  virtual void paint()=0;
//  virtual int needSpace(bool/*,int*/)=0;
  bool updateScaling(bool force);
  void checkRange(bool force=false);
  void distributeTicks();
  int FloatStr(Float,TCHAR*,int) const;
  TCHAR*FloatStr(Float) const;
  bool visible() const {return !!(side&VISIBLE);}
  void setVisible(bool v) {if (v) side|=VISIBLE; else side&=~VISIBLE;}
  bool isDigital() const {return (side&DIGITAL)==DIGITAL;}
  bool isTime() const {return (side&DIGITAL)==TIME;}
  bool isLog() const {return !!(side&LOG);}
  const TCHAR*getName() const {return name&&*name?name:isTime()?TEXT("time"):TEXT("value");}
  int getTitle(TCHAR*buf,int buflen) const;
  const TCHAR*getUnit() const {return unit?unit:TEXT("");}
  int axistext(TCHAR*buf,int blen) const;
  TCHAR*axistext() const;
  int getTickText(const Tick&,TCHAR*,int) const;
  const TCHAR*autotickformat() const {return fmt&&*fmt?fmt:TEXT("%g");}
//  void checkNeed();
  int textWidth(const TCHAR*, int) const;
  struct Metrics{
    char ticklen;
    char tickwidth;
    char gap;		// Platz zwischen Text und Strich, zwischen Text und oberen Rand
    char fontheight;
    char lastSpaceNeeded;
    char steppx;	// Maximale Pixelzahl für minimale Auflösung: 8 (verhindert gefüllte Flächen für mickrige Rauschamplituden)
    char textycorrect;
    Metrics():ticklen(4),tickwidth(1),gap(1),fontheight(14),lastSpaceNeeded(1),steppx(4),textycorrect(4) {}
  }metrics;
  void onTimer(int ms) {if (shrink_countdown>0 && (shrink_countdown-=ms)<=0) checkRange(true);}
  bool findEditableLabel(const POINT&pt) const;
  bool getTickRect(int i,Rect&rc) const;
};

class XAxis:public Axis{
  void paint();
  int needSpace(Margin&);
  friend class Plotarea;
  friend class PlotXY;
  XAxis(PlotXY*p,const AXISATTR&a):Axis(p,a) {}
};

class YAxis:public Axis{
  void paint();
  int needSpace(Margin&);
  friend class Plotarea;
  friend class PlotXY;
  YAxis(PlotXY*p,const AXISATTR&a):Axis(p,a) {}
};

class Trace:public TRACEATTR{
  friend class Axis;
  friend class PlotXY;
  union{
    Axis*xyaxis[2];
    struct{
     XAxis*xaxis;
     YAxis*yaxis;	// yaxis->plotarea bestimmt den Zeichenbereich
    };
  };
  PlotXY*plot;
  Trace*tr_fillto;	// nur bei fillto>=0
  typedef std::vector<POINT> points_t;
  points_t fill,line,mark;
  typedef std::vector<Trace*> traces_t;
  traces_t sub;	// Sub-Traces (für Digital- oder auch ein Bündel Analogsignale)
  Trace*sup;		// Übergeordneter (Sammel-)Trace:
  Pen pline;
  Brush bfill,bline;	// bline bei JOINVP = Verbindung per Polygon
  Font fmark;		// Schriftart für Marker-Symbole
  Trace(PlotXY*,TRACEATTR&);
  int textWidth(const TCHAR*,int) const;
  void paintPoly();
  void paintLine();
  void paintMark();
  void calcpolys();
  bool visible() const {return !!(flags&VISIBLE);}
  void setVisible(bool v) {if (v) flags|=VISIBLE; else flags&=~VISIBLE;}
  int name(TCHAR*,int) const;
  TCHAR*name() const;
  void name(TCHAR*,int);
  int defaultName(TCHAR*,int) const;
  Color penColor() const;
  void penColor(Color);
  Color penDefaultColor() const;
  int penWidth() const;
  void penWidth(int);
  int penDash() const;
  void penDash(int);
  int penJoin() const;
  void penJoin(int);
  int penJoinOptions(TCHAR*,int) const;
  unsigned penMarker() const;
  void penMarker(unsigned);
  Color fillColor() const;
  void fillColor(Color);
  int fillTo() const;
  void fillTo(int);
  int fillToOptions(TCHAR*,int) const;
  DATA*getData(bool y) const{return xydata[y];}
  Axis*getAxis(bool y) const{return xyaxis[y];}
  void setAxis(bool y, Axis*ax) {xyaxis[y]=ax;}
  void getRange(bool y,Range<Float>&) const;
  bool editable() const {return !!(flags&EDITABLE);}
  HMENU popup();
//  void handleMeasureItem(WPARAM,LPARAM);
//  void handleDrawItem(WPARAM,LPARAM);
  ~Trace();		// check arrange
};

class Plotarea{
  friend class PlotXY;
  Rect rect;		// Zeichenfläche
  Margin margin;	// Platz für Achsen
  typedef std::vector<XAxis*> xaxes_t;
  typedef std::vector<YAxis*> yaxes_t;
  typedef std::vector<Axis*> axes_t;
  xaxes_t xaxes;	// Top: erst mal nur bei oberster Plotarea; Bottom: erst mal nur bei unterster Plotarea
  yaxes_t yaxes;	// linksseitig von rechts nach links, rechtsseitig von links nach rechts
  axes_t&xyaxes(bool y) {return y?*(axes_t*)&yaxes:*(axes_t*)&xaxes;}
  bool fixedheight;	// bei Digitalskale: Maus verschiebt analoge Nachbarn
  bool valid;		// Fertig berechnet
  bool visible;		// Mindestens 1 sichtbarer Plot
  int ydiv;		// Mit Maus verschiebbare Unterteilung darunter = rect.bottom+margin.bottom (Hilfsgröße)
  Axis*insertAxis(PlotXY*,AXISATTR&);	// entnimmt side&1, entnimmt+korrigiert iaxis, liefert XAxis* oder YAxis*
  Axis*eraseAxis(const AXISATTR&);	// entnimmt side&1, iaxis, liefert XAxis* oder YAxis*
  static void fixiaxis(axes_t&,int,int,int=0);	// ändert iaxis und ggf. iplotarea
  bool findEditableLabel(const POINT&pt) const;
public:
  Plotarea():fixedheight(false),valid(false),visible(true) {}
};

class PlotXY{
  friend class Axis;
  friend class XAxis;
  friend class YAxis;
  friend class Trace;
  HWND wnd;
  HDC dc;
  Brush bBack,bArea;
  typedef std::vector<Plotarea> areas_t;
  areas_t areas;
  typedef std::vector<Trace*> traces_t;
  traces_t traces;
//  std::vector<XAxis*>xaxes;
//  std::vector<YAxis*>yaxes;
//  std::vector<Axis*>&axes(bool y) {return y?*(std::vector<Axis*>*)&yaxes:*(std::vector<Axis*>*)&xaxes;}
  PlotXY(HWND w):wnd(w),dc(0),dirty(0) {}
  void paint(Rect* =0);
  static LRESULT CALLBACK wndproc(HWND,UINT,WPARAM,LPARAM);
  LRESULT wndproc(UINT,WPARAM,LPARAM);
  Axis*insertAxis(AXISATTR&);	// entnimmt side&1, entnimmt+korrigiert iplotarea, iaxis, liefert XAxis* oder YAxis*
  Axis*eraseAxis(AXISATTR&);	// entnimmt iplotarea, side&1, iaxis, liefert XAxis* oder YAxis*
  Trace*insertTrace(TRACEATTR&);
  Trace*eraseTrace(TRACEATTR&);
  Plotarea&insertPlotarea(int);
  bool erasePlotarea(int);
  void fixiplotarea(int,int);

  Axis*assignAxis(AXISATTR&);	// findet oder erstellt eine passende Achse, ggf. unter Erstellung einer Plotarea
  int textWidth(const TCHAR*,int=-1) const;
  Metric textMetric() const;
  void arrange();
  void update(unsigned what,int plotarea=-1);
  enum{
    GAP=3,
    MOUSECAPTURE=20,	// Pixel Abstand zur Linie bzw. deren Endpunkt
  };
  static Pen nullpen;
  unsigned dirty;	// Bit 7: alles (Plotbereiche: Anzahl, Sichtbarkeit usw. geändert)
		// Bit 6: Anordnung der Plotbereiche (Mausverschiebung)
		// Bit 5: Daten inklusive Bereich
		// Bit 4: Daten bei gleichem Bereich
  struct Mousetrack{
    int itrace;		// Nur EDITABLE
    int segment;	// Integer-Anteil: Polyliniensegment
    Float where;	// Gebrochener Anteil: Wo auf dem Segment, 0..1 = dazwischen, sonst über die Enden hinaus
    Float distance;	// Vorgefundener kleinster Abstand
    bool tracking;	// wenn nahe genug ODER Mauscapture
    Mousetrack():tracking(false) {}
  }mousetrack;
  typedef POINT Point;	// Bei GdiPlus: PointF
  bool findEditableLabel(const POINT&) const;
  int findArea(const POINT&pt,bool*overDivider=0) const;	// der Rückgabewert kann durchaus -1 oder ==areas.size() sein!
  bool isDividerVariable(int) const;
  static Float distance(const Point line[2],const POINT&pt,Float&where);	// Abstand quer zur Linie oder zu den Endpunkten
  bool findTrace(const POINT&pt);	// Polylinie finden, die am nächsten kommt
public:
  static BOOL init();
};
Vorgefundene Kodierung: UTF-80