cabana: add sparkline to signal view (#27717)

* display small line chart beside signal

* TODO

* 1 px width

* auto stretch

* static lines

* use std::vector

* cleanup

* use ts from last message

* remove hardcorded size
old-commit-hash: eee3c8ee60
beeps
Dean Lee 2 years ago committed by GitHub
parent d2fcf6dbae
commit 7ffea181b7
  1. 2
      tools/cabana/chartswidget.cc
  2. 64
      tools/cabana/signaledit.cc
  3. 4
      tools/cabana/signaledit.h

@ -558,7 +558,7 @@ void ChartView::updateSeries(const cabana::Signal *sig) {
}
s.series->setColor(getColor(s.sig));
auto msgs = can->events().at(s.msg_id);
const auto &msgs = can->events().at(s.msg_id);
auto first = std::upper_bound(msgs.cbegin(), msgs.cend(), CanEvent{.mono_time = s.last_value_mono_time});
int new_size = std::max<int>(s.vals.size() + std::distance(first, msgs.cend()), settings.max_cached_minutes * 60 * 100);
if (s.vals.capacity() <= new_size) {

@ -62,14 +62,11 @@ void SignalModel::updateState(const QHash<MessageId, CanData> *msgs) {
auto &dat = can->lastMessage(msg_id).dat;
int row = 0;
for (auto item : root->children) {
QString value = QString::number(get_raw_value((uint8_t *)dat.begin(), dat.size(), *item->sig), 'f', item->sig->precision);
if (!item->sig->unit.isEmpty()){
value += " " + item->sig->unit;
}
if (value != item->sig_val) {
item->sig_val = value;
emit dataChanged(index(row, 1), index(row, 1), {Qt::DisplayRole});
item->sig_val = QString::number(get_raw_value((uint8_t *)dat.constData(), dat.size(), *item->sig), 'f', item->sig->precision);
if (!item->sig->unit.isEmpty()) {
item->sig_val += " " + item->sig->unit;
}
emit dataChanged(index(row, 1), index(row, 1), {Qt::DisplayRole});
++row;
}
}
@ -321,6 +318,8 @@ QSize SignalItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QMo
}
void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
int v_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin);
auto item = (SignalModel::Item *)index.internalPointer();
if (index.column() == 0 && item && item->type == SignalModel::Item::Sig) {
painter->save();
@ -331,8 +330,6 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
// color label
auto bg_color = getColor(item->sig);
int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
int v_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin);
QRect rc{option.rect.left() + h_margin, option.rect.top(), color_label_width, option.rect.height()};
painter->setPen(Qt::NoPen);
painter->setBrush(item->highlight ? bg_color.darker(125) : bg_color);
@ -351,19 +348,62 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
painter->drawText(text_rect, option.displayAlignment, text);
painter->restore();
} else if (index.column() == 1 && item && item->type == SignalModel::Item::Sig) {
// draw signal value
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
}
painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text));
QRect rc = option.rect.adjusted(0, 0, -70, 0);
drawSparkline(painter, option, index);
// draw signal value
int right_offset = ((SignalView *)parent())->tree->indexWidget(index)->sizeHint().width() + 2 * h_margin;
QRect rc = option.rect.adjusted(0, 0, -right_offset, 0);
auto text = painter->fontMetrics().elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, rc.width());
painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text));
painter->drawText(rc, Qt::AlignRight | Qt::AlignVCenter, text);
} else {
QStyledItemDelegate::paint(painter, option, index);
}
}
void SignalItemDelegate::drawSparkline(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
static std::vector<QPointF> points;
// TODO: get seconds from settings.
const uint64_t chart_seconds = 15; // seconds
const auto &msg_id = ((SignalView *)parent())->msg_id;
const auto &msgs = can->events().at(msg_id);
uint64_t ts = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9;
auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), CanEvent{.mono_time = (uint64_t)std::max<int64_t>(ts - chart_seconds * 1e9, 0)});
if (first != msgs.cend()) {
double min = std::numeric_limits<double>::max();
double max = std::numeric_limits<double>::lowest();
const auto sig = ((SignalModel::Item *)index.internalPointer())->sig;
auto last = std::lower_bound(first, msgs.cend(), CanEvent{.mono_time = ts});
points.clear();
for (auto it = first; it != last; ++it) {
double value = get_raw_value(it->dat, it->size, *sig);
points.emplace_back((it->mono_time - first->mono_time) / 1e9, value);
min = std::min(min, value);
max = std::max(max, value);
}
if (min == max) {
min -= 1;
max += 1;
}
int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin);
int v_margin = std::max(option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin) + 2, 4);
const double xscale = (option.rect.width() - 175.0 * option.widget->devicePixelRatioF() - h_margin * 2) / chart_seconds;
const double yscale = (option.rect.height() - v_margin * 2) / (max - min);
const int left = option.rect.left();
const int top = option.rect.top() + v_margin;
for (auto &pt : points) {
pt.rx() = left + pt.x() * xscale;
pt.ry() = top + std::abs(pt.y() - max) * yscale;
}
painter->setPen(getColor(sig));
painter->drawPolyline(points.data(), points.size());
}
}
QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
auto item = (SignalModel::Item *)index.internalPointer();
if (item->type == SignalModel::Item::Name || item->type == SignalModel::Item::Offset ||

@ -82,6 +82,7 @@ public:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void drawSparkline(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QValidator *name_validator, *double_validator;
QFont small_font;
const int color_label_width = 18;
@ -99,6 +100,7 @@ public:
void selectSignal(const cabana::Signal *sig, bool expand = false);
void rowClicked(const QModelIndex &index);
SignalModel *model = nullptr;
MessageId msg_id;
signals:
void highlight(const cabana::Signal *sig);
@ -117,9 +119,9 @@ private:
}
};
MessageId msg_id;
TreeView *tree;
QLineEdit *filter_edit;
ChartsWidget *charts;
QLabel *signal_count_lb;
friend SignalItemDelegate;
};

Loading…
Cancel
Save