mirror of
https://github.com/MoreTore/openpilot.git
synced 2026-08-05 08:16:06 +08:00
cabana: optimize sparkline rendering by reducing points in horizontal segments (#35689)
Optimize sparkline rendering by reducing redundant points in flat segments
This commit is contained in:
@@ -4,51 +4,97 @@
|
||||
#include <limits>
|
||||
#include <QPainter>
|
||||
|
||||
void Sparkline::update(const MessageId &msg_id, const cabana::Signal *sig, double last_msg_ts, int range, QSize size) {
|
||||
points.clear();
|
||||
double value = 0;
|
||||
auto [first, last] = can->eventsInRange(msg_id, std::make_pair(last_msg_ts -range, last_msg_ts));
|
||||
for (auto it = first; it != last; ++it) {
|
||||
if (sig->getValue((*it)->dat, (*it)->size, &value)) {
|
||||
points.emplace_back(((*it)->mono_time - (*first)->mono_time) / 1e9, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (points.empty() || size.isEmpty()) {
|
||||
void Sparkline::update(const cabana::Signal *sig, CanEventIter first, CanEventIter last, int range, QSize size) {
|
||||
if (first == last || size.isEmpty()) {
|
||||
pixmap = QPixmap();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto [min, max] = std::minmax_element(points.begin(), points.end(),
|
||||
[](auto &l, auto &r) { return l.y() < r.y(); });
|
||||
min_val = min->y() == max->y() ? min->y() - 1 : min->y();
|
||||
max_val = min->y() == max->y() ? max->y() + 1 : max->y();
|
||||
freq_ = points.size() / std::max(points.back().x() - points.front().x(), 1.0);
|
||||
points_.clear();
|
||||
min_val = std::numeric_limits<double>::max();
|
||||
max_val = std::numeric_limits<double>::lowest();
|
||||
points_.reserve(std::distance(first, last));
|
||||
|
||||
uint64_t start_time = (*first)->mono_time;
|
||||
double value = 0.0;
|
||||
for (auto it = first; it != last; ++it) {
|
||||
if (sig->getValue((*it)->dat, (*it)->size, &value)) {
|
||||
min_val = std::min(min_val, value);
|
||||
max_val = std::max(max_val, value);
|
||||
points_.emplace_back(((*it)->mono_time - start_time) / 1e9, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (points_.empty()) {
|
||||
pixmap = QPixmap();
|
||||
return;
|
||||
}
|
||||
|
||||
freq_ = points_.size() / std::max(points_.back().x() - points_.front().x(), 1.0);
|
||||
render(sig->color, range, size);
|
||||
}
|
||||
|
||||
void Sparkline::render(const QColor &color, int range, QSize size) {
|
||||
const double xscale = (size.width() - 1) / (double)range;
|
||||
const double yscale = (size.height() - 3) / (max_val - min_val);
|
||||
for (auto &v : points) {
|
||||
v = QPoint(v.x() * xscale, 1 + std::abs(v.y() - max_val) * yscale);
|
||||
// Adjust for flat lines
|
||||
bool is_flat_line = min_val == max_val;
|
||||
if (is_flat_line) {
|
||||
min_val -= 1.0;
|
||||
max_val += 1.0;
|
||||
}
|
||||
|
||||
// Calculate scaling
|
||||
const double xscale = (size.width() - 1) / (double)range;
|
||||
const double yscale = (size.height() - 3) / (max_val - min_val);
|
||||
bool draw_individual_points = (points_.back().x() * xscale / points_.size()) > 8.0;
|
||||
|
||||
// Transform or downsample points
|
||||
render_points_.reserve(points_.size());
|
||||
render_points_.clear();
|
||||
if (draw_individual_points) {
|
||||
for (const auto &p : points_) {
|
||||
render_points_.emplace_back(p.x() * xscale, 1.0 + (max_val - p.y()) * yscale);
|
||||
}
|
||||
} else if (is_flat_line) {
|
||||
double y = size.height() / 2.0;
|
||||
render_points_.emplace_back(0.0, y);
|
||||
render_points_.emplace_back(points_.back().x() * xscale, y);
|
||||
} else {
|
||||
double prev_y = points_.front().y();
|
||||
render_points_.emplace_back(points_.front().x() * xscale, 1.0 + (max_val - prev_y) * yscale);
|
||||
bool in_flat = false;
|
||||
|
||||
for (size_t i = 1; i < points_.size(); ++i) {
|
||||
const auto &p = points_[i];
|
||||
double y = p.y();
|
||||
if (std::abs(y - prev_y) < 1e-6) {
|
||||
in_flat = true;
|
||||
} else {
|
||||
if (in_flat) render_points_.emplace_back(points_[i - 1].x() * xscale, 1.0 + (max_val - prev_y) * yscale);
|
||||
render_points_.emplace_back(p.x() * xscale, 1.0 + (max_val - y) * yscale);
|
||||
in_flat = false;
|
||||
}
|
||||
prev_y = y;
|
||||
}
|
||||
if (in_flat) render_points_.emplace_back(points_.back().x() * xscale, 1.0 + (max_val - prev_y) * yscale);
|
||||
}
|
||||
|
||||
// Render to pixmap
|
||||
qreal dpr = qApp->devicePixelRatio();
|
||||
size *= dpr;
|
||||
if (size != pixmap.size()) {
|
||||
pixmap = QPixmap(size);
|
||||
const QSize pixmap_size = size * dpr;
|
||||
if (pixmap.size() != pixmap_size) {
|
||||
pixmap = QPixmap(pixmap_size);
|
||||
}
|
||||
pixmap.setDevicePixelRatio(dpr);
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter painter(&pixmap);
|
||||
painter.setRenderHint(QPainter::Antialiasing, points.size() < 500);
|
||||
painter.setRenderHint(QPainter::Antialiasing, render_points_.size() <= 500);
|
||||
painter.setPen(color);
|
||||
painter.drawPolyline(points.data(), points.size());
|
||||
painter.drawPolyline(render_points_.data(), render_points_.size());
|
||||
|
||||
painter.setPen(QPen(color, 3));
|
||||
if ((points.back().x() - points.front().x()) / points.size() > 8) {
|
||||
painter.drawPoints(points.data(), points.size());
|
||||
if (draw_individual_points) {
|
||||
painter.drawPoints(render_points_.data(), render_points_.size());
|
||||
} else {
|
||||
painter.drawPoint(points.back());
|
||||
painter.drawPoint(render_points_.back());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
class Sparkline {
|
||||
public:
|
||||
void update(const MessageId &msg_id, const cabana::Signal *sig, double last_msg_ts, int range, QSize size);
|
||||
void update(const cabana::Signal *sig, CanEventIter first, CanEventIter last, int range, QSize size);
|
||||
inline double freq() const { return freq_; }
|
||||
bool isEmpty() const { return pixmap.isNull(); }
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
private:
|
||||
void render(const QColor &color, int range, QSize size);
|
||||
|
||||
std::vector<QPointF> points;
|
||||
std::vector<QPointF> points_;
|
||||
std::vector<QPointF> render_points_;
|
||||
double freq_ = 0;
|
||||
};
|
||||
|
||||
@@ -634,11 +634,12 @@ void SignalView::updateState(const std::set<MessageId> *msgs) {
|
||||
QSize size(available_width - value_width,
|
||||
delegate->button_size.height() - style()->pixelMetric(QStyle::PM_FocusFrameVMargin) * 2);
|
||||
|
||||
auto [first, last] = can->eventsInRange(model->msg_id, std::make_pair(last_msg.ts -settings.sparkline_range, last_msg.ts));
|
||||
QFutureSynchronizer<void> synchronizer;
|
||||
for (int i = first_visible.row(); i <= last_visible.row(); ++i) {
|
||||
auto item = model->getItem(model->index(i, 1));
|
||||
synchronizer.addFuture(QtConcurrent::run(
|
||||
&item->sparkline, &Sparkline::update, model->msg_id, item->sig, last_msg.ts, settings.sparkline_range, size));
|
||||
&item->sparkline, &Sparkline::update, item->sig, first, last, settings.sparkline_range, size));
|
||||
}
|
||||
synchronizer.waitForFinished();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user