|
|
|
@ -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)); |
|
|
|
|
void Sparkline::update(const cabana::Signal *sig, CanEventIter first, CanEventIter last, int range, QSize size) { |
|
|
|
|
if (first == last || size.isEmpty()) { |
|
|
|
|
pixmap = QPixmap(); |
|
|
|
|
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) { |
|
|
|
|
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(); |
|
|
|
|
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); |
|
|
|
|
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) { |
|
|
|
|
// 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); |
|
|
|
|
for (auto &v : points) { |
|
|
|
|
v = QPoint(v.x() * xscale, 1 + std::abs(v.y() - max_val) * yscale); |
|
|
|
|
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()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|