#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();
};
Detected encoding: UTF-8 | 0
|