cabana/chart: populate the points incrementally (#30326)

* populate the points incrementally

* less spacing
old-commit-hash: 57621afcb0
testing-closet
Dean Lee 2 years ago committed by GitHub
parent 8123daceaf
commit 83678f7a87
  1. 90
      tools/cabana/chart/chart.cc
  2. 12
      tools/cabana/chart/chart.h
  3. 29
      tools/cabana/chart/chartswidget.cc
  4. 2
      tools/cabana/chart/chartswidget.h
  5. 28
      tools/cabana/streams/abstractstream.cc
  6. 8
      tools/cabana/streams/abstractstream.h
  7. 4
      tools/cabana/util.cc
  8. 4
      tools/cabana/util.h

@ -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<const CanEvent *> &events,
std::vector<QPointF> &vals, std::vector<QPointF> &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<QPointF> 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<QPointF>::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<double>::max();
double max = std::numeric_limits<double>::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<QPointF>::fromStdVector(series_type == SeriesType::StepLine ? s.step_vals : s.vals));
}
updateSeriesPoints();
updateTitle();

@ -2,6 +2,7 @@
#include <tuple>
#include <utility>
#include <vector>
#include <QMenu>
#include <QGraphicsPixmapItem>
@ -31,7 +32,7 @@ public:
ChartView(const std::pair<double, double> &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<QPointF> vals;
QVector<QPointF> step_vals;
uint64_t last_value_mono_time = 0;
std::vector<QPointF> vals;
std::vector<QPointF> 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<const CanEvent *> &events,
std::vector<QPointF> &vals, std::vector<QPointF> &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<SigItem> sigs;
std::vector<SigItem> sigs;
double cur_sec = 0;
SeriesType series_type = SeriesType::Line;
bool is_scrubbing = false;

@ -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<void> 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());
}
}

@ -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();

@ -1,7 +1,6 @@
#include "tools/cabana/streams/abstractstream.h"
#include <algorithm>
#include <vector>
#include <QTimer>
@ -139,9 +138,10 @@ void AbstractStream::updateLastMsgsTo(double sec) {
}
void AbstractStream::mergeEvents(std::vector<Event *>::const_iterator first, std::vector<Event *>::const_iterator last) {
static std::unordered_map<MessageId, std::deque<const CanEvent *>> new_events_map;
static MessageEventsMap msg_events;
static std::vector<const CanEvent *> 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<Event *>::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

@ -2,7 +2,6 @@
#include <array>
#include <atomic>
#include <deque>
#include <memory>
#include <tuple>
#include <unordered_map>
@ -52,6 +51,8 @@ struct BusConfig {
bool can_fd = false;
};
typedef std::unordered_map<MessageId, std::vector<const CanEvent *>> 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<const CanEvent *> &allEvents() const { return all_events_; }
const std::vector<const CanEvent *> &events(const MessageId &id) const;
virtual const std::vector<std::tuple<double, double, TimelineType>> 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<MessageId, CanData> *new_msgs, bool has_new_ids);
void sourcesUpdated(const SourceSet &s);
@ -104,7 +106,7 @@ protected:
std::atomic<bool> processing = false;
std::unique_ptr<QHash<MessageId, CanData>> new_msgs;
QHash<MessageId, CanData> all_msgs;
std::unordered_map<MessageId, std::vector<const CanEvent *>> events_;
MessageEventsMap events_;
std::vector<const CanEvent *> all_events_;
std::unique_ptr<MonotonicBuffer> event_buffer;
std::mutex mutex;

@ -19,7 +19,7 @@
// SegmentTree
void SegmentTree::build(const QVector<QPointF> &arr) {
void SegmentTree::build(const std::vector<QPointF> &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<QPointF> &arr) {
}
}
void SegmentTree::build_tree(const QVector<QPointF> &arr, int n, int left, int right) {
void SegmentTree::build_tree(const std::vector<QPointF> &arr, int n, int left, int right) {
if (left == right) {
const double y = arr[left].y();
tree[n] = {y, y};

@ -57,12 +57,12 @@ enum {
class SegmentTree {
public:
SegmentTree() = default;
void build(const QVector<QPointF> &arr);
void build(const std::vector<QPointF> &arr);
inline std::pair<double, double> minmax(int left, int right) const { return get_minmax(1, 0, size - 1, left, right); }
private:
std::pair<double, double> get_minmax(int n, int left, int right, int range_left, int range_right) const;
void build_tree(const QVector<QPointF> &arr, int n, int left, int right);
void build_tree(const std::vector<QPointF> &arr, int n, int left, int right);
std::vector<std::pair<double, double>> tree;
int size = 0;
};

Loading…
Cancel
Save