diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index 2bd6cfd6cc..dee7cc5248 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -279,38 +279,50 @@ void ChartView::updateSeriesPoints() { } } -void ChartView::updateSeries(const cabana::Signal *sig, bool clear) { +void ChartView::appendCanEvents(const cabana::Signal *sig, const std::vector &events, + std::vector &vals, std::vector &step_vals) { + vals.reserve(vals.size() + events.capacity()); + step_vals.reserve(step_vals.size() + events.capacity() * 2); + + double value = 0; + const uint64_t begin_mono_time = can->routeStartTime() * 1e9; + for (const CanEvent *e : events) { + if (sig->getValue(e->dat, e->size, &value)) { + const double ts = (e->mono_time - std::min(e->mono_time, begin_mono_time)) / 1e9; + vals.emplace_back(ts, value); + if (!step_vals.empty()) + step_vals.emplace_back(ts, step_vals.back().y()); + step_vals.emplace_back(ts, value); + } + } +} + +void ChartView::updateSeries(const cabana::Signal *sig, const MessageEventsMap *msg_new_events) { for (auto &s : sigs) { if (!sig || s.sig == sig) { - if (clear) { + if (!msg_new_events) { s.vals.clear(); s.step_vals.clear(); - s.last_value_mono_time = 0; } + auto events = msg_new_events ? msg_new_events : &can->eventsMap(); + auto it = events->find(s.msg_id); + if (it == events->end() || it->second.empty()) continue; - const auto &msgs = can->events(s.msg_id); - s.vals.reserve(msgs.capacity()); - s.step_vals.reserve(msgs.capacity() * 2); - - auto first = std::upper_bound(msgs.cbegin(), msgs.cend(), s.last_value_mono_time, CompareCanEvent()); - const double route_start_time = can->routeStartTime(); - for (auto end = msgs.cend(); first != end; ++first) { - const CanEvent *e = *first; - double value = 0; - if (s.sig->getValue(e->dat, e->size, &value)) { - double ts = e->mono_time / 1e9 - route_start_time; // seconds - s.vals.append({ts, value}); - if (!s.step_vals.empty()) { - s.step_vals.append({ts, s.step_vals.back().y()}); - } - s.step_vals.append({ts, value}); - s.last_value_mono_time = e->mono_time; - } + if (s.vals.empty() || (it->second.back()->mono_time / 1e9 - can->routeStartTime()) > s.vals.back().x()) { + appendCanEvents(s.sig, it->second, s.vals, s.step_vals); + } else { + std::vector vals, step_vals; + appendCanEvents(s.sig, it->second, vals, step_vals); + s.vals.insert(std::lower_bound(s.vals.begin(), s.vals.end(), vals.front().x(), xLessThan), + vals.begin(), vals.end()); + s.step_vals.insert(std::lower_bound(s.step_vals.begin(), s.step_vals.end(), step_vals.front().x(), xLessThan), + step_vals.begin(), step_vals.end()); } + if (!can->liveStreaming()) { s.segment_tree.build(s.vals); } - s.series->replace(series_type == SeriesType::StepLine ? s.step_vals : s.vals); + s.series->replace(QVector::fromStdVector(series_type == SeriesType::StepLine ? s.step_vals : s.vals)); } } updateAxisY(); @@ -320,7 +332,7 @@ void ChartView::updateSeries(const cabana::Signal *sig, bool clear) { // auto zoom on yaxis void ChartView::updateAxisY() { - if (sigs.isEmpty()) return; + if (sigs.empty()) return; double min = std::numeric_limits::max(); double max = std::numeric_limits::lowest(); @@ -344,9 +356,7 @@ void ChartView::updateAxisY() { if (it->y() > s.max) s.max = it->y(); } } else { - 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.max = max_y; + std::tie(s.min, s.max) = s.segment_tree.minmax(std::distance(s.vals.cbegin(), first), std::distance(s.vals.cbegin(), last)); } min = std::min(min, s.min); max = std::max(max, s.max); @@ -365,7 +375,7 @@ void ChartView::updateAxisY() { axis_y->setRange(min_y, max_y); axis_y->setTickCount(tick_count); - int n = std::max(int(-std::floor(std::log10((max_y - min_y) / (tick_count - 1)))), 0) + 1; + int n = std::max(int(-std::floor(std::log10((max_y - min_y) / (tick_count - 1)))), 0); int max_label_width = 0; QFontMetrics fm(axis_y->labelsFont()); for (int i = 0; i < tick_count; i++) { @@ -453,7 +463,7 @@ static QPixmap getDropPixmap(const QPixmap &src) { return px; } -void ChartView::contextMenuEvent(QContextMenuEvent *event) { +void ChartView::contextMenuEvent(QContextMenuEvent *event) { QMenu context_menu(this); context_menu.addActions(menu->actions()); context_menu.addSeparator(); @@ -492,13 +502,9 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && rubber && rubber->isVisible()) { rubber->hide(); auto rect = rubber->geometry().normalized(); - double min = chart()->mapToValue(rect.topLeft()).x(); - double max = chart()->mapToValue(rect.bottomRight()).x(); - // Prevent zooming/seeking past the end of the route - min = std::clamp(min, 0., can->totalSeconds()); - max = std::clamp(max, 0., can->totalSeconds()); - + double min = std::clamp(chart()->mapToValue(rect.topLeft()).x(), 0., can->totalSeconds()); + double max = std::clamp(chart()->mapToValue(rect.bottomRight()).x(), 0., can->totalSeconds()); if (rubber->width() <= 0) { // no rubber dragged, seek to mouse position can->seekTo(min); @@ -623,7 +629,7 @@ void ChartView::dropEvent(QDropEvent *event) { source_chart->chart()->removeSeries(s.series); addSeries(s.series); } - sigs.append(source_chart->sigs); + sigs.insert(sigs.end(), std::move_iterator(source_chart->sigs.begin()), std::move_iterator(source_chart->sigs.end())); updateAxisY(); updateTitle(); startAnimation(); @@ -763,13 +769,8 @@ void ChartView::drawSignalValue(QPainter *painter) { 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()); - } - } + auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), cur_sec, [](auto &p, double x) { return p.x() > x; }); + QString value = (it != s.vals.crend() && it->x() >= axis_x->min()) ? 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()); @@ -841,9 +842,8 @@ void ChartView::setSeriesType(SeriesType type) { s.series->deleteLater(); } for (auto &s : sigs) { - auto series = createSeries(series_type, s.sig->color); - series->replace(series_type == SeriesType::StepLine ? s.step_vals : s.vals); - s.series = series; + s.series = createSeries(series_type, s.sig->color); + s.series->replace(QVector::fromStdVector(series_type == SeriesType::StepLine ? s.step_vals : s.vals)); } updateSeriesPoints(); updateTitle(); diff --git a/tools/cabana/chart/chart.h b/tools/cabana/chart/chart.h index f91b81cc91..896eaaf2a3 100644 --- a/tools/cabana/chart/chart.h +++ b/tools/cabana/chart/chart.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -31,7 +32,7 @@ public: ChartView(const std::pair &x_range, ChartsWidget *parent = nullptr); void addSignal(const MessageId &msg_id, const cabana::Signal *sig); bool hasSignal(const MessageId &msg_id, const cabana::Signal *sig) const; - void updateSeries(const cabana::Signal *sig = nullptr, bool clear = true); + void updateSeries(const cabana::Signal *sig = nullptr, const MessageEventsMap *msg_new_events = nullptr); void updatePlot(double cur, double min, double max); void setSeriesType(SeriesType type); void updatePlotArea(int left, bool force = false); @@ -43,9 +44,8 @@ public: MessageId msg_id; const cabana::Signal *sig = nullptr; QXYSeries *series = nullptr; - QVector vals; - QVector step_vals; - uint64_t last_value_mono_time = 0; + std::vector vals; + std::vector step_vals; QPointF track_pt{}; SegmentTree segment_tree; double min = 0; @@ -64,6 +64,8 @@ private slots: void signalRemoved(const cabana::Signal *sig) { removeIf([=](auto &s) { return s.sig == sig; }); } private: + void appendCanEvents(const cabana::Signal *sig, const std::vector &events, + std::vector &vals, std::vector &step_vals); void createToolButtons(); void addSeries(QXYSeries *series); void contextMenuEvent(QContextMenuEvent *event) override; @@ -107,7 +109,7 @@ private: QGraphicsProxyWidget *close_btn_proxy; QGraphicsProxyWidget *manage_btn_proxy; TipLabel tip_label; - QList sigs; + std::vector sigs; double cur_sec = 0; SeriesType series_type = SeriesType::Line; bool is_scrubbing = false; diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index 64eb99325c..a0cb09d07b 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -12,7 +12,7 @@ #include "tools/cabana/chart/chart.h" const int MAX_COLUMN_COUNT = 4; -const int CHART_SPACING = 10; +const int CHART_SPACING = 4; ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), auto_scroll_timer(this), QFrame(parent) { setFrameStyle(QFrame::StyledPanel | QFrame::Plain); @@ -78,8 +78,9 @@ ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), auto_scroll_tim // charts charts_container = new ChartsContainer(this); - + charts_container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); charts_scroll = new QScrollArea(this); + charts_scroll->viewport()->setBackgroundRole(QPalette::Base); charts_scroll->setFrameStyle(QFrame::NoFrame); charts_scroll->setWidgetResizable(true); charts_scroll->setWidget(charts_container); @@ -149,11 +150,10 @@ void ChartsWidget::updateTabBar() { } } -void ChartsWidget::eventsMerged() { +void ChartsWidget::eventsMerged(const MessageEventsMap &new_events) { QFutureSynchronizer future_synchronizer; - bool clear = !can->liveStreaming(); for (auto c : charts) { - future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, nullptr, clear)); + future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, nullptr, &new_events)); } } @@ -282,7 +282,7 @@ void ChartsWidget::splitChart(ChartView *src_chart) { it->series->setColor(it->sig->color); c->addSeries(it->series); - c->sigs.push_back(*it); + c->sigs.emplace_back(std::move(*it)); c->updateAxisY(); c->updateTitle(); it = src_chart->sigs.erase(it); @@ -322,7 +322,7 @@ void ChartsWidget::updateLayout(bool force) { } for (int i = 0; i < current_charts.size(); ++i) { charts_layout->addWidget(current_charts[i], i / n, i % n); - if (current_charts[i]->sigs.isEmpty()) { + if (current_charts[i]->sigs.empty()) { // the chart will be resized after add signal. delay setVisible to reduce flicker. QTimer::singleShot(0, [c = current_charts[i]]() { c->setVisible(true); }); } else { @@ -474,8 +474,9 @@ bool ChartsWidget::event(QEvent *event) { ChartsContainer::ChartsContainer(ChartsWidget *parent) : charts_widget(parent), QWidget(parent) { setAcceptDrops(true); + setBackgroundRole(QPalette::Window); QVBoxLayout *charts_main_layout = new QVBoxLayout(this); - charts_main_layout->setContentsMargins(0, 10, 0, 0); + charts_main_layout->setContentsMargins(0, CHART_SPACING, 0, CHART_SPACING); charts_layout = new QGridLayout(); charts_layout->setSpacing(CHART_SPACING); charts_main_layout->addLayout(charts_layout); @@ -519,15 +520,11 @@ void ChartsContainer::paintEvent(QPaintEvent *ev) { r.setHeight(CHART_SPACING); } - const int margin = (CHART_SPACING - 2) / 2; - QPainterPath path; - path.addPolygon(QPolygonF({r.topLeft(), QPointF(r.left() + CHART_SPACING, r.top() + r.height() / 2), r.bottomLeft()})); - path.addPolygon(QPolygonF({r.topRight(), QPointF(r.right() - CHART_SPACING, r.top() + r.height() / 2), r.bottomRight()})); - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing); - p.fillPath(path, palette().highlight()); - p.fillRect(r.adjusted(2, margin, -2, -margin), palette().highlight()); + p.setPen(QPen(palette().highlight(), 2)); + p.drawLine(r.topLeft() + QPoint(1, 0), r.bottomLeft() + QPoint(1, 0)); + p.drawLine(r.topLeft() + QPoint(0, r.height() / 2), r.topRight() + QPoint(0, r.height() / 2)); + p.drawLine(r.topRight(), r.bottomRight()); } } diff --git a/tools/cabana/chart/chartswidget.h b/tools/cabana/chart/chartswidget.h index 6a7b535543..c85cd09963 100644 --- a/tools/cabana/chart/chartswidget.h +++ b/tools/cabana/chart/chartswidget.h @@ -63,7 +63,7 @@ private: void removeChart(ChartView *chart); void splitChart(ChartView *chart); QRect chartVisibleRect(ChartView *chart); - void eventsMerged(); + void eventsMerged(const MessageEventsMap &new_events); void updateState(); void zoomReset(); void startAutoScroll(); diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc index 6fa479815d..eb2859e5a3 100644 --- a/tools/cabana/streams/abstractstream.cc +++ b/tools/cabana/streams/abstractstream.cc @@ -1,7 +1,6 @@ #include "tools/cabana/streams/abstractstream.h" #include -#include #include @@ -139,9 +138,10 @@ void AbstractStream::updateLastMsgsTo(double sec) { } void AbstractStream::mergeEvents(std::vector::const_iterator first, std::vector::const_iterator last) { - static std::unordered_map> new_events_map; + static MessageEventsMap msg_events; static std::vector new_events; - new_events_map.clear(); + + std::for_each(msg_events.begin(), msg_events.end(), [](auto &e) { e.second.clear(); }); new_events.clear(); for (auto it = first; it != last; ++it) { @@ -156,25 +156,25 @@ void AbstractStream::mergeEvents(std::vector::const_iterator first, std e->size = dat.size(); memcpy(e->dat, (uint8_t *)dat.begin(), e->size); - new_events_map[{.source = e->src, .address = e->address}].push_back(e); + msg_events[{.source = e->src, .address = e->address}].push_back(e); new_events.push_back(e); } } } - for (auto &[id, new_e] : new_events_map) { - auto &e = events_[id]; - auto insert_pos = std::upper_bound(e.cbegin(), e.cend(), new_e.front()->mono_time, CompareCanEvent()); - e.insert(insert_pos, new_e.cbegin(), new_e.cend()); - } - if (!new_events.empty()) { - auto insert_pos = std::upper_bound(all_events_.cbegin(), all_events_.cend(), new_events.front()->mono_time, CompareCanEvent()); - all_events_.insert(insert_pos, new_events.cbegin(), new_events.cend()); + for (auto &[id, new_e] : msg_events) { + if (!new_e.empty()) { + auto &e = events_[id]; + auto pos = std::upper_bound(e.cbegin(), e.cend(), new_e.front()->mono_time, CompareCanEvent()); + e.insert(pos, new_e.cbegin(), new_e.cend()); + } + } + auto pos = std::upper_bound(all_events_.cbegin(), all_events_.cend(), new_events.front()->mono_time, CompareCanEvent()); + all_events_.insert(pos, new_events.cbegin(), new_events.cend()); + emit eventsMerged(msg_events); } - lastest_event_ts = all_events_.empty() ? 0 : all_events_.back()->mono_time; - emit eventsMerged(); } // CanData diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 3a89d4e57d..64c1991501 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -52,6 +51,8 @@ struct BusConfig { bool can_fd = false; }; +typedef std::unordered_map> MessageEventsMap; + class AbstractStream : public QObject { Q_OBJECT @@ -73,6 +74,7 @@ public: virtual double getSpeed() { return 1; } virtual bool isPaused() const { return false; } virtual void pause(bool pause) {} + const MessageEventsMap &eventsMap() const { return events_; } const std::vector &allEvents() const { return all_events_; } const std::vector &events(const MessageId &id) const; virtual const std::vector> getTimeline() { return {}; } @@ -82,7 +84,7 @@ signals: void resume(); void seekedTo(double sec); void streamStarted(); - void eventsMerged(); + void eventsMerged(const MessageEventsMap &events_map); void updated(); void msgsReceived(const QHash *new_msgs, bool has_new_ids); void sourcesUpdated(const SourceSet &s); @@ -104,7 +106,7 @@ protected: std::atomic processing = false; std::unique_ptr> new_msgs; QHash all_msgs; - std::unordered_map> events_; + MessageEventsMap events_; std::vector all_events_; std::unique_ptr event_buffer; std::mutex mutex; diff --git a/tools/cabana/util.cc b/tools/cabana/util.cc index 4c21530774..41d55b793f 100644 --- a/tools/cabana/util.cc +++ b/tools/cabana/util.cc @@ -19,7 +19,7 @@ // SegmentTree -void SegmentTree::build(const QVector &arr) { +void SegmentTree::build(const std::vector &arr) { size = arr.size(); tree.resize(4 * size); // size of the tree is 4 times the size of the array if (size > 0) { @@ -27,7 +27,7 @@ void SegmentTree::build(const QVector &arr) { } } -void SegmentTree::build_tree(const QVector &arr, int n, int left, int right) { +void SegmentTree::build_tree(const std::vector &arr, int n, int left, int right) { if (left == right) { const double y = arr[left].y(); tree[n] = {y, y}; diff --git a/tools/cabana/util.h b/tools/cabana/util.h index 2c1bc5cf7b..0409d5c825 100644 --- a/tools/cabana/util.h +++ b/tools/cabana/util.h @@ -57,12 +57,12 @@ enum { class SegmentTree { public: SegmentTree() = default; - void build(const QVector &arr); + void build(const std::vector &arr); inline std::pair minmax(int left, int right) const { return get_minmax(1, 0, size - 1, left, right); } private: std::pair get_minmax(int n, int left, int right, int range_left, int range_right) const; - void build_tree(const QVector &arr, int n, int left, int right); + void build_tree(const std::vector &arr, int n, int left, int right); std::vector> tree; int size = 0; };