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 programmal1 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ájljai2: 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ájlt3! Nyissuk meg a QTCreator/QTDesigner súgóját QTCreatorban a baloldalon a Help-et kiválasztva!
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ó. 1
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ára4. 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:
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. 4
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: QT += widgets gui printsupport
-ra! 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 QCPAxis5. 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 widgetet6! Á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 2. Adjuk a definíciót a cpp fájlhoz:
on_btnPlot_clicked();)
a header filehoz,
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
Nincs vonal - az adatpontokba a scatter style –ban megadott szimbólumok kerülnek -
lsLine
Egyenes vonal köti össze a pontokat
lsStepLeft
Lépcsőzetes vonalak, a magasságuk a bal adatpont értéke
lsStepRight
-“ – jobb oldali adatpont értéke
lsStepCenter
-"- középen levő adatpontok értéke
lsImpulse
Az értékek tengelyével párhuzamos egyes a 0 értéktől az adat pontig
A lehetséges szimbolumok7: Scatter - stílusok ssNone
Csak vonalak, nincsenek szimbólumok
ssDot
Egy pixelnyi pont (ha nagyobb kell akkor az ssDisc vagy ssCircle a jó)
ssCross
kereszt
ssPlus
Plusz jel
ssCircle
Kör, aminek a belsejét az ecsettel tölti ki
ssDisc
Kör, amit a toll színével tölt ki
ssSquare
Négyzet
ssDiamond
Gyémánt alak
ssStar
Nyolc ágú csillag (kereszt és plusz jel)
ssTriangle
lapján álló egyenlőoldalú háromszög
ssTriangleInverted
Ugyanez a csúcsán állva
ssCrossSquare
Négyzet benne egy kereszt
ssPlusSquare
Négyzet benne egy + jel
ssCrossCircle
Kör egy kereszttel
ssPlusCircle
Kör + jellel
ssPeace
“Béke jele”
ssPixmap
Tetszőleges pixmap (beállítás QCPScatterStyle::setPixmap()), a középpontja az adatponton
ssCustom
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ény8.
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
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()
>addGraph();
függvénybe az QCPGraph
*graph = ui.plotter-
sor utánra szúrjuk be ezt a sort: graph->setName(ui.edtFile1->text());
Adjunk hozzá két jelölőnégyzetet az UI-hoz9. 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).