mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-02-18 21:14:01 +08:00
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
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user