在 运行 while 循环中从 QLineSeries 更新 QChart
Updating QChart from QLineSeries in a running while loop
我想让我的 QChart 在将一个点添加到附加到它的 QLineSeries 对象时动态更新,但似乎这个更新只发生在 while 循环 I am 运行 完成之后。我在 interface.cpp 中使用所说的 while 循环调用函数 updatePlot() 将数据点添加到线系列,但这只会在 while 循环完全完成后更新图表。这里发生的伪代码:
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);
}
这没有给我任何错误并且 while 循环完成,但这些线仅在 runbacktest() 退出后绘制。然后它会正确绘制所有数据,但一次绘制。
我需要做的是在每次添加行时更新 QChart,我猜这是使用某种形式的自定义信号槽侦听器,但我不知道如何去做。如果图表在函数完成后才会更新,甚至可以在 QChart 框架内实现吗?
此外,我已经尝试了 QChart::update() 和 QChartView::repaint()。两者都产生了与没有相同的结果。
编辑: 我尝试设置一个新线程,只要数据完成,它就会向主线程发出信号,但它似乎没有任何改变。在输入所有数据之后,QChart 仍然不会更新。我添加了几行来帮助调试,看起来发出信号的函数运行得很好,但是接收信号的槽函数只在线程完成后运行。不仅如此,通过睡眠减慢信号速度并不会使其绘制缓慢(就像我想的那样),因为 QChart 仍然拒绝更新,直到最终更新到 addData() 之后。
要么删除您的 while 循环并使用计时器一次执行一个步骤。
或者运行你的runbacktest
在另一个线程中运行,并在数据准备好时发送一个信号来更新UI线程中的QChart
。
无论哪种方式,您都需要将控制权交还给事件循环,以便重新绘制图表。
运行“连续”操作的 Qt 习惯用法是使用零持续时间“计时器”。它实际上不是计时器,但 Qt 称它为计时器。
您可以分块执行操作,大约需要一毫秒。为此,反转控制流。 Qt 并没有为它提供太多的语法糖,但它很容易补救。
转换这段代码,它保持一个循环:
for (int i = 0; i < 1000; ++i) {
doSomething(i);
}
进入这个由事件循环调用的 lambda:
m_tasks.addTask([this](i = 0) mutable {
doSomething(i);
++i;
return i < 1000;
});
假设:
class Controller : public QObject {
Tasks m_tasks;
...
};
其中 Tasks
class 维护事件循环要执行的任务列表:
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);
}
};
我想让我的 QChart 在将一个点添加到附加到它的 QLineSeries 对象时动态更新,但似乎这个更新只发生在 while 循环 I am 运行 完成之后。我在 interface.cpp 中使用所说的 while 循环调用函数 updatePlot() 将数据点添加到线系列,但这只会在 while 循环完全完成后更新图表。这里发生的伪代码:
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);
}
这没有给我任何错误并且 while 循环完成,但这些线仅在 runbacktest() 退出后绘制。然后它会正确绘制所有数据,但一次绘制。
我需要做的是在每次添加行时更新 QChart,我猜这是使用某种形式的自定义信号槽侦听器,但我不知道如何去做。如果图表在函数完成后才会更新,甚至可以在 QChart 框架内实现吗?
此外,我已经尝试了 QChart::update() 和 QChartView::repaint()。两者都产生了与没有相同的结果。
编辑: 我尝试设置一个新线程,只要数据完成,它就会向主线程发出信号,但它似乎没有任何改变。在输入所有数据之后,QChart 仍然不会更新。我添加了几行来帮助调试,看起来发出信号的函数运行得很好,但是接收信号的槽函数只在线程完成后运行。不仅如此,通过睡眠减慢信号速度并不会使其绘制缓慢(就像我想的那样),因为 QChart 仍然拒绝更新,直到最终更新到 addData() 之后。
要么删除您的 while 循环并使用计时器一次执行一个步骤。
或者运行你的runbacktest
在另一个线程中运行,并在数据准备好时发送一个信号来更新UI线程中的QChart
。
无论哪种方式,您都需要将控制权交还给事件循环,以便重新绘制图表。
运行“连续”操作的 Qt 习惯用法是使用零持续时间“计时器”。它实际上不是计时器,但 Qt 称它为计时器。
您可以分块执行操作,大约需要一毫秒。为此,反转控制流。 Qt 并没有为它提供太多的语法糖,但它很容易补救。
转换这段代码,它保持一个循环:
for (int i = 0; i < 1000; ++i) {
doSomething(i);
}
进入这个由事件循环调用的 lambda:
m_tasks.addTask([this](i = 0) mutable {
doSomething(i);
++i;
return i < 1000;
});
假设:
class Controller : public QObject {
Tasks m_tasks;
...
};
其中 Tasks
class 维护事件循环要执行的任务列表:
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);
}
};