cabana: display current time & values in chart (#28042)

* display current time&value in chart

* cleanup

* use macro

* use BrightText

* use x-square

* tiplabel: set point size 9

* add 1 point offset

* change Light color

* lighter color

* use const iterator to avoid implict sharing detachs
old-commit-hash: 45f1f764ea
beeps
Dean Lee 2 years ago committed by GitHub
parent 46e075aa36
commit 4a318e17bd
  1. 99
      tools/cabana/chart/chart.cc
  2. 2
      tools/cabana/chart/chart.h
  3. 2
      tools/cabana/chart/chartswidget.cc
  4. 3
      tools/cabana/chart/tiplabel.cc
  5. 15
      tools/cabana/dbc/dbc.cc
  6. 1
      tools/cabana/dbc/dbc.h
  7. 12
      tools/cabana/signalview.cc

@ -5,6 +5,7 @@
#include <QDrag> #include <QDrag>
#include <QGraphicsLayout> #include <QGraphicsLayout>
#include <QGraphicsDropShadowEffect> #include <QGraphicsDropShadowEffect>
#include <QGraphicsItemGroup>
#include <QGraphicsOpacityEffect> #include <QGraphicsOpacityEffect>
#include <QMenu> #include <QMenu>
#include <QMimeData> #include <QMimeData>
@ -17,6 +18,8 @@
#include "tools/cabana/chart/chartswidget.h" #include "tools/cabana/chart/chartswidget.h"
// ChartAxisElement's padding is 4 (https://codebrowser.dev/qt5/qtcharts/src/charts/axis/chartaxiselement_p.h.html)
const int AXIS_X_TOP_MARGIN = 4;
static inline bool xLessThan(const QPointF &p, float x) { return p.x() < x; } static inline bool xLessThan(const QPointF &p, float x) { return p.x() < x; }
ChartView::ChartView(const std::pair<double, double> &x_range, ChartsWidget *parent) : charts_widget(parent), tip_label(this), QChartView(nullptr, parent) { ChartView::ChartView(const std::pair<double, double> &x_range, ChartsWidget *parent) : charts_widget(parent), tip_label(this), QChartView(nullptr, parent) {
@ -39,6 +42,7 @@ ChartView::ChartView(const std::pair<double, double> &x_range, ChartsWidget *par
setRubberBand(QChartView::HorizontalRubberBand); setRubberBand(QChartView::HorizontalRubberBand);
setMouseTracking(true); setMouseTracking(true);
setTheme(settings.theme == DARK_THEME ? QChart::QChart::ChartThemeDark : QChart::ChartThemeLight); setTheme(settings.theme == DARK_THEME ? QChart::QChart::ChartThemeDark : QChart::ChartThemeLight);
signal_value_font.setPointSize(9);
QObject::connect(axis_y, &QValueAxis::rangeChanged, [this]() { resetChartCache(); }); QObject::connect(axis_y, &QValueAxis::rangeChanged, [this]() { resetChartCache(); });
QObject::connect(axis_y, &QAbstractAxis::titleTextChanged, [this]() { resetChartCache(); }); QObject::connect(axis_y, &QAbstractAxis::titleTextChanged, [this]() { resetChartCache(); });
@ -119,7 +123,7 @@ void ChartView::addSignal(const MessageId &msg_id, const cabana::Signal *sig) {
} }
bool ChartView::hasSignal(const MessageId &msg_id, const cabana::Signal *sig) const { bool ChartView::hasSignal(const MessageId &msg_id, const cabana::Signal *sig) const {
return std::any_of(sigs.begin(), sigs.end(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; }); return std::any_of(sigs.cbegin(), sigs.cend(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; });
} }
void ChartView::removeIf(std::function<bool(const SigItem &s)> predicate) { void ChartView::removeIf(std::function<bool(const SigItem &s)> predicate) {
@ -143,14 +147,14 @@ void ChartView::removeIf(std::function<bool(const SigItem &s)> predicate) {
} }
void ChartView::signalUpdated(const cabana::Signal *sig) { void ChartView::signalUpdated(const cabana::Signal *sig) {
if (std::any_of(sigs.begin(), sigs.end(), [=](auto &s) { return s.sig == sig; })) { if (std::any_of(sigs.cbegin(), sigs.cend(), [=](auto &s) { return s.sig == sig; })) {
updateTitle(); updateTitle();
updateSeries(sig); updateSeries(sig);
} }
} }
void ChartView::msgUpdated(MessageId id) { void ChartView::msgUpdated(MessageId id) {
if (std::any_of(sigs.begin(), sigs.end(), [=](auto &s) { return s.msg_id == id; })) if (std::any_of(sigs.cbegin(), sigs.cend(), [=](auto &s) { return s.msg_id == id; }))
updateTitle(); updateTitle();
} }
@ -189,10 +193,16 @@ void ChartView::updatePlotArea(int left_pos, bool force) {
qreal left, top, right, bottom; qreal left, top, right, bottom;
chart()->layout()->getContentsMargins(&left, &top, &right, &bottom); chart()->layout()->getContentsMargins(&left, &top, &right, &bottom);
chart()->legend()->setGeometry({move_icon->sceneBoundingRect().topRight(), manage_btn_proxy->sceneBoundingRect().bottomLeft()}); QSizeF legend_size = chart()->legend()->layout()->minimumSize();
legend_size.setWidth(manage_btn_proxy->sceneBoundingRect().left() - move_icon->sceneBoundingRect().right());
chart()->legend()->setGeometry({move_icon->sceneBoundingRect().topRight(), legend_size});
// add top space for signal value
int adjust_top = chart()->legend()->geometry().height() + QFontMetrics(signal_value_font).height() + 3;
adjust_top = std::max<int>(adjust_top, manage_btn_proxy->sceneBoundingRect().height() + style()->pixelMetric(QStyle::PM_LayoutTopMargin));
// add right space for x-axis label
QSizeF x_label_size = QFontMetrics(axis_x->labelsFont()).size(Qt::TextSingleLine, QString::number(axis_x->max(), 'f', 2)); QSizeF x_label_size = QFontMetrics(axis_x->labelsFont()).size(Qt::TextSingleLine, QString::number(axis_x->max(), 'f', 2));
x_label_size += QSizeF{5, 5}; x_label_size += QSizeF{5, 5};
int adjust_top = chart()->legend()->geometry().height() + style()->pixelMetric(QStyle::PM_LayoutTopMargin);
chart()->setPlotArea(rect().adjusted(align_to + left, adjust_top + top, -x_label_size.width() / 2 - right, -x_label_size.height() - bottom)); chart()->setPlotArea(rect().adjusted(align_to + left, adjust_top + top, -x_label_size.width() / 2 - right, -x_label_size.height() - bottom));
chart()->layout()->invalidate(); chart()->layout()->invalidate();
resetChartCache(); resetChartCache();
@ -229,11 +239,11 @@ void ChartView::updatePlot(double cur, double min, double max) {
void ChartView::updateSeriesPoints() { void ChartView::updateSeriesPoints() {
// Show points when zoomed in enough // Show points when zoomed in enough
for (auto &s : sigs) { for (auto &s : sigs) {
auto begin = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), xLessThan); auto begin = std::lower_bound(s.vals.cbegin(), s.vals.cend(), axis_x->min(), xLessThan);
auto end = std::lower_bound(begin, s.vals.end(), axis_x->max(), xLessThan); auto end = std::lower_bound(begin, s.vals.cend(), axis_x->max(), xLessThan);
if (begin != end) { if (begin != end) {
int num_points = std::max<int>((end - begin), 1); int num_points = std::max<int>((end - begin), 1);
QPointF right_pt = end == s.vals.end() ? s.vals.back() : *end; QPointF right_pt = end == s.vals.cend() ? s.vals.back() : *end;
double pixels_per_point = (chart()->mapToPosition(right_pt).x() - chart()->mapToPosition(*begin).x()) / num_points; double pixels_per_point = (chart()->mapToPosition(right_pt).x() - chart()->mapToPosition(*begin).x()) / num_points;
if (series_type == SeriesType::Scatter) { if (series_type == SeriesType::Scatter) {
@ -305,8 +315,8 @@ void ChartView::updateAxisY() {
unit.clear(); unit.clear();
} }
auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), xLessThan); auto first = std::lower_bound(s.vals.cbegin(), s.vals.cend(), axis_x->min(), xLessThan);
auto last = std::lower_bound(first, s.vals.end(), axis_x->max(), xLessThan); auto last = std::lower_bound(first, s.vals.cend(), axis_x->max(), xLessThan);
s.min = std::numeric_limits<double>::max(); s.min = std::numeric_limits<double>::max();
s.max = std::numeric_limits<double>::lowest(); s.max = std::numeric_limits<double>::lowest();
if (can->liveStreaming()) { if (can->liveStreaming()) {
@ -315,7 +325,7 @@ void ChartView::updateAxisY() {
if (it->y() > s.max) s.max = it->y(); if (it->y() > s.max) s.max = it->y();
} }
} else { } else {
auto [min_y, max_y] = s.segment_tree.minmax(std::distance(s.vals.begin(), first), std::distance(s.vals.begin(), last)); auto [min_y, max_y] = s.segment_tree.minmax(std::distance(s.vals.cbegin(), first), std::distance(s.vals.cbegin(), last));
s.min = min_y; s.min = min_y;
s.max = max_y; s.max = max_y;
} }
@ -530,8 +540,8 @@ void ChartView::showTip(double sec) {
if (s.series->isVisible()) { if (s.series->isVisible()) {
QString value = "--"; QString value = "--";
// use reverse iterator to find last item <= sec. // use reverse iterator to find last item <= sec.
auto it = std::lower_bound(s.vals.rbegin(), s.vals.rend(), sec, [](auto &p, double x) { return p.x() > x; }); auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), sec, [](auto &p, double x) { return p.x() > x; });
if (it != s.vals.rend() && it->x() >= axis_x->min()) { if (it != s.vals.crend() && it->x() >= axis_x->min()) {
value = QString::number(it->y()); value = QString::number(it->y());
s.track_pt = *it; s.track_pt = *it;
x = std::max(x, chart()->mapToPosition(*it).x()); x = std::max(x, chart()->mapToPosition(*it).x());
@ -640,14 +650,7 @@ void ChartView::drawBackground(QPainter *painter, const QRectF &rect) {
} }
void ChartView::drawForeground(QPainter *painter, const QRectF &rect) { void ChartView::drawForeground(QPainter *painter, const QRectF &rect) {
// draw time line drawTimeline(painter);
qreal x = chart()->mapToPosition(QPointF{cur_sec, 0}).x();
x = std::clamp(x, chart()->plotArea().left(), chart()->plotArea().right());
qreal y1 = chart()->plotArea().top() - 2;
qreal y2 = chart()->plotArea().bottom() + 2;
painter->setPen(QPen(chart()->titleBrush().color(), 2));
painter->drawLine(QPointF{x, y1}, QPointF{x, y2});
// draw track points // draw track points
painter->setPen(Qt::NoPen); painter->setPen(Qt::NoPen);
qreal track_line_x = -1; qreal track_line_x = -1;
@ -660,16 +663,17 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) {
} }
} }
if (track_line_x > 0) { if (track_line_x > 0) {
auto plot_area = chart()->plotArea();
painter->setPen(QPen(Qt::darkGray, 1, Qt::DashLine)); painter->setPen(QPen(Qt::darkGray, 1, Qt::DashLine));
painter->drawLine(QPointF{track_line_x, y1}, QPointF{track_line_x, y2}); painter->drawLine(QPointF{track_line_x, plot_area.top()}, QPointF{track_line_x, plot_area.bottom()});
} }
// paint points. OpenGL mode lacks certain features (such as showing points) // paint points. OpenGL mode lacks certain features (such as showing points)
painter->setPen(Qt::NoPen); painter->setPen(Qt::NoPen);
for (auto &s : sigs) { for (auto &s : sigs) {
if (s.series->useOpenGL() && s.series->isVisible() && s.series->pointsVisible()) { if (s.series->useOpenGL() && s.series->isVisible() && s.series->pointsVisible()) {
auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), xLessThan); auto first = std::lower_bound(s.vals.cbegin(), s.vals.cend(), axis_x->min(), xLessThan);
auto last = std::lower_bound(first, s.vals.end(), axis_x->max(), xLessThan); auto last = std::lower_bound(first, s.vals.cend(), axis_x->max(), xLessThan);
painter->setBrush(s.series->color()); painter->setBrush(s.series->color());
for (auto it = first; it != last; ++it) { for (auto it = first; it != last; ++it) {
painter->drawEllipse(chart()->mapToPosition(*it), 4, 4); painter->drawEllipse(chart()->mapToPosition(*it), 4, 4);
@ -683,9 +687,8 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) {
painter->setPen(Qt::white); painter->setPen(Qt::white);
auto rubber_rect = rubber->geometry().normalized(); auto rubber_rect = rubber->geometry().normalized();
for (const auto &pt : {rubber_rect.bottomLeft(), rubber_rect.bottomRight()}) { for (const auto &pt : {rubber_rect.bottomLeft(), rubber_rect.bottomRight()}) {
QString sec = QString::number(chart()->mapToValue(pt).x(), 'f', 1); QString sec = QString::number(chart()->mapToValue(pt).x(), 'f', 2);
// ChartAxisElement's padding is 4 (https://codebrowser.dev/qt5/qtcharts/src/charts/axis/chartaxiselement_p.h.html) auto r = painter->fontMetrics().boundingRect(sec).adjusted(-6, -AXIS_X_TOP_MARGIN, 6, AXIS_X_TOP_MARGIN);
auto r = painter->fontMetrics().boundingRect(sec).adjusted(-6, -4, 6, 4);
pt == rubber_rect.bottomLeft() ? r.moveTopRight(pt + QPoint{0, 2}) : r.moveTopLeft(pt + QPoint{0, 2}); pt == rubber_rect.bottomLeft() ? r.moveTopRight(pt + QPoint{0, 2}) : r.moveTopLeft(pt + QPoint{0, 2});
painter->fillRect(r, Qt::gray); painter->fillRect(r, Qt::gray);
painter->drawText(r, Qt::AlignCenter, sec); painter->drawText(r, Qt::AlignCenter, sec);
@ -693,6 +696,48 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) {
} }
} }
void ChartView::drawTimeline(QPainter *painter) {
const auto plot_area = chart()->plotArea();
// draw line
qreal x = std::clamp(chart()->mapToPosition(QPointF{cur_sec, 0}).x(), plot_area.left(), plot_area.right());
painter->setPen(QPen(chart()->titleBrush().color(), 2));
painter->drawLine(QPointF{x, plot_area.top()}, QPointF{x, plot_area.bottom() + 1});
// draw current time
QString time_str = QString::number(cur_sec, 'f', 2);
QSize time_str_size = QFontMetrics(axis_x->labelsFont()).size(Qt::TextSingleLine, time_str) + QSize(8, 2);
QRect time_str_rect(QPoint(x - time_str_size.width() / 2, plot_area.bottom() + AXIS_X_TOP_MARGIN), time_str_size);
QPainterPath path;
path.addRoundedRect(time_str_rect, 3, 3);
painter->fillPath(path, settings.theme == DARK_THEME ? Qt::darkGray : Qt::gray);
painter->setPen(palette().color(QPalette::BrightText));
painter->setFont(axis_x->labelsFont());
painter->drawText(time_str_rect, Qt::AlignCenter, time_str);
// draw signal value
auto item_group = qgraphicsitem_cast<QGraphicsItemGroup *>(chart()->legend()->childItems()[0]);
assert(item_group != nullptr);
auto legend_markers = item_group->childItems();
assert(legend_markers.size() == sigs.size());
painter->setFont(signal_value_font);
painter->setPen(chart()->legend()->labelColor());
int i = 0;
for (auto &s : sigs) {
QString value = "--";
if (s.series->isVisible()) {
auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), cur_sec, [](auto &p, double x) { return p.x() > x; });
if (it != s.vals.crend() && it->x() >= axis_x->min()) {
value = s.sig->formatValue(it->y());
}
}
QRectF marker_rect = legend_markers[i++]->sceneBoundingRect();
QRectF value_rect(marker_rect.bottomLeft() - QPoint(0, 1), marker_rect.size());
QString elided_val = painter->fontMetrics().elidedText(value, Qt::ElideRight, value_rect.width());
painter->drawText(value_rect, Qt::AlignHCenter | Qt::AlignTop, elided_val);
}
}
QXYSeries *ChartView::createSeries(SeriesType type, QColor color) { QXYSeries *ChartView::createSeries(SeriesType type, QColor color) {
QXYSeries *series = nullptr; QXYSeries *series = nullptr;
if (type == SeriesType::Line) { if (type == SeriesType::Line) {

@ -80,6 +80,7 @@ private:
void drawForeground(QPainter *painter, const QRectF &rect) override; void drawForeground(QPainter *painter, const QRectF &rect) override;
void drawBackground(QPainter *painter, const QRectF &rect) override; void drawBackground(QPainter *painter, const QRectF &rect) override;
void drawDropIndicator(bool draw) { if (std::exchange(can_drop, draw) != can_drop) viewport()->update(); } void drawDropIndicator(bool draw) { if (std::exchange(can_drop, draw) != can_drop) viewport()->update(); }
void drawTimeline(QPainter *painter);
std::tuple<double, double, int> getNiceAxisNumbers(qreal min, qreal max, int tick_count); std::tuple<double, double, int> getNiceAxisNumbers(qreal min, qreal max, int tick_count);
qreal niceNumber(qreal x, bool ceiling); qreal niceNumber(qreal x, bool ceiling);
QXYSeries *createSeries(SeriesType type, QColor color); QXYSeries *createSeries(SeriesType type, QColor color);
@ -104,6 +105,7 @@ private:
QPixmap chart_pixmap; QPixmap chart_pixmap;
bool can_drop = false; bool can_drop = false;
double tooltip_x = -1; double tooltip_x = -1;
QFont signal_value_font;
ChartsWidget *charts_widget; ChartsWidget *charts_widget;
friend class ChartsWidget; friend class ChartsWidget;
}; };

@ -60,7 +60,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), auto_scroll_tim
reset_zoom_action = toolbar->addWidget(reset_zoom_btn = new ToolButton("zoom-out", tr("Reset Zoom"))); reset_zoom_action = toolbar->addWidget(reset_zoom_btn = new ToolButton("zoom-out", tr("Reset Zoom")));
reset_zoom_btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); reset_zoom_btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toolbar->addWidget(remove_all_btn = new ToolButton("x", tr("Remove all charts"))); toolbar->addWidget(remove_all_btn = new ToolButton("x-square", tr("Remove all charts")));
toolbar->addWidget(dock_btn = new ToolButton("")); toolbar->addWidget(dock_btn = new ToolButton(""));
main_layout->addWidget(toolbar); main_layout->addWidget(toolbar);

@ -9,6 +9,9 @@
TipLabel::TipLabel(QWidget *parent) : QLabel(parent, Qt::ToolTip | Qt::FramelessWindowHint) { TipLabel::TipLabel(QWidget *parent) : QLabel(parent, Qt::ToolTip | Qt::FramelessWindowHint) {
setForegroundRole(QPalette::ToolTipText); setForegroundRole(QPalette::ToolTipText);
setBackgroundRole(QPalette::ToolTipBase); setBackgroundRole(QPalette::ToolTipBase);
QFont font;
font.setPointSizeF(8.34563465);
setFont(font);
auto palette = QToolTip::palette(); auto palette = QToolTip::palette();
if (settings.theme != DARK_THEME) { if (settings.theme != DARK_THEME) {
palette.setColor(QPalette::ToolTipBase, QApplication::palette().color(QPalette::Base)); palette.setColor(QPalette::ToolTipBase, QApplication::palette().color(QPalette::Base));

@ -17,6 +17,21 @@ void cabana::Signal::updatePrecision() {
precision = std::max(num_decimals(factor), num_decimals(offset)); precision = std::max(num_decimals(factor), num_decimals(offset));
} }
QString cabana::Signal::formatValue(double value) const {
// Show enum string
for (auto &[val, desc] : val_desc) {
if (std::abs(value - val.toInt()) < 1e-6) {
return desc;
}
}
QString val_str = QString::number(value, 'f', precision);
if (!unit.isEmpty()) {
val_str += " " + unit;
}
return val_str;
}
// helper functions // helper functions
static QVector<int> BIG_ENDIAN_START_BITS = []() { static QVector<int> BIG_ENDIAN_START_BITS = []() {

@ -57,6 +57,7 @@ namespace cabana {
ValueDescription val_desc; ValueDescription val_desc;
int precision = 0; int precision = 0;
void updatePrecision(); void updatePrecision();
QString formatValue(double value) const;
}; };
struct Msg { struct Msg {

@ -595,17 +595,7 @@ void SignalView::updateState(const QHash<MessageId, CanData> *msgs) {
const auto &last_msg = can->lastMessage(model->msg_id); const auto &last_msg = can->lastMessage(model->msg_id);
for (auto item : model->root->children) { for (auto item : model->root->children) {
double value = get_raw_value((uint8_t *)last_msg.dat.constData(), last_msg.dat.size(), *item->sig); double value = get_raw_value((uint8_t *)last_msg.dat.constData(), last_msg.dat.size(), *item->sig);
item->sig_val = QString::number(value, 'f', item->sig->precision); item->sig_val = item->sig->formatValue(value);
// Show unit
if (!item->sig->unit.isEmpty()) {
item->sig_val += " " + item->sig->unit;
}
// Show enum string
for (auto &[val, desc] : item->sig->val_desc) {
if (std::abs(value - val.toInt()) < 1e-6) {
item->sig_val = desc;
}
}
max_value_width = std::max(max_value_width, fontMetrics().width(item->sig_val)); max_value_width = std::max(max_value_width, fontMetrics().width(item->sig_val));
} }

Loading…
Cancel
Save