cabana: add a slider to set sparkline time range (#27727)

* add slider to set sparkline time range

* rename signaleedit to signalview

* default is 15s

* util::formatSeconds

* update() is faster than invalidate

* draw points

* fix scatter width
old-commit-hash: d6961152b8
beeps
Dean Lee 2 years ago committed by GitHub
parent 6f52cc09f4
commit 9897bc83c4
  1. 2
      tools/cabana/SConscript
  2. 2
      tools/cabana/binaryview.cc
  3. 30
      tools/cabana/chartswidget.cc
  4. 2
      tools/cabana/detailwidget.h
  5. 2
      tools/cabana/settings.cc
  6. 3
      tools/cabana/settings.h
  7. 45
      tools/cabana/signalview.cc
  8. 5
      tools/cabana/signalview.h
  9. 4
      tools/cabana/util.h
  10. 13
      tools/cabana/videowidget.cc

@ -28,7 +28,7 @@ cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "asset
prev_moc_path = cabana_env['QT_MOCHPREFIX'] prev_moc_path = cabana_env['QT_MOCHPREFIX']
cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_' cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_'
cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc',
'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc',
'commands.cc', 'messageswidget.cc', 'route.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) 'commands.cc', 'messageswidget.cc', 'route.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)

@ -11,7 +11,7 @@
#include <QToolTip> #include <QToolTip>
#include "tools/cabana/commands.h" #include "tools/cabana/commands.h"
#include "tools/cabana/signaledit.h" #include "tools/cabana/signalview.h"
// BinaryView // BinaryView

@ -189,7 +189,7 @@ void ChartsWidget::setMaxChartRange(int value) {
void ChartsWidget::updateToolBar() { void ChartsWidget::updateToolBar() {
title_label->setText(tr("Charts: %1").arg(charts.size())); title_label->setText(tr("Charts: %1").arg(charts.size()));
columns_action->setText(tr("Column: %1").arg(column_count)); columns_action->setText(tr("Column: %1").arg(column_count));
range_lb->setText(QString("Range: %1:%2 ").arg(max_chart_range / 60, 2, 10, QLatin1Char('0')).arg(max_chart_range % 60, 2, 10, QLatin1Char('0'))); range_lb->setText(QString("Range: %1 ").arg(utils::formatSeconds(max_chart_range)));
range_lb_action->setVisible(!is_zoomed); range_lb_action->setVisible(!is_zoomed);
range_slider_action->setVisible(!is_zoomed); range_slider_action->setVisible(!is_zoomed);
undo_zoom_action->setVisible(is_zoomed); undo_zoom_action->setVisible(is_zoomed);
@ -528,7 +528,7 @@ void ChartView::updatePlot(double cur, double min, double max) {
updateAxisY(); updateAxisY();
updateSeriesPoints(); updateSeriesPoints();
} }
scene()->invalidate({}, QGraphicsScene::ForegroundLayer); update();
} }
void ChartView::updateSeriesPoints() { void ChartView::updateSeriesPoints() {
@ -536,14 +536,16 @@ void ChartView::updateSeriesPoints() {
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.begin(), s.vals.end(), 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.end(), axis_x->max(), xLessThan);
if (begin != end) {
int num_points = std::max<int>(end - begin, 1); int num_points = std::max<int>((end - begin), 1);
int pixels_per_point = width() / num_points; QPointF right_pt = end == s.vals.end() ? s.vals.back() : *end;
double pixels_per_point = (chart()->mapToPosition(right_pt).x() - chart()->mapToPosition(*begin).x()) / num_points;
if (series_type == SeriesType::Scatter) {
((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 3, 2, 8) * devicePixelRatioF()); if (series_type == SeriesType::Scatter) {
} else { ((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 2.0, 2.0, 8.0) * devicePixelRatioF());
s.series->setPointsVisible(pixels_per_point > 20); } else {
s.series->setPointsVisible(pixels_per_point > 20);
}
} }
} }
} }
@ -715,7 +717,7 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
// zoom in if selected range is greater than 0.5s // zoom in if selected range is greater than 0.5s
emit zoomIn(min_rounded, max_rounded); emit zoomIn(min_rounded, max_rounded);
} else { } else {
scene()->invalidate({}, QGraphicsScene::ForegroundLayer); update();
} }
event->accept(); event->accept();
} else if (!can->liveStreaming() && event->button() == Qt::RightButton) { } else if (!can->liveStreaming() && event->button() == Qt::RightButton) {
@ -773,7 +775,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
text_list.push_front(QString::number(chart()->mapToValue({x, 0}).x(), 'f', 3)); text_list.push_front(QString::number(chart()->mapToValue({x, 0}).x(), 'f', 3));
QPointF tooltip_pt(x + 12, plot_area.top() - 20); QPointF tooltip_pt(x + 12, plot_area.top() - 20);
QToolTip::showText(mapToGlobal(tooltip_pt.toPoint()), text_list.join("<br />"), this, plot_area.toRect()); QToolTip::showText(mapToGlobal(tooltip_pt.toPoint()), text_list.join("<br />"), this, plot_area.toRect());
scene()->invalidate({}, QGraphicsScene::ForegroundLayer); update();
} else { } else {
QToolTip::hideText(); QToolTip::hideText();
} }
@ -786,7 +788,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
if (rubber_rect != rubber->geometry()) { if (rubber_rect != rubber->geometry()) {
rubber->setGeometry(rubber_rect); rubber->setGeometry(rubber_rect);
} }
scene()->invalidate({}, QGraphicsScene::ForegroundLayer); update();
} }
} }
@ -847,8 +849,8 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) {
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.begin(), s.vals.end(), 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.end(), axis_x->max(), xLessThan);
painter->setBrush(s.series->color());
for (auto it = first; it != last; ++it) { for (auto it = first; it != last; ++it) {
painter->setBrush(s.series->color());
painter->drawEllipse(chart()->mapToPosition(*it), 4, 4); painter->drawEllipse(chart()->mapToPosition(*it), 4, 4);
} }
} }

