QT und QCustomPlot. Frage zu eigenem Intervall



  • Hallo,

    ich bin neu dabei mit QT und QCustomPlot und habe mal eine Newbiefrage. Ich will per QCustomPlot eine Datenreihe aus einem Array darstellen und diese soll zB alle 10 Sekunden aktualisiert werden (Array kann sich ändern). Ich habe das interaction-example von QCustomPlot (http://www.qcustomplot.com/) im mainwindow.cpp umgeschrieben, sodass die Daten aus einem eigenen Array angezeigt werden was funktioniert.

    Leider verstehe ich grundsätzlich nicht wie ich diese Aktion zum Beispiel in eine Endlosschleife mit zB Sleep(5000); bekomme, sodass die Graphen aktualisiert werden. Ich hab testweise ein Sleep(5000) zwischen die verschiedenen addRandomGraph(); gesetzt aber musste feststellen, dass die Graphen dadurch nicht hintereinander erscheinen sondern das dadurch das ganze Fenster inklusiv aller Graphen verzögert dargestellt wird. Das bedeutet wohl, dass addrandomgraph(); selbst gar nicht den jeweiligen Graph darstellt sondern irgendwie nur vorbereitet. Bin da noch ziemlicher Newbie was den ganzen Aufbau so eines GUIs angeht. Hat jemand eine Idee wo ich ansetzen muss? Eventuell bin ich diesbezüglich in der mainwindow.cpp auch in der falschen Datei unterwegs??

    Im Projekt sind:
    interaction-example.pro
    qcustomplot.h
    mainwindow.h
    qcustomplot.cpp
    main.cpp
    mainwindow.cpp
    mainwindow.ui

    Hier die leicht modifizierte mainwindow.cpp um ein Array zu zeigen:

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    // Test QuellArray
    double array[10] = {1, 2, 3, 4, 5, 7, 9, 5, 7, -9};
    
    //----------------------------------------------------------------------------------------------------------------------
    //----------------------------------------------------------------------------------------------------------------------
    MainWindow::MainWindow(QWidget *parent) :
      QMainWindow(parent),
      ui(new Ui::MainWindow)
    {
      srand(QDateTime::currentDateTime().toTime_t());
      ui->setupUi(this);
    
      ui->customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes |
                                      QCP::iSelectLegend | QCP::iSelectPlottables);
      ui->customPlot->xAxis->setRange(-8, 8);
      ui->customPlot->yAxis->setRange(-5, 5);
      ui->customPlot->axisRect()->setupFullAxesBox();
    
      ui->customPlot->plotLayout()->insertRow(0);
      ui->customPlot->plotLayout()->addElement(0, 0, new QCPPlotTitle(ui->customPlot, "Interaction Example"));
    
      ui->customPlot->xAxis->setLabel("x Axis");
      ui->customPlot->yAxis->setLabel("y Axis");
      ui->customPlot->legend->setVisible(true);
      QFont legendFont = font();
      legendFont.setPointSize(10);
      ui->customPlot->legend->setFont(legendFont);
      ui->customPlot->legend->setSelectedFont(legendFont);
      ui->customPlot->legend->setSelectableParts(QCPLegend::spItems); // legend box shall not be selectable, only legend items
    
      addRandomGraph();
      addRandomGraph();
      addRandomGraph();
      Sleep(5000); // test Unterbrechung
      addRandomGraph();
    
      // connect slot that ties some axis selections together (especially opposite axes):
      connect(ui->customPlot, SIGNAL(selectionChangedByUser()), this, SLOT(selectionChanged()));
      // connect slots that takes care that when an axis is selected, only that direction can be dragged and zoomed:
      connect(ui->customPlot, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress()));
      connect(ui->customPlot, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheel()));
    
      // make bottom and left axes transfer their ranges to top and right axes:
      connect(ui->customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), ui->customPlot->xAxis2, SLOT(setRange(QCPRange)));
      connect(ui->customPlot->yAxis, SIGNAL(rangeChanged(QCPRange)), ui->customPlot->yAxis2, SLOT(setRange(QCPRange)));
    
      // connect some interaction slots:
      connect(ui->customPlot, SIGNAL(titleDoubleClick(QMouseEvent*,QCPPlotTitle*)), this, SLOT(titleDoubleClick(QMouseEvent*,QCPPlotTitle*)));
      connect(ui->customPlot, SIGNAL(axisDoubleClick(QCPAxis*,QCPAxis::SelectablePart,QMouseEvent*)), this, SLOT(axisLabelDoubleClick(QCPAxis*,QCPAxis::SelectablePart)));
      connect(ui->customPlot, SIGNAL(legendDoubleClick(QCPLegend*,QCPAbstractLegendItem*,QMouseEvent*)), this, SLOT(legendDoubleClick(QCPLegend*,QCPAbstractLegendItem*)));
    
      // connect slot that shows a message in the status bar when a graph is clicked:
      connect(ui->customPlot, SIGNAL(plottableClick(QCPAbstractPlottable*,QMouseEvent*)), this, SLOT(graphClicked(QCPAbstractPlottable*)));
    
      // setup policy and connect slot for context menu popup:
      ui->customPlot->setContextMenuPolicy(Qt::CustomContextMenu);
      connect(ui->customPlot, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuRequest(QPoint)));
    
    }
    //----------------------------------------------------------------------------------------------------------------------
    //----------------------------------------------------------------------------------------------------------------------
    
    //----------------------------------------------------------------------------------------------------------------------
    //----------------------------------------------------------------------------------------------------------------------
    MainWindow::~MainWindow()
    {
    
      delete ui;
    
    }
    //----------------------------------------------------------------------------------------------------------------------
    //----------------------------------------------------------------------------------------------------------------------
    
    void MainWindow::titleDoubleClick(QMouseEvent* event, QCPPlotTitle* title)
    {
      Q_UNUSED(event)
      // Set the plot title by double clicking on it
      bool ok;
      QString newTitle = QInputDialog::getText(this, "QCustomPlot example", "New plot title:", QLineEdit::Normal, title->text(), &ok);
      if (ok)
      {
        title->setText(newTitle);
        ui->customPlot->replot();
      }
    }
    
    void MainWindow::axisLabelDoubleClick(QCPAxis *axis, QCPAxis::SelectablePart part)
    {
      // Set an axis label by double clicking on it
      if (part == QCPAxis::spAxisLabel) // only react when the actual axis label is clicked, not tick label or axis backbone
      {
        bool ok;
        QString newLabel = QInputDialog::getText(this, "QCustomPlot example", "New axis label:", QLineEdit::Normal, axis->label(), &ok);
        if (ok)
        {
          axis->setLabel(newLabel);
          ui->customPlot->replot();
        }
      }
    }
    
    void MainWindow::legendDoubleClick(QCPLegend *legend, QCPAbstractLegendItem *item)
    {
      // Rename a graph by double clicking on its legend item
      Q_UNUSED(legend)
      if (item) // only react if item was clicked (user could have clicked on border padding of legend where there is no item, then item is 0)
      {
        QCPPlottableLegendItem *plItem = qobject_cast<QCPPlottableLegendItem*>(item);
        bool ok;
        QString newName = QInputDialog::getText(this, "QCustomPlot example", "New graph name:", QLineEdit::Normal, plItem->plottable()->name(), &ok);
        if (ok)
        {
          plItem->plottable()->setName(newName);
          ui->customPlot->replot();
        }
      }
    }
    
    void MainWindow::selectionChanged()
    {
      /*
       normally, axis base line, axis tick labels and axis labels are selectable separately, but we want
       the user only to be able to select the axis as a whole, so we tie the selected states of the tick labels
       and the axis base line together. However, the axis label shall be selectable individually.
    
       The selection state of the left and right axes shall be synchronized as well as the state of the
       bottom and top axes.
    
       Further, we want to synchronize the selection of the graphs with the selection state of the respective
       legend item belonging to that graph. So the user can select a graph by either clicking on the graph itself
       or on its legend item.
      */
    
      // make top and bottom axes be selected synchronously, and handle axis and tick labels as one selectable object:
      if (ui->customPlot->xAxis->selectedParts().testFlag(QCPAxis::spAxis) || ui->customPlot->xAxis->selectedParts().testFlag(QCPAxis::spTickLabels) ||
          ui->customPlot->xAxis2->selectedParts().testFlag(QCPAxis::spAxis) || ui->customPlot->xAxis2->selectedParts().testFlag(QCPAxis::spTickLabels))
      {
        ui->customPlot->xAxis2->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels);
        ui->customPlot->xAxis->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels);
      }
      // make left and right axes be selected synchronously, and handle axis and tick labels as one selectable object:
      if (ui->customPlot->yAxis->selectedParts().testFlag(QCPAxis::spAxis) || ui->customPlot->yAxis->selectedParts().testFlag(QCPAxis::spTickLabels) ||
          ui->customPlot->yAxis2->selectedParts().testFlag(QCPAxis::spAxis) || ui->customPlot->yAxis2->selectedParts().testFlag(QCPAxis::spTickLabels))
      {
        ui->customPlot->yAxis2->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels);
        ui->customPlot->yAxis->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels);
      }
    
      // synchronize selection of graphs with selection of corresponding legend items:
      for (int i=0; i<ui->customPlot->graphCount(); ++i)
      {
        QCPGraph *graph = ui->customPlot->graph(i);
        QCPPlottableLegendItem *item = ui->customPlot->legend->itemWithPlottable(graph);
        if (item->selected() || graph->selected())
        {
          item->setSelected(true);
          graph->setSelected(true);
        }
      }
    }
    
    void MainWindow::mousePress()
    {
      // if an axis is selected, only allow the direction of that axis to be dragged
      // if no axis is selected, both directions may be dragged
    
      if (ui->customPlot->xAxis->selectedParts().testFlag(QCPAxis::spAxis))
        ui->customPlot->axisRect()->setRangeDrag(ui->customPlot->xAxis->orientation());
      else if (ui->customPlot->yAxis->selectedParts().testFlag(QCPAxis::spAxis))
        ui->customPlot->axisRect()->setRangeDrag(ui->customPlot->yAxis->orientation());
      else
        ui->customPlot->axisRect()->setRangeDrag(Qt::Horizontal|Qt::Vertical);
    }
    
    void MainWindow::mouseWheel()
    {
      // if an axis is selected, only allow the direction of that axis to be zoomed
      // if no axis is selected, both directions may be zoomed
    
      if (ui->customPlot->xAxis->selectedParts().testFlag(QCPAxis::spAxis))
        ui->customPlot->axisRect()->setRangeZoom(ui->customPlot->xAxis->orientation());
      else if (ui->customPlot->yAxis->selectedParts().testFlag(QCPAxis::spAxis))
        ui->customPlot->axisRect()->setRangeZoom(ui->customPlot->yAxis->orientation());
      else
        ui->customPlot->axisRect()->setRangeZoom(Qt::Horizontal|Qt::Vertical);
    }
    
    /*
    //-----------------------------------------------------------------------------------------------------------------------
    // original
    void MainWindow::addRandomGraph()
    {
      int n = 50; // number of points in graph
      double xScale = (rand()/(double)RAND_MAX + 0.5)*2;
      double yScale = (rand()/(double)RAND_MAX + 0.5)*2;
      double xOffset = (rand()/(double)RAND_MAX - 0.5)*4;
      double yOffset = (rand()/(double)RAND_MAX - 0.5)*5;
      double r1 = (rand()/(double)RAND_MAX - 0.5)*2;
      double r2 = (rand()/(double)RAND_MAX - 0.5)*2;
      double r3 = (rand()/(double)RAND_MAX - 0.5)*2;
      double r4 = (rand()/(double)RAND_MAX - 0.5)*2;
      QVector<double> x(n), y(n);
      for (int i=0; i<n; i++)
      {
        x[i] = (i/(double)n-0.5)*10.0*xScale + xOffset;
        y[i] = (qSin(x[i]*r1*5)*qSin(qCos(x[i]*r2)*r4*3)+r3*qCos(qSin(x[i])*r4*2))*yScale + yOffset;
      }
    
      ui->customPlot->addGraph();
      ui->customPlot->graph()->setName(QString("New graph %1").arg(ui->customPlot->graphCount()-1));
      ui->customPlot->graph()->setData(x, y);
      ui->customPlot->graph()->setLineStyle((QCPGraph::LineStyle)(rand()%5+1));
      if (rand()%100 > 50)
        ui->customPlot->graph()->setScatterStyle(QCPScatterStyle((QCPScatterStyle::ScatterShape)(rand()%14+1)));
      QPen graphPen;
      graphPen.setColor(QColor(rand()%245+10, rand()%245+10, rand()%245+10));
      graphPen.setWidthF(rand()/(double)RAND_MAX*2+1);
      ui->customPlot->graph()->setPen(graphPen);
      ui->customPlot->replot();
    }
    //-----------------------------------------------------------------------------------------------------------------------
    */
    
    //-----------------------------------------------------------------------------------------------------------------------
    // Test mit eigenem Array
    void MainWindow::addRandomGraph()
    {
    
       array[9]=array[9]+10;
    
       int AnzahlWerte = 10;
    
     QVector <double> x(AnzahlWerte), y(AnzahlWerte);
    
       double intervall = 1;
       for(int s=0; s<AnzahlWerte; s=s+1)
       {
           x[s]=s+intervall;
           y[s]=array[s];
       }
    
      ui->customPlot->addGraph();
      ui->customPlot->graph()->setName(QString("New graph %1").arg(ui->customPlot->graphCount()-1));
      ui->customPlot->graph()->setData(x, y);
      ui->customPlot->graph()->setLineStyle((QCPGraph::LineStyle)(rand()%5+1));
      if (rand()%100 > 50)
        ui->customPlot->graph()->setScatterStyle(QCPScatterStyle((QCPScatterStyle::ScatterShape)(rand()%14+1)));
      QPen graphPen;
      graphPen.setColor(QColor(rand()%245+10, rand()%245+10, rand()%245+10));
      graphPen.setWidthF(rand()/(double)RAND_MAX*2+1);
      ui->customPlot->graph()->setPen(graphPen);
      ui->customPlot->replot();
    }
    //-----------------------------------------------------------------------------------------------------------------------
    
    void MainWindow::removeSelectedGraph()
    {
      if (ui->customPlot->selectedGraphs().size() > 0)
      {
        ui->customPlot->removeGraph(ui->customPlot->selectedGraphs().first());
        ui->customPlot->replot();
      }
    }
    
    void MainWindow::removeAllGraphs()
    {
      ui->customPlot->clearGraphs();
      ui->customPlot->replot();
    }
    
    void MainWindow::contextMenuRequest(QPoint pos)
    {
      QMenu *menu = new QMenu(this);
      menu->setAttribute(Qt::WA_DeleteOnClose);
    
      if (ui->customPlot->legend->selectTest(pos, false) >= 0) // context menu on legend requested
      {
        menu->addAction("Move to top left", this, SLOT(moveLegend()))->setData((int)(Qt::AlignTop|Qt::AlignLeft));
        menu->addAction("Move to top center", this, SLOT(moveLegend()))->setData((int)(Qt::AlignTop|Qt::AlignHCenter));
        menu->addAction("Move to top right", this, SLOT(moveLegend()))->setData((int)(Qt::AlignTop|Qt::AlignRight));
        menu->addAction("Move to bottom right", this, SLOT(moveLegend()))->setData((int)(Qt::AlignBottom|Qt::AlignRight));
        menu->addAction("Move to bottom left", this, SLOT(moveLegend()))->setData((int)(Qt::AlignBottom|Qt::AlignLeft));
      } else  // general context menu on graphs requested
      {
        menu->addAction("Add random graph", this, SLOT(addRandomGraph()));
        if (ui->customPlot->selectedGraphs().size() > 0)
          menu->addAction("Remove selected graph", this, SLOT(removeSelectedGraph()));
        if (ui->customPlot->graphCount() > 0)
          menu->addAction("Remove all graphs", this, SLOT(removeAllGraphs()));
      }
    
      menu->popup(ui->customPlot->mapToGlobal(pos));
    }
    
    void MainWindow::moveLegend()
    {
      if (QAction* contextAction = qobject_cast<QAction*>(sender())) // make sure this slot is really called by a context menu action, so it carries the data we need
      {
        bool ok;
        int dataInt = contextAction->data().toInt(&ok);
        if (ok)
        {
          ui->customPlot->axisRect()->insetLayout()->setInsetAlignment(0, (Qt::Alignment)dataInt);
          ui->customPlot->replot();
        }
      }
    }
    
    void MainWindow::graphClicked(QCPAbstractPlottable *plottable)
    {
      ui->statusBar->showMessage(QString("Clicked on graph '%1'.").arg(plottable->name()), 1000);
    }
    


  • Es ist zu viel Code. Wenn du etwas alle x Sekunden ausführen willst, bieten sich grundsätzlich Timer (QTimer in Qt) an. Oder vielleicht auch Nebenthreads, dürfte in dem Fall aber noch nicht nötig sein.
    Probier das erstmal aus, wenns nicht hilft, beschreib noch etwas genauer, was dein Problem ist.



  • Hallo danke,
    ja sorry das stimmt ist viel Code der nicht zur Sache tut.
    Das Problem ist das ich nicht weiß durch welchen Befehl der Graph angezeigt wird?
    Ich bin leider noch nicht so fit in C++ und QT daher die Frage.

    Ich habe den Code unten von mainwindow.cpp mal auf das Grundgerüst reduziert und die nicht relevanten Dinge rausgelassen. Durch den Aufruf von addRandomGraph();wird ein Graph definiert der dann angezeigt wird. Das Original-Beispiel hatte mehrere addRandomGraph(); hintereinander aufgerufen wo jedes mal ein anderer RandomGraph erzeugt wurde. Da ich dachte durch den Aufruf von addRandomGraph(); wird auch direkt der Graph im GUI angezeigt dachte ich man muss nur addRandomGraph(); in eine Schleife mit entsprechendem Intervall zB 5 Sekunden setzten und dann würde in diesem Intervall der Graph neu angezeigt werden. Nachdem ich dann den Teil in void MainWindow::addRandomGraph() so verändert habe, das statt Zufallskurven mein Array angezeigt wurde, habe ich das Sleep(5000); zwischen einen der Aufrufe der vier addRandomGraph(); gelegt, da ich dachte das würde dazu führen, dass der vierte Graph erst 5 Sekunden nach den vorigen angezeigt wird. Beim Ausführen allerdings hat das gesamte Programm 5 Sekunden gewartet bis etwas zu sehen war und dann waren aber alle vier Graphen gleichzeitig da obwohl ich vor den letzten addRandomGraph(); Aufruf Sleep(5000); gesetzt hatte.

    Das beweißt meiner Meinung nach, dass der eigentliche Befehl zur Anzeige woanders stecken muss. Soweit meine blutigen Newbiegedanken. Da ich von GUI und QT noch wenig Ahnung habe, können diese Überlegungen auch alle Quatsch sein weil vielleicht das ganze Konstrukt anders funktioniert als ich es mir vorstelle.

    Also im Prinzip verstehe ich nicht welcher Befehl in welcher Datei genau den Graph dann wirklich zum darstellen im GUI veranlasst? Denn durch meinen Test mit Sleep(5000); kann man ja zumindest festhalten, dass die Aufrufe von addRandomGraph(); nur die Daten definieren aber in dem GUI nicht zum anzeigen veranlassen.

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    // Test Fixes DatenArray statt Zufallsgraphen (die letzte Stelle -9 wird pro Durchlauf testweise um 10 erhöht)
    double array[10] = {1, 2, 3, 4, 5, 7, 9, 5, 7, -9};
    
    MainWindow::MainWindow(QWidget *parent) :
      QMainWindow(parent),
      ui(new Ui::MainWindow)
    {
    ....
      addRandomGraph();
      addRandomGraph();
      addRandomGraph();
      Sleep(5000); // test Unterbrechung
      addRandomGraph();
    ....
    }
    
    MainWindow::~MainWindow()
    {
      delete ui;
    }
    void ....andere
    
    // Hier der Teil wo die Daten für den Graphen definiert werden
    void MainWindow::addRandomGraph()
    {
    
       // sorgt als Testzweck dafür, dass bei jedem Durchlauf die letzte Stelle im Array um 10 erhöht wird.
       array[9]=array[9]+10;
    
       int AnzahlWerte = 10;
    
     QVector <double> x(AnzahlWerte), y(AnzahlWerte);
    
       // hier wird das Test-Array dem y[] zugewiesen
       // bei x[] entsteht eine Reihe wie 1,2,3,4,5,6,7,8,9,10
       double intervall = 1;
       for(int s=0; s<AnzahlWerte; s=s+1)
       {
           x[s]=s+intervall;
           y[s]=array[s];
       }
    
    ...
    }
    
    void ....andere
    


  • Ich habe QCustomPlot noch nie benutzt, 🙄 deswegen müssen allgemeine C++ und Qt Kenntnisse ausreichen.
    Sleep(5000) im Hauptthread aufzurufen ist keine gute Idee, damit hängt dein Programm natürlich. Deswegen der Vorschlag mit dem Timer.
    Ich glaub jetzt auch nicht, dass du wirklich ein Problem mit dem Graphen hast, ich glaub eher, dass dein Problem tatsächlich der Sleep ist. setData oder replot wird die Entscheidende Funktion sein. Aber zwischen den Aufrufen muss das Programm reagieren. Also Timer benutzen.
    Und hier wird jedesmal ein Graph hinzugefügt, du willst wahrscheinlich immer denselben haben. Musst dir also schauen, wie du beim Hinzufügen des Graphs den auch merken kannst, damit du den beim nächsten Durchlauf überschreibst, statt wieder einen neuen hinzuzufügen.

    Mir springen in dem Code paar Sachen ins Auge, die mir nicht gefallen, z.B. das hier:

    QMenu *menu = new QMenu(this);
    menu->setAttribute(Qt::WA_DeleteOnClose);

    Sehe keinen Grund, warum man das Menu nicht einfach auf dem Stack erstellen können sollte.



  • Mechanics schrieb:

    Ich habe QCustomPlot noch nie benutzt, 🙄 deswegen müssen allgemeine C++ und Qt Kenntnisse ausreichen.
    Sleep(5000) im Hauptthread aufzurufen ist keine gute Idee, damit hängt dein Programm natürlich. Deswegen der Vorschlag mit dem Timer.
    Ich glaub jetzt auch nicht, dass du wirklich ein Problem mit dem Graphen hast, ich glaub eher, dass dein Problem tatsächlich der Sleep ist. setData oder replot wird die Entscheidende Funktion sein. Aber zwischen den Aufrufen muss das Programm reagieren. Also Timer benutzen.
    Und hier wird jedesmal ein Graph hinzugefügt, du willst wahrscheinlich immer denselben haben. Musst dir also schauen, wie du beim Hinzufügen des Graphs den auch merken kannst, damit du den beim nächsten Durchlauf überschreibst, statt wieder einen neuen hinzuzufügen.

    Mir springen in dem Code paar Sachen ins Auge, die mir nicht gefallen, z.B. das hier:

    QMenu *menu = new QMenu(this);
    menu->setAttribute(Qt::WA_DeleteOnClose);

    Sehe keinen Grund, warum man das Menu nicht einfach auf dem Stack erstellen können sollte.

    Ok ich werde mich versuchen weiter einzulesen bzgl Timer und generell QT. Und ja genau, der Graph soll aktualisiert werden also genau so wie du geschrieben hast klingt es logisch. Das mit dem Stack... muss gestehen das übersteigt leider meine derzeitigen Fähigkeiten...

    Wenn ich es irgendwie hinbekomme, durch Intervall den Graph zu aktualisieren dann wäre das schon Traumhaft... Ich such jetzt mal nach Tutorials und versuche weiter 😉

    Grüße



  • array++ schrieb:

    Das mit dem Stack... muss gestehen das übersteigt leider meine derzeitigen Fähigkeiten...

    Wie immer in so einem der Tipp - du musst zuerst richtig C++ lernen. Sowas wie Qt und erst recht Plots lenken viel zu sehr von der Sprache ab, und die richtig zu beherrschen ist sehr wichtig.


Anmelden zum Antworten