1

I want to make my QChart dynamically update whenever a point is added to the QLineSeries object attached to it, but it seems that this update only occurs after the while loop I am running has finished. I am using said while loop in interface.cpp that calls a function updatePlot() which adds the data point to the line series, but this only updates the chart after the while loop has completely finished. Pseudo code of what is happening here:

qtwindow.cpp

// Constructor that initializes the series which will be passed into the interface
AlgoWindow::AlgoWindow( ..., TradingInterface* interface, ... ) {

    ...

    QLineSeries* series = new QLineSeries();
    QLineSeries* benchmark = new QLineSeries();

    QChart* chart = new QChart();
    chart->addSeries(series);
    chart->addSeries(benchmark);

    // Also creates custom axes which are attached to each series
    ...
}

// Slot connected to a button signal
void AlgoWindow::buttonClicked() {

    // Runs the backtest 
    interface->runbacktest(..., series, benchmark, ...);
}

interface.cpp

void TradingInterface::runbacktest(..., QtCharts::QLineSeries* algoplot, QtCharts::QLineSeries* benchplot) {

    // Runs a huge while loop that continuously checks for events
    while (continue_backtest) {
        if (!eventsqueue.isEmpty()) {
             // Handle each event for the bar
        } else {
             // All events have been handled for the day, so plot
             updatePlot(algoplot, benchplot);
        }
    }
}

void TradingInterface::updatePlot(QtCharts::QLineSeries *algoseries,
    QtCharts::QLineSeries *benchseries) {

    // Get the date and the information to put in each point
    long date = portfolio.bars->latestDates.back();
    double equitycurve = portfolio.all_holdings.rbegin().operator*().second["equitycurve"];
    double benchcurve = benchmarkportfolio.all_holdings.rbegin().operator*.second["equitycurve"];

    // Append the new points to their respective QLineSeries
    algoseries->append(date * 1000, equitycurve*100);
    benchseries->append(date * 1000, benchcurve*100);
}

This gives me no errors and the while loop completes, but the lines are only plotted after runbacktest() exits. It then plots all the data correctly, but all at once.

What I need to happen is for the QChart to update every time the lines are added, which my guess was to use some form of custom signal-slot listener but I have no clue how to go about that. If the graph will not update until after the function completes, is it even possible within the QChart framework?

Also, I have already tried QChart::update() and QChartView::repaint(). Both produced the same results as without.

EDIT: I tried setting up a new thread that emits a signal back to the main thread whenever the data is completed but it seems to have changed nothing. The QChart still does not update until after all the data has been inputted. I added a couple lines to help debug and it seems like the function which emits the signal runs consistently just fine, but the slot function which receives the signal only runs after the thread has finished. Not only that, but slowing the signals down with a sleep does not make it plot slowly (like I thought), as the QChart still refuses to update until after the final update to addData().

random_0620
  • 1,636
  • 5
  • 23
  • 44

2 Answers2

1

Either remove your while loop and perform the work one step at a time with a timer.

Or run your runbacktest function in another thread and send a signal to update the QChart in the UI's thread when the data is ready.

Either way you need to give control back to the event loop so that the chart can be repainted.

f4.
  • 3,814
  • 1
  • 23
  • 30
  • I tried to make a new thread which emits a signal whenever the data is ready, but it still seems like the signals it emits are only received by the slot after the thread has finished. I made a custom QObject class initialized in the main window class which I pass in to runbacktest(). That QObject has a signal which is connected to a slot function back in the main window class, and it emits that signal after each day. I still have the same problem as before, however. I fiddled with sleeping the threads and the problem still seems to be that the QChart does not update until after all data is in. – random_0620 Jun 23 '18 at 19:35
  • 1
    @s_kirkiles The problem seems to be your implementation, I use the method indicated in the answer and it works correctly, you could provide a [mcve] of your thread. – eyllanesc Jun 23 '18 at 19:52
  • Usually adding threads in attempt to solve a problem results in having one more problem. That one suggestion is not good advice. Otherwise it’s OK. – Kuba hasn't forgotten Monica Jun 25 '18 at 14:19
0

The Qt idiom for running an operation “continuously” is to use a zero-duration “timer”. It’s not a timer really, but Qt calls it one.

You can do the operation in chunks that take approximately a millisecond. For this, invert the control flow. Qt doesn't provide too much syntactic sugar for it, but it's easy to remedy.

Convert this code, which maintains a loop:

for (int i = 0; i < 1000; ++i) {
  doSomething(i);
}

into this lambda, which is invoked by the event loop:

m_tasks.addTask([this](i = 0) mutable {
  doSomething(i);
  ++i;
  return i < 1000;
});

assuming:

class Controller : public QObject {
  Tasks m_tasks;
  ...
};

where the Tasks class maintains a list of tasks to be executed by the event loop:

class Tasks : public QObject {
  Q_OBJECT
  QBasicTimer timer;
  std::list<std::function<bool()>> tasks;
protected:
  void timerEvent(QTimerEvent *ev) override {
    if (ev->timerId() != timer.timerId())
      return;
    for (auto it = tasks.begin(); it != tasks.end(); ) {
      bool keep = (*it)();
      if (!keep)
        it = tasks.erase(it);
      else
        ++it;
    }
    if (tasks.empty())
      timer.stop();
  }
public:
  using QObject :: QObject;
  template <typename F> void addTask(F &&fun) {
    tasks.emplace_back(std::forward(fun));
    if (!timer.isActive())
      timer.start(0, this);
  }
};
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313