@ -8,7 +8,7 @@
#include "tools/cabana/binaryview.h" #include "tools/cabana/binaryview.h"
#include "tools/cabana/chartswidget.h" #include "tools/cabana/chartswidget.h"
#include "tools/cabana/historylog.h" #include "tools/cabana/historylog.h"
#include "tools/cabana/signaledit.h" #include "tools/cabana/signalview.h"
class EditMessageDialog : public QDialog { class EditMessageDialog : public QDialog {
public: public:

@ -27,6 +27,7 @@ void Settings::save() {
s.setValue("recent_files", recent_files); s.setValue("recent_files", recent_files);
s.setValue("message_header_state", message_header_state); s.setValue("message_header_state", message_header_state);
s.setValue("chart_series_type", chart_series_type); s.setValue("chart_series_type", chart_series_type);
s.setValue("sparkline_range", sparkline_range);
} }
void Settings::load() { void Settings::load() {
@ -44,6 +45,7 @@ void Settings::load() {
recent_files = s.value("recent_files").toStringList(); recent_files = s.value("recent_files").toStringList();
message_header_state = s.value("message_header_state").toByteArray(); message_header_state = s.value("message_header_state").toByteArray();
chart_series_type = s.value("chart_series_type", 0).toInt(); chart_series_type = s.value("chart_series_type", 0).toInt();
sparkline_range = s.value("sparkline_range", 15).toInt();
} }
// SettingsDlg // SettingsDlg

@ -17,8 +17,9 @@ public:
int max_cached_minutes = 30; int max_cached_minutes = 30;
int chart_height = 200; int chart_height = 200;
int chart_column_count = 1; int chart_column_count = 1;
int chart_range = 3 * 60; // e minutes int chart_range = 3 * 60; // 3 minutes
int chart_series_type = 0; int chart_series_type = 0;
int sparkline_range = 15; // 15 seconds
QString last_dir; QString last_dir;
QString last_route_dir; QString last_route_dir;
QByteArray geometry; QByteArray geometry;

@ -1,4 +1,4 @@
#include "tools/cabana/signaledit.h" #include "tools/cabana/signalview.h"
#include <QApplication> #include <QApplication>
#include <QCompleter> #include <QCompleter>
@ -366,17 +366,15 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
void SignalItemDelegate::drawSparkline(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { void SignalItemDelegate::drawSparkline(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
static std::vector<QPointF> points; 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 &msg_id = ((SignalView *)parent())->msg_id;
const auto &msgs = can->events().at(msg_id); const auto &msgs = can->events().at(msg_id);
uint64_t ts = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9; 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)}); auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), CanEvent{.mono_time = (uint64_t)std::max<int64_t>(ts - settings.sparkline_range * 1e9, 0)});
if (first != msgs.cend()) { auto last = std::upper_bound(first, msgs.cend(), CanEvent{.mono_time = ts});
if (first != last) {
double min = std::numeric_limits<double>::max(); double min = std::numeric_limits<double>::max();
double max = std::numeric_limits<double>::lowest(); double max = std::numeric_limits<double>::lowest();
const auto sig = ((SignalModel::Item *)index.internalPointer())->sig; const auto sig = ((SignalModel::Item *)index.internalPointer())->sig;
auto last = std::lower_bound(first, msgs.cend(), CanEvent{.mono_time = ts});
points.clear(); points.clear();
for (auto it = first; it != last; ++it) { for (auto it = first; it != last; ++it) {
double value = get_raw_value(it->dat, it->size, *sig); double value = get_raw_value(it->dat, it->size, *sig);
@ -391,7 +389,7 @@ void SignalItemDelegate::drawSparkline(QPainter *painter, const QStyleOptionView
int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin); int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin);
int v_margin = std::max(option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin) + 2, 4); 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 xscale = (option.rect.width() - 175.0 * option.widget->devicePixelRatioF() - h_margin * 2) / settings.sparkline_range;
const double yscale = (option.rect.height() - v_margin * 2) / (max - min); const double yscale = (option.rect.height() - v_margin * 2) / (max - min);
const int left = option.rect.left(); const int left = option.rect.left();
const int top = option.rect.top() + v_margin; const int top = option.rect.top() + v_margin;
@ -401,6 +399,13 @@ void SignalItemDelegate::drawSparkline(QPainter *painter, const QStyleOptionView
} }
painter->setPen(getColor(sig)); painter->setPen(getColor(sig));
painter->drawPolyline(points.data(), points.size()); painter->drawPolyline(points.data(), points.size());
if ((points.back().x() - points.front().x()) / points.size() > 10) {
painter->setPen(Qt::NoPen);
painter->setBrush(getColor(sig));
for (const auto &pt : points) {
painter->drawEllipse(pt, 2, 2);
}
}
} }
} }
@ -451,6 +456,17 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts),
filter_edit->setPlaceholderText(tr("filter signals")); filter_edit->setPlaceholderText(tr("filter signals"));
hl->addWidget(filter_edit); hl->addWidget(filter_edit);
hl->addStretch(1); hl->addStretch(1);
// WARNING: increasing the maximum range can result in severe performance degradation.
// 30s is a reasonable value at present.
const int max_range = 30; // 30s
settings.sparkline_range = std::clamp(settings.sparkline_range, 1, max_range);
hl->addWidget(sparkline_label = new QLabel());
hl->addWidget(sparkline_range_slider = new QSlider(Qt::Horizontal, this));
sparkline_range_slider->setRange(1, max_range);
sparkline_range_slider->setValue(settings.sparkline_range);
sparkline_range_slider->setToolTip(tr("Sparkline time range"));
auto collapse_btn = toolButton("dash-square", tr("Collapse All")); auto collapse_btn = toolButton("dash-square", tr("Collapse All"));
collapse_btn->setIconSize({12, 12}); collapse_btn->setIconSize({12, 12});
hl->addWidget(collapse_btn); hl->addWidget(collapse_btn);
@ -473,8 +489,10 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts),
main_layout->setSpacing(0); main_layout->setSpacing(0);
main_layout->addWidget(title_bar); main_layout->addWidget(title_bar);
main_layout->addWidget(tree); main_layout->addWidget(tree);
updateToolBar();
QObject::connect(filter_edit, &QLineEdit::textEdited, model, &SignalModel::setFilter); QObject::connect(filter_edit, &QLineEdit::textEdited, model, &SignalModel::setFilter);
QObject::connect(sparkline_range_slider, &QSlider::valueChanged, this, &SignalView::setSparklineRange);
QObject::connect(collapse_btn, &QPushButton::clicked, tree, &QTreeView::collapseAll); QObject::connect(collapse_btn, &QPushButton::clicked, tree, &QTreeView::collapseAll);
QObject::connect(tree, &QAbstractItemView::clicked, this, &SignalView::rowClicked); QObject::connect(tree, &QAbstractItemView::clicked, this, &SignalView::rowClicked);
QObject::connect(tree, &QTreeView::viewportEntered, [this]() { emit highlight(nullptr); }); QObject::connect(tree, &QTreeView::viewportEntered, [this]() { emit highlight(nullptr); });
@ -521,7 +539,7 @@ void SignalView::rowsChanged() {
}); });
} }
} }
signal_count_lb->setText(tr("Signals: %1").arg(model->rowCount())); updateToolBar();
updateChartState(); updateChartState();
} }
@ -569,6 +587,17 @@ void SignalView::signalHovered(const cabana::Signal *sig) {
} }
} }
void SignalView::updateToolBar() {
signal_count_lb->setText(tr("Signals: %1").arg(model->rowCount()));
sparkline_label->setText(QString("Range: %1 ").arg(utils::formatSeconds(settings.sparkline_range)));
}
void SignalView::setSparklineRange(int value) {
settings.sparkline_range = value;
updateToolBar();
model->updateState(nullptr);
}
void SignalView::leaveEvent(QEvent *event) { void SignalView::leaveEvent(QEvent *event) {
emit highlight(nullptr); emit highlight(nullptr);
QWidget::leaveEvent(event); QWidget::leaveEvent(event);

@ -3,6 +3,7 @@
#include <QAbstractItemModel> #include <QAbstractItemModel>
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QSlider>
#include <QStyledItemDelegate> #include <QStyledItemDelegate>
#include <QTableWidget> #include <QTableWidget>
#include <QTreeView> #include <QTreeView>
@ -109,6 +110,8 @@ signals:
private: private:
void rowsChanged(); void rowsChanged();
void leaveEvent(QEvent *event); void leaveEvent(QEvent *event);
void updateToolBar();
void setSparklineRange(int value);
struct TreeView : public QTreeView { struct TreeView : public QTreeView {
TreeView(QWidget *parent) : QTreeView(parent) {} TreeView(QWidget *parent) : QTreeView(parent) {}
@ -120,6 +123,8 @@ private:
}; };
TreeView *tree; TreeView *tree;
QLabel *sparkline_label;
QSlider *sparkline_range_slider;
QLineEdit *filter_edit; QLineEdit *filter_edit;
ChartsWidget *charts; ChartsWidget *charts;
QLabel *signal_count_lb; QLabel *signal_count_lb;

@ -4,6 +4,7 @@
#include <cmath> #include <cmath>
#include <QByteArray> #include <QByteArray>
#include <QDateTime>
#include <QColor> #include <QColor>
#include <QFont> #include <QFont>
#include <QRegExpValidator> #include <QRegExpValidator>
@ -97,6 +98,9 @@ public:
namespace utils { namespace utils {
QPixmap icon(const QString &id); QPixmap icon(const QString &id);
inline QString formatSeconds(int seconds) {
return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss");
}
} }
QToolButton *toolButton(const QString &icon, const QString &tooltip); QToolButton *toolButton(const QString &icon, const QString &tooltip);

@ -1,7 +1,6 @@
#include "tools/cabana/videowidget.h" #include "tools/cabana/videowidget.h"
#include <QButtonGroup> #include <QButtonGroup>
#include <QDateTime>
#include <QMouseEvent> #include <QMouseEvent>
#include <QPainter> #include <QPainter>
#include <QStyleOptionSlider> #include <QStyleOptionSlider>
@ -20,10 +19,6 @@ static const QColor timeline_colors[] = {
[(int)TimelineType::AlertCritical] = QColor(199, 0, 57), [(int)TimelineType::AlertCritical] = QColor(199, 0, 57),
}; };
static inline QString formatTime(int seconds) {
return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss");
}
VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) { VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
setFrameStyle(QFrame::StyledPanel | QFrame::Plain); setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
auto main_layout = new QVBoxLayout(this); auto main_layout = new QVBoxLayout(this);
@ -101,11 +96,11 @@ QWidget *VideoWidget::createCameraWidget() {
slider_layout->addWidget(end_time_label); slider_layout->addWidget(end_time_label);
l->addLayout(slider_layout); l->addLayout(slider_layout);
QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); }); QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); });
QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(formatTime(value / 1000)); }); QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(utils::formatSeconds(value / 1000)); });
QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); }); QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); });
QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState); QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState);
QObject::connect(can, &AbstractStream::streamStarted, [this]() { QObject::connect(can, &AbstractStream::streamStarted, [this]() {
end_time_label->setText(formatTime(can->totalSeconds())); end_time_label->setText(utils::formatSeconds(can->totalSeconds()));
slider->setRange(0, can->totalSeconds() * 1000); slider->setRange(0, can->totalSeconds() * 1000);
}); });
return w; return w;
@ -116,7 +111,7 @@ void VideoWidget::rangeChanged(double min, double max, bool is_zoomed) {
min = 0; min = 0;
max = can->totalSeconds(); max = can->totalSeconds();
} }
end_time_label->setText(formatTime(max)); end_time_label->setText(utils::formatSeconds(max));
slider->setRange(min * 1000, max * 1000); slider->setRange(min * 1000, max * 1000);
} }
@ -230,7 +225,7 @@ void Slider::mouseMoveEvent(QMouseEvent *e) {
} }
int x = std::clamp(e->pos().x() - thumb.width() / 2, THUMBNAIL_MARGIN, rect().right() - thumb.width() - THUMBNAIL_MARGIN); int x = std::clamp(e->pos().x() - thumb.width() / 2, THUMBNAIL_MARGIN, rect().right() - thumb.width() - THUMBNAIL_MARGIN);
int y = -thumb.height() - THUMBNAIL_MARGIN - style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing); int y = -thumb.height() - THUMBNAIL_MARGIN - style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing);
thumbnail_label.showPixmap(mapToGlobal({x, y}), formatTime(seconds), thumb); thumbnail_label.showPixmap(mapToGlobal({x, y}), utils::formatSeconds(seconds), thumb);
QSlider::mouseMoveEvent(e); QSlider::mouseMoveEvent(e);
} }

Loading…
Cancel
Save