cabana: optimize sparkline rendering by reducing points in horizontal segments (#35689)

Optimize sparkline rendering by reducing redundant points in flat segments
pull/35723/head
Dean Lee 2 days ago committed by GitHub
parent 631a067257
commit 12f766f8c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 88
      tools/cabana/chart/sparkline.cc
  2. 5
      tools/cabana/chart/sparkline.h
  3. 3
      tools/cabana/signalview.cc

@ -4,51 +4,97 @@
#include <limits> #include <limits>
#include <QPainter> #include <QPainter>
void Sparkline::update(const MessageId &msg_id, const cabana::Signal *sig, double last_msg_ts, int range, QSize size) { void Sparkline::update(const cabana::Signal *sig, CanEventIter first, CanEventIter last, int range, QSize size) {
points.clear(); if (first == last || size.isEmpty()) {
double value = 0; pixmap = QPixmap();
auto [first, last] = can->eventsInRange(msg_id, std::make_pair(last_msg_ts -range, last_msg_ts)); return;
}
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) { for (auto it = first; it != last; ++it) {
if (sig->getValue((*it)->dat, (*it)->size, &value)) { if (sig->getValue((*it)->dat, (*it)->size, &value)) {
points.emplace_back(((*it)->mono_time - (*first)->mono_time) / 1e9, 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() || size.isEmpty()) { if (points_.empty()) {
pixmap = QPixmap(); pixmap = QPixmap();
return; return;
} }
const auto [min, max] = std::minmax_element(points.begin(), points.end(), freq_ = points_.size() / std::max(points_.back().x() - points_.front().x(), 1.0);
[](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);
render(sig->color, range, size); render(sig->color, range, size);
} }
void Sparkline::render(const QColor &color, int range, QSize size) { void Sparkline::render(const QColor &color, int range, QSize size) {
// 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 xscale = (size.width() - 1) / (double)range;
const double yscale = (size.height() - 3) / (max_val - min_val); const double yscale = (size.height() - 3) / (max_val - min_val);
for (auto &v : points) { bool draw_individual_points = (points_.back().x() * xscale / points_.size()) > 8.0;
v = QPoint(v.x() * xscale, 1 + std::abs(v.y() - max_val) * yscale);
// 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(); qreal dpr = qApp->devicePixelRatio();
size *= dpr; const QSize pixmap_size = size * dpr;
if (size != pixmap.size()) { if (pixmap.size() != pixmap_size) {
pixmap = QPixmap(size); pixmap = QPixmap(pixmap_size);
} }
pixmap.setDevicePixelRatio(dpr); pixmap.setDevicePixelRatio(dpr);
pixmap.fill(Qt::transparent); pixmap.fill(Qt::transparent);
QPainter painter(&pixmap); QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing, points.size() < 500); painter.setRenderHint(QPainter::Antialiasing, render_points_.size() <= 500);
painter.setPen(color); painter.setPen(color);
painter.drawPolyline(points.data(), points.size()); painter.drawPolyline(render_points_.data(), render_points_.size());
painter.setPen(QPen(color, 3)); painter.setPen(QPen(color, 3));
if ((points.back().x() - points.front().x()) / points.size() > 8) { if (draw_individual_points) {
painter.drawPoints(points.data(), points.size()); painter.drawPoints(render_points_.data(), render_points_.size());
} else { } else {
painter.drawPoint(points.back()); painter.drawPoint(render_points_.back());
} }
} }

@ -9,7 +9,7 @@
class Sparkline { class Sparkline {
public: 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_; } inline double freq() const { return freq_; }
bool isEmpty() const { return pixmap.isNull(); } bool isEmpty() const { return pixmap.isNull(); }
@ -20,6 +20,7 @@ public:
private: private:
void render(const QColor &color, int range, QSize size); 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; double freq_ = 0;
}; };

@ -634,11 +634,12 @@ void SignalView::updateState(const std::set<MessageId> *msgs) {
QSize size(available_width - value_width, QSize size(available_width - value_width,
delegate->button_size.height() - style()->pixelMetric(QStyle::PM_FocusFrameVMargin) * 2); 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; QFutureSynchronizer<void> synchronizer;
for (int i = first_visible.row(); i <= last_visible.row(); ++i) { for (int i = first_visible.row(); i <= last_visible.row(); ++i) {
auto item = model->getItem(model->index(i, 1)); auto item = model->getItem(model->index(i, 1));
synchronizer.addFuture(QtConcurrent::run( 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(); synchronizer.waitForFinished();
} }

Loading…
Cancel
Save