Görbe ábrázolás a QCustomPlot widgettel Gyakran előforduló feladat mérési és számítási eredmények ábrázolása 2D-ben. A Qt vel ez a feladat is megoldható, de elég sok munkával. Szerencsére ezt a munkát valaki már elvégezte és az eredmény egy sokoldalú és mégis ingyenes widget a qcustomplot lett. A qcustomplot widget sokkal többre képes, mint egy egyszerű görbe megjelenítése (pl hibahatárokat is meg tud jeleníteni), de mi csak a legegyszerűbbel egy y(x) függvény megjelenítésével foglalkozunk. A projekt aktuális forrása a tanszéki szerverről letölthető. Az ingyenes 7z programmal 1 archivált fájl a Programozás 3 alatt a 2016/17 I. félév almappában található. A QCustomplot projekthez adásának lépései: 1. A letöltött GrafikaQTvel.7z-beli fájlok között már ott vannak a QCustomplot 1.3.2 verziójának forrásfájljai 2 : qcustomplot.cpp és qcustomplot.h is.. Adjuk hozzá ezeket a projekthez! 2. Opcionálisan hozzáadhatjuk a QCustomPlot súgó fájlját is a súgóhoz: Töltsük le a qcustomplot.qch súgó fájlt 3! Nyissuk meg a QTCreator/QTDesigner súgóját QTCreatorban a baloldalon a Help-et kiválasztva! 1 A tanszéki szerverről letölthető a 7z 64 bites 16.04-es verziója. Az aktuális verzió Windows alá itt található: http://www.7-zip.org/download.html. Ha Linux alatt nincs feltelepítve a p7zip nevű csomagban található parancssori 7z program, akkor az valószínűleg letölthető a disztribúcióhoz. Ha mégsem, akkor a fenti letöltő oldalon an egy link a Linux alatt működő p7zip nevű csomagra, ami tartalmazza a 7z programot is.. 2 Az aktuális verzió a http://www.qcustomplot.com/ weboldalról tölthető le, nekünk most elegendő a két forrásfájl. 3 Ha a teljes qcustomplot rendszert letöltöttük, akkor már a súgó fájl is rendelkezésre áll, egyébként a tanszéki szerveren is megtalálható.
QTDesignerben az F1 gombbal (Windows alatt a fájlnév assistant.exe) kapjuk meg a súgót: Qt Creatorban válasszuk ki a Tools/Options menüt, QTDesignerben az Edit/Preferences menüt! Mindkét esetben a Documentation fülre kattintva az alábbi dialógus ablak nyílik meg:
Ide adhatjuk hozzá a letöltött súgó fájlt. 3. Ezután kapcsoljunk át a jobb oldali stckpages 1. lapjára 4. Nevezzük át pnlgraph-ra! Adjunk ehhez hozzá egy widgetet, amit nevezzük át plotter-re. A pnlgraph elrendezését állítsuk be pl. vízszintesre, hogy a plotter teljes lapot kitöltse. 4. Léptessük elő (Promote to ) a graph widgetet QCustomPlot-ra: 4 Ld. a programot leíró előző dokumentumban, ahol az első, 0 indexű lapot adtuk hozzá. Közben a GUI változások miatt a widget neve pnlright-ról stckpages-re változott.
5. Állítsuk vissza az stckpages indexét 0-ra és az scratools-nál kattintsunk a Rajzolás fejlécre, majd mentsük el a változásokat! 6. Adjuk hozzá a #include "qcustomplot.h" sort a grafikaqtvel.cpp fájlhoz! 7. Módosítsuk a grafikaqtvel.cpp fájlban az on_tbxmenu_currentchanged(int index) függvényt! index == 1 esetén kapcsoljon át az 1-es lapra! 8. QT Creatorral fordítás esetén módosítsuk a projekt fájlban a QT += sort: -ra! QT += widgets gui printsupport Fordítsuk le és futtassuk a programot! Amikor a grafika lapra átkapcsolunk megjelenik a jobboldalon a QCustomPlot alap grafikonja.
Egy X-Y függvény megjelenítése a QCustomPlot Widget-tel A QCustomPlot widget alapból egy négy tengellyel (xaxis, yaxis, xaxis2 és yaxis2) vel határolt téglalapban jeleníti meg a görbéket. A tengelyek típusa QCPAxis 5. Alapból csak a bal oldali (yaxis) és alsó (xaxis) tengelyek láthatóak. A függvénygörbe látható részét a tengelyek aktuális adattartományai (QCPAxis::range()) határozzák meg. A tengelyek beosztásai automatikusak, vagy általunk megadottak lehetnek. Ha az x és y párok a rendelkezésünkre állnak és plotter egy QCustomplot objektum, akkor a következő lépésekkel tudjuk megjeleníteni az y(x) görbét: 1. Adjunk új függvénygörbét hozzá: QCPGraph *graph = plotter->addgraph(); A hozzáadott görbének az alsó részen lesz az x tengelye, a bal oldalán az y tengelye 2. Rakjuk be az X és Y adatpárokat két double vektorba (QVector<double> x és QVector<double> y). 3. Adjuk hozzá az adatokat a görbéhez: plotter->setdata(x, y); 4. Állítsuk be az x és y tartományokat: plotter->xaxis->setrange(xmin, xmax); plotter->yaxis->setrange(ymin, ymax); 5. Rajzoltassuk ki a görbét: plotter->replot(); Nincs szükség a görbe újrarajzoltatására, ha a widget méretét megváltoztatjuk Fájl adatok beolvasása és megjelenítése Dialógus a szöveges és bináris fájlok beolvasására A fájlok kiválasztására a QFileDialog osztály sztatikus QFileDialog::getOpenFileName() függvényét használjuk majd. 5 Minden QCustomPlothoz tartozó osztály neve QCP-vel kezdődik.
1. Először adjuk hozzá a grafikaqtvel.h-hoz a btnbrowse1 gomb clicked SIGNALjához tartozó SLOT-ot: void on_btnbrowse1_clicked(); Ebben kell lekezeljük a fájl kiválasztást és a fájl név beírását az edtfile1 mezőbe. Amíg a mezőben nincs érvényes fájl név, addig a kirajzolás gomb legyen inaktív! 2. Válasszuk ki az stckpages widgetet 6! Állítsuk az stckpages indexét 1-re! Állítsuk át a btnplot és a btnprint gombokat inaktívra töröljük ki a kijelölést a Property Editorban ezek enabled jelölőnégyzetéből! Állítsuk vissza az stckpages indexét 0-ra, majd mentsük el a változásokat! 3. Adjuk hozzá a SLOT függvényt a grafikaqtvel.cpp fájlhoz! void GrafikaQTvel::on_btnBrowse1_clicked() Használjuk a sztatikus QFileDialog::getOpenFileName(szülő objektum, dialógus felirat, fájl név szűrők) függvényt, ami csak létező fájl nevet fogad el és ha az Elvet/Cancel gombot nyomjuk meg, akkor csak egy üres string-et ad vissza: QString filename = QFileDialog::getOpenFileName(this, "Adat fájl megnyitása", "./", "ASCII fájlok (*.txt);;bináris fájlok (*.)" ); A szűrők megnevezéseket és zárójelbe rakott kiterjesztéseket tartalmaznak. Több szűrőt két pontosvesszővel választunk el egymástól. if (!filename.isempty()) // volt létező fájl név ui.edtfile1->settext(filename); Láthatóan semmit sem tettünk a kirajzolás gomb aktívvá tételére. Ennek több oka van: szeretnénk, ha nem csak kiválaszthatnánk, de be is írhatnánk a fájl nevét. Ha kitöröljük a beviteli mező tartalmát, akkor a gombot újra inaktívvá kell tennünk. Ehhez az edtfile1 mező textchanged() SIGNAL- jához kell SLOT ot rendelnünk: 1. Adjuk hozzá a 6 Vagy sikerül rákattintanunk az ablakban, vagy az Object Inspector-ban rákattintunk az stckpages-re.
void on_edtfile1_textchanged(qstring); SLOT-ot a header fájlhoz. 2. Adjuk a törzsét a cpp fájlhoz. A btnplot akkor aktív, mikor van szöveg a beviteli mezőben (s nem üres) és az egy létező fájl neve. Ez utóbbit a sztatikus QFile::exists() függvénnyel nézhetjük meg. void GrafikaQTvel::on_edtFile1_textChanged(QString s) bool benable =!s.isempty()&& QFile::exists(s); ui.btnplot->setenabled(benable); A fájlok beolvasása A Fontos QT osztályok segédletben leírtakat használjuk fel a beolvasásra. A szöveges fájlok formátuma: Fájl fejléc sor: "Teszt Adat Fájl" X és Y koordináták soronként ;-vel elválasztva. A bináris fájlok formátuma: Fájl fejléc: (qint32)0x54443350; Pontok száma a fájlban 32 biten (qint32) X értékek dupla pontos (double) számokat tartalmazó vektora Y értékek vektora A beolvasó függvény deklarációja int _ReadFile(QString név, QVector<double> &x, QVector<double> &y); Ez a GrafikaQtvel osztály private részébe kerül. A függvény maga elég bonyolult, ezért itt nem foglalkozunk vele, a feltöltött.7z fájlban ez már benne van. Az adatok megjelenítése Az adatokat a btnplot megnyomásával rajzoltatjuk ki. Ehhez 1. Adjuk hozzá a SLOT deklarációját (void on_btnplot_clicked();) a header filehoz, 2. Adjuk a definíciót a cpp fájlhoz:
Ha sikerül beolvassuk az adatfájlt két lokális vektorba, majd hozzáadunk egy új görbét a widgethez, beállítjuk az adatokat és megjelenítjük azt: void GrafikaQTvel::on_btnPlot_clicked() QVector <double> x, y; if (_ReadFile(ui.edtFile1->text(), x, y) > 0) // egyébként hiba QCPGraph *graph = ui.plotter->addgraph(); plotter->setdata(x, y); ui.plotter->replot(); A tanszéki fájl szerverről letölthetünk két minta adatfájlt, sin.txt -t és cosinus.txt -t. Futtassuk le a programot és olvassuk be a sinus.txt fájlt, majd jelenítsük meg! Mi a probléma a megjelenítéssel?
Elfelejtettük beállítani a megjelenítési határokat. Ezt beépíthetnénk a beolvasásba is, de a kirajzolásba is betehetjük. Mivel a beolvasásba berakni nem lenne kényelmes, itt az egyszerűbbet választjuk: a kirajzolásba rakjuk be a következő programrészletet az ui.plotter->replot(); sor elé: double minx = 9.0e99, miny = 9.0e00, maxx=-9.0e99, maxy=-9.0e99; for (int i = 0; i < x.size(); ++i) if (minx > x[i]) minx = x[i]; if (miny > y[i]) miny = y[i]; if (maxx < x[i]) maxx = x[i]; if (maxy < y[i]) maxy = y[i]; ui.plotter->xaxis->setrange(minx, maxx); ui.plotter->yaxis->setrange(miny, maxy); Ezután már a várt képet kapjuk:
Interaktív függvénygörbe rajzolás A QCustomPlot-nak van néhány beépített kényelmi szolgáltatása. Ezek egyike, hogy a képet az egérrel mozgathatjuk, illetve nagyíthatjuk és kicsinyíthetjük. Ehhez mindössze az alábbi sort kell beírjuk a GrafikaQTvel konstruktorába: ui.plotter->setinteractions(qcp::irangedrag QCP::iRangeZoom); Néhány egyéb lehetőség A tengelyeket tetszőleges szövegekkel feliratoztathatjuk a tengelyek setlabel() függvényeivel. Pl. írjuk be a GrafikaQTvel konstruktorába a következő két sort: ui.plotter->xaxis->setlabel("time (sec)"); ui.plotter->yaxis->setlabel("signal (V)"); Egy QCustomplot tetszőleges számú görbe megjelenítésére képes. Ilyenkor a görbéket azoosítás céljából célszerű névvel ellátni. Erre a QCPGraph::setName(const QString &name); függvény szolgál. A görbék színét a hozzájuk tartozó toll színével lehet megadni. Állítsuk be, pl a függvénygörbe színét az aktuális tollszínre! Ehhez az on_btnplot_clicked() függvénybe a graph->setdata() hívás elé írjuk be a következőket: QPen pen = graph->pen(); pen.setcolor(_params.pencolor); graph->setpen(pen); A görbe vonalait a QCPGraph::setLineStyle(QCPGraph::LineStyle ls) és QCPGraph::setScatterStyle(ScatterShape ss) együttesével adhatjuk meg. A lehetséges vonal stílusok: Vonal Stílusok lsnone lsline lsstepleft lsstepright lsstepcenter lsimpulse Nincs vonal - az adatpontokba a scatter style ban megadott szimbólumok kerülnek - Egyenes vonal köti össze a pontokat Lépcsőzetes vonalak, a magasságuk a bal adatpont értéke - jobb oldali adatpont értéke -"- középen levő adatpontok értéke Az értékek tengelyével párhuzamos egyes a 0 értéktől az adat pontig
A lehetséges szimbolumok 7 : Scatter - stílusok ssnone ssdot sscross ssplus sscircle ssdisc sssquare ssdiamond ssstar sstriangle sstriangleinverted sscrosssquare ssplussquare sscrosscircle sspluscircle sspeace sspixmap sscustom Csak vonalak, nincsenek szimbólumok Egy pixelnyi pont (ha nagyobb kell akkor az ssdisc vagy sscircle a jó) kereszt Plusz jel Kör, aminek a belsejét az ecsettel tölti ki Kör, amit a toll színével tölt ki Négyzet Gyémánt alak Nyolc ágú csillag (kereszt és plusz jel) lapján álló egyenlőoldalú háromszög Ugyanez a csúcsán állva Négyzet benne egy kereszt Négyzet benne egy + jel Kör egy kereszttel Kör + jellel Béke jele Tetszőleges pixmap (beállítás QCPScatterStyle::setPixmap()), a középpontja az adatponton Tetszőleges rajz műveletek (QCPScatterStyle:: setcustompath()) az on_btnplot_clicked() függvénybe a graph->setdata() hívás elé írjuk be a következőket: graph->setlinestyle(qcpgraph::lsnone); graph->setscatterstyle(qcpscatterstyle::ssdisc); Az így ábrázolt függvényen a pontok összefolynak és egy vastag vonalnak néznek ki. Ha viszont az egér görgőjével belenagyítunk akkor látszik, hogy egy pontozott vonalról van szó. 7 QCPScatterStyle típusú változókat az alábbi konstruktorok egyikével lehet létrehozni: QCPScatterStyle (ScatterShape shape, double size=6) QCPScatterStyle (ScatterShape shape, const QColor &color, double size) QCPScatterStyle (ScatterShape shape, const QColor &color, const QColor &fill, double size) QCPScatterStyle (ScatterShape shape, const QPen &pen, const QBrush &brush, double size) QCPScatterStyle (const QPixmap &pixmap) QCPScatterStyle (const QPainterPath &custompath, const QPen &pen, const QBrush &brush=qt::nobrush, double size=6) Ha csak valamelyik ssxxx stílust adjuk meg a setscatterstyle() függvénynek, akkor az első konstruktort használjuk.
Ennélfogva célszerű megtartani az lsline vonal stílust. A szimbólumok megjelenítésére két módszert használhatunk ilyenkor: - Az ssnone scatter stílust használjuk, amíg a pontok közel vannak egymáshoz, utána pedig más stílust. - A QCustomPlot 2.0 esetekben viszont azt is megadhatjuk hányadik pontokat akarjuk valamilyen szimbólummal megjelölni. Erre szolgál a setscatterskip(int lépésköz) függvény 8. Most válasszuk ki a cosinus.txt fájlt és azt is olvassuk be. Mindkettő megjelenik a grafikonon. Ha ezt nem akarjuk, akkor a memóriában levő függvénygörbéket ki kell törölni. Az UI-n van erre egy jelölőnégyzet, amit idáig nem használtunk. Módosítsuk a void GrafikaQTvel::on_btnPlot_clicked() függvényt! A _ReadFile sor elé írjuk be a következőket: if (ui.chkoverwrite1->ischecked()) while (ui.plotter->graph(0)) ui.plotter->removegraph(0); A QCustomPlot graphcount() függvénye megadja hány függvénygörbe van pillanatnyilag. A graph(index) függvénye pedig az index-edik görbére mutató pointert, vagy ha nincsenek görbék nullpointer-t ad vissza. Láttuk, hogy a nagyítást és mozgatást a setinteractions() fügvénnyel állíthatjuk be. A paraméterei OR-ral összekapcsolt bitek. Adjunk hozzá egy újabbat, a QCP::iSelectPlottables bitet, ami lehetővé teszi egy görbe kiválasztását:: ui.plotter->setinteractions(qcp::irangedrag QCP::iRangeZoom QCP::iSelectPlottables); Azonban továbbra sem tudjuk kiválaszztani a görbét! Valamiért, ha a scatterstyle nem ssnone a görbe nem választható ki. Comment-ezzük ki az lsnone-t és ssnone-t tartalmazó sorokat és próbáljuk újra! 8 A QCustomPlot súgója szerint ez a függvény az 1.3.2-ben is létezik, de ez nem igaz.
A kiválasztott görbékre mutató pointerek listáját a QList<QCPGraph*> selectedgraphs() függvény adja vissza. A görbékhez egy jelmagyarázatot is megjeleníthetünk, amiben az egyes görbék nevei és a hozzájuk tartozó vonalak szerepelnek.a QCustomplot::legend->setVisible(bool) függvénnyel. Ahhoz, hogy a változást lássuk szükség van a görbe újrarajzolására is! Módosítsuk a kódot úgy, hogy minden függvénygörbéhez adjuk hozzá a fájl nevét és adjunk egy jelölőnégyzetet a laphoz, amelyik megmutatja/eltünteti a jelmagyarázatot! Az void GrafikaQTvel::on_btnPlot_clicked() függvénybe az QCPGraph *graph = ui.plotter- >addgraph(); sor utánra szúrjuk be ezt a sort: graph->setname(ui.edtfile1->text()); Adjunk hozzá két jelölőnégyzetet az UI-hoz 9. Nevük legyen chkscatter és chklegend! Az elsőhöz nem kell SLOT, elég beírni a kikommentezett graph- >setscatterstyle(qcpscatterstyle::ssdisc); sor elé a vizsgálatát: if (ui.chkscatter->ischecked()) graph->setscatterstyle(qcpscatterstyle::ssdisc); A jelmagyarázathoz csináljuk meg a void on_chklegend_toggled(bool); SIGNAL-t: void GrafikaQTvel::on_chkLegend_toggled(bool b) ui.plotter->legend->setvisible(b); ui.plotter->replot(); 9 Az egyszerűség kedvéért a hozzáadás előtt szüntessük meg a layout-ot (Break Layout).