diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc
index 54cacf56cd..87a0e8abd0 100644
--- a/tools/cabana/chartswidget.cc
+++ b/tools/cabana/chartswidget.cc
@@ -68,10 +68,6 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
charts_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
main_layout->addWidget(charts_scroll);
- align_charts_timer = new QTimer(this);
- align_charts_timer->setSingleShot(true);
- align_charts_timer->callOnTimeout(this, &ChartsWidget::alignCharts);
-
// init settings
use_dark_theme = QApplication::style()->standardPalette().color(QPalette::WindowText).value() >
QApplication::style()->standardPalette().color(QPalette::Background).value();
@@ -125,9 +121,9 @@ void ChartsWidget::zoomReset() {
void ChartsWidget::updateState() {
if (charts.isEmpty()) return;
+ const auto events = can->events();
if (can->liveStreaming()) {
// appends incoming events to the end of series
- const auto events = can->events();
for (auto c : charts) {
c->updateSeries(nullptr, events, false);
}
@@ -135,33 +131,30 @@ void ChartsWidget::updateState() {
const double cur_sec = can->currentSec();
if (!is_zoomed) {
- double pos = (cur_sec - display_range.first) / max_chart_range;
+ double pos = (cur_sec - display_range.first) / std::max(1.0, (display_range.second - display_range.first));
if (pos < 0 || pos > 0.8) {
- const double min_event_sec = can->events()->empty() ? 0 : (can->events()->front()->mono_time / (double)1e9 - can->routeStartTime());
- display_range.first = std::floor(std::max(min_event_sec, cur_sec - max_chart_range * 0.2));
+ display_range.first = std::max(0.0, cur_sec - max_chart_range * 0.1);
}
- display_range.second = std::floor(display_range.first + max_chart_range);
+ double max_event_sec = events->empty() ? 0 : (events->back()->mono_time / 1e9 - can->routeStartTime());
+ double max_sec = std::min(std::floor(display_range.first + max_chart_range), max_event_sec);
+ display_range.first = std::max(0.0, max_sec - max_chart_range);
+ display_range.second = display_range.first + max_chart_range;
} else if (cur_sec < zoomed_range.first || cur_sec >= zoomed_range.second) {
// loop in zoommed range
can->seekTo(zoomed_range.first);
}
- setUpdatesEnabled(false);
+ charts_layout->parentWidget()->setUpdatesEnabled(false);
const auto &range = is_zoomed ? zoomed_range : display_range;
for (auto c : charts) {
c->updatePlot(cur_sec, range.first, range.second);
}
- setUpdatesEnabled(true);
+ alignCharts();
+ charts_layout->parentWidget()->setUpdatesEnabled(true);
}
void ChartsWidget::setMaxChartRange(int value) {
max_chart_range = settings.chart_range = value;
- double current_sec = can->currentSec();
- const double min_event_sec = can->events()->empty() ? 0 : (can->events()->front()->mono_time / (double)1e9 - can->routeStartTime());
- // keep current_sec's pos
- double pos = (current_sec - display_range.first) / (display_range.second - display_range.first);
- display_range.first = std::floor(std::max(min_event_sec, current_sec - max_chart_range * (1.0 - pos)));
- display_range.second = std::floor(display_range.first + max_chart_range);
updateToolBar();
updateState();
}
@@ -200,7 +193,6 @@ ChartView *ChartsWidget::createChart() {
QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset);
QObject::connect(chart, &ChartView::seriesRemoved, this, &ChartsWidget::seriesChanged);
QObject::connect(chart, &ChartView::seriesAdded, this, &ChartsWidget::seriesChanged);
- QObject::connect(chart, &ChartView::axisYUpdated, [this]() { align_charts_timer->start(100); });
charts.push_back(chart);
updateLayout();
return chart;
@@ -242,6 +234,7 @@ void ChartsWidget::updateLayout() {
for (int i = 0; i < charts.size(); ++i) {
charts_layout->addWidget(charts[charts.size() - i - 1], i / n, i % n);
}
+ alignCharts(true);
}
void ChartsWidget::resizeEvent(QResizeEvent *event) {
@@ -278,13 +271,16 @@ void ChartsWidget::removeAll() {
emit seriesChanged();
}
-void ChartsWidget::alignCharts() {
+void ChartsWidget::alignCharts(bool force) {
int plot_left = 0;
for (auto c : charts) {
- plot_left = qMax((qreal)plot_left, c->getYAsixLabelWidth());
+ plot_left = std::max(plot_left, c->y_label_width);
}
- for (auto c : charts) {
- c->setPlotAreaLeftPosition(plot_left);
+ plot_left = std::max((plot_left / 10) * 10 + 10, 50);
+ if (std::exchange(align_to, plot_left) != align_to || force) {
+ for (auto c : charts) {
+ c->updatePlotArea(align_to);
+ }
}
}
@@ -302,15 +298,20 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
series_type = settings.chart_series_type == 0 ? QAbstractSeries::SeriesTypeLine : QAbstractSeries::SeriesTypeScatter;
QChart *chart = new QChart();
- chart->setBackgroundRoundness(0);
+ chart->setBackgroundVisible(false);
axis_x = new QValueAxis(this);
axis_y = new QValueAxis(this);
+ axis_y->setLabelFormat("%.1f");
chart->addAxis(axis_x, Qt::AlignBottom);
chart->addAxis(axis_y, Qt::AlignLeft);
chart->legend()->layout()->setContentsMargins(0, 0, 40, 0);
chart->legend()->setShowToolTips(true);
- chart->layout()->setContentsMargins(0, 0, 0, 0);
- chart->setMargins({20, 11, 11, 11});
+ chart->setMargins({0, 0, 0, 0});
+
+ background = new QGraphicsRectItem(chart);
+ background->setBrush(Qt::white);
+ background->setPen(Qt::NoPen);
+ background->setZValue(chart->zValue() - 1);
QToolButton *remove_btn = new QToolButton();
remove_btn->setIcon(utils::icon("x"));
@@ -351,22 +352,6 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
QObject::connect(remove_btn, &QToolButton::clicked, this, &ChartView::remove);
}
-qreal ChartView::getYAsixLabelWidth() const {
- if (axis_y->max() <= axis_y->min() || axis_y->tickCount() <= 1) {
- return 0;
- }
- QFontMetrics fm(axis_y->labelsFont());
- int n = qMax(int(-qFloor(std::log10((axis_y->max() - axis_y->min()) / (axis_y->tickCount() - 1)))), 0) + 1;
- return qMax(fm.width(QString::number(axis_y->min(), 'f', n)), fm.width(QString::number(axis_y->max(), 'f', n))) + 20;
-}
-
-void ChartView::setPlotAreaLeftPosition(int pos) {
- if (std::ceil(chart()->plotArea().left()) != pos) {
- const float left_margin = chart()->margins().left() + pos - chart()->plotArea().left();
- chart()->setMargins(QMargins(left_margin, 11, 11, 11));
- }
-}
-
void ChartView::addSeries(const QString &msg_id, const Signal *sig) {
QXYSeries *series = createSeries(series_type);
chart()->addSeries(series);
@@ -467,11 +452,21 @@ void ChartView::manageSeries() {
void ChartView::resizeEvent(QResizeEvent *event) {
QChartView::resizeEvent(event);
+ updatePlotArea();
int x = event->size().width() - close_btn_proxy->size().width() - 11;
close_btn_proxy->setPos(x, 8);
manage_btn_proxy->setPos(x - manage_btn_proxy->size().width() - 5, 8);
}
+void ChartView::updatePlotArea(int left) {
+ align_to = left > 0 ? left : align_to;
+ QRect r = rect();
+ background->setRect(r);
+ chart()->legend()->setGeometry(QRect(r.left(), r.top(), r.width(), 45));
+ chart()->setPlotArea(QRect(align_to, r.top() + 45, r.width() - align_to - 22, r.height() - 80));
+ chart()->layout()->invalidate();
+}
+
void ChartView::updateTitle() {
for (auto &s : sigs) {
s.series->setName(QString("%1 %2 %3").arg(s.sig->name.c_str()).arg(msgName(s.msg_id)).arg(s.msg_id));
@@ -536,7 +531,7 @@ void ChartView::updateSeries(const Signal *sig, const std::vector *even
for (auto it = begin; it != events->end(); ++it) {
if ((*it)->which == cereal::Event::Which::CAN) {
for (const auto &c : (*it)->event.getCan()) {
- if (s.source == c.getSrc() && s.address == c.getAddress()) {
+ if (s.address == c.getAddress() && s.source == c.getSrc()) {
auto dat = c.getDat();
double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *s.sig);
double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds
@@ -549,46 +544,47 @@ void ChartView::updateSeries(const Signal *sig, const std::vector *even
s.last_value_mono_time = events->back()->mono_time;
}
s.series->replace(s.vals);
- updateAxisY();
}
}
+ updateAxisY();
}
// auto zoom on yaxis
void ChartView::updateAxisY() {
if (sigs.isEmpty()) return;
- double min_y = std::numeric_limits::max();
- double max_y = std::numeric_limits::lowest();
+ double min = std::numeric_limits::max();
+ double max = std::numeric_limits::lowest();
for (auto &s : sigs) {
- auto begin = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; });
- for (auto it = begin; it != s.vals.end() && it->x() <= axis_x->max(); ++it) {
- if (it->y() < min_y) min_y = it->y();
- if (it->y() > max_y) max_y = it->y();
+ auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; });
+ auto last = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->max(), [](auto &p, double x) { return p.x() < x; });
+ for (auto it = first; it != last; ++it) {
+ if (it->y() < min) min = it->y();
+ if (it->y() > max) max = it->y();
}
}
+ if (min == std::numeric_limits::max()) min = 0;
+ if (max == std::numeric_limits::lowest()) max = 0;
- if (min_y == std::numeric_limits::max()) min_y = 0;
- if (max_y == std::numeric_limits::lowest()) max_y = 0;
- if (std::abs(max_y - min_y) < 1e-3) {
- applyNiceNumbers(min_y - 1, max_y + 1);
- } else {
- double range = max_y - min_y;
- applyNiceNumbers(min_y - range * 0.05, max_y + range * 0.05);
+ double delta = std::abs(max - min) < 1e-3 ? 1 : (max - min) * 0.05;
+ auto [min_y, max_y, tick_count] = getNiceAxisNumbers(min - delta, max + delta, axis_y->tickCount());
+ if (min_y != axis_y->min() || max_y != axis_y->max()) {
+ axis_y->setRange(min_y, max_y);
+ axis_y->setTickCount(tick_count);
+
+ QFontMetrics fm(axis_y->labelsFont());
+ int n = qMax(int(-qFloor(std::log10((max_y - min_y) / (tick_count - 1)))), 0) + 1;
+ y_label_width = qMax(fm.width(QString::number(min_y, 'f', n)), fm.width(QString::number(max_y, 'f', n))) + 20; // left margin 20
}
- emit axisYUpdated();
}
-void ChartView::applyNiceNumbers(qreal min, qreal max) {
- int tick_count = axis_y->tickCount();
+std::tuple ChartView::getNiceAxisNumbers(qreal min, qreal max, int tick_count) {
qreal range = niceNumber((max - min), true); // range with ceiling
qreal step = niceNumber(range / (tick_count - 1), false);
min = qFloor(min / step);
max = qCeil(max / step);
tick_count = int(max - min) + 1;
- axis_y->setRange(min * step, max * step);
- axis_y->setTickCount(tick_count);
- axis_y->setLabelFormat("%.1f");
+ return {min * step, max * step, tick_count};
}
// nice numbers can be expressed as form of 1*10^n, 2* 10^n or 5*10^n
diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h
index 8c50993971..f0f9e5cd9c 100644
--- a/tools/cabana/chartswidget.h
+++ b/tools/cabana/chartswidget.h
@@ -7,7 +7,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -18,6 +17,8 @@
using namespace QtCharts;
+const int CHART_MIN_WIDTH = 300;
+
class ChartView : public QChartView {
Q_OBJECT
@@ -29,9 +30,8 @@ public:
bool hasSeries(const QString &msg_id, const Signal *sig) const;
void updateSeries(const Signal *sig = nullptr, const std::vector *events = nullptr, bool clear = true);
void updatePlot(double cur, double min, double max);
- void setPlotAreaLeftPosition(int pos);
- qreal getYAsixLabelWidth() const;
void setSeriesType(QAbstractSeries::SeriesType type);
+ void updatePlotArea(int left = 0);
struct SigItem {
QString msg_id;
@@ -49,7 +49,6 @@ signals:
void zoomIn(double min, double max);
void zoomReset();
void remove();
- void axisYUpdated();
private slots:
void msgRemoved(uint32_t address);
@@ -67,25 +66,30 @@ private:
void dropEvent(QDropEvent *event) override;
void leaveEvent(QEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
+ QSize sizeHint() const override { return {CHART_MIN_WIDTH, settings.chart_height}; }
void updateAxisY();
void updateTitle();
void drawForeground(QPainter *painter, const QRectF &rect) override;
- void applyNiceNumbers(qreal min, qreal max);
+ std::tuple getNiceAxisNumbers(qreal min, qreal max, int tick_count);
qreal niceNumber(qreal x, bool ceiling);
QXYSeries *createSeries(QAbstractSeries::SeriesType type);
void updateSeriesPoints();
+ int y_label_width = 50;
+ int align_to = 0;
QValueAxis *axis_x;
QValueAxis *axis_y;
QVector track_pts;
QGraphicsProxyWidget *close_btn_proxy;
QGraphicsProxyWidget *manage_btn_proxy;
+ QGraphicsRectItem *background;
QList sigs;
double cur_sec = 0;
const QString mime_type = "application/x-cabanachartview";
QAbstractSeries::SeriesType series_type = QAbstractSeries::SeriesTypeLine;
QAction *line_series_action;
QAction *scatter_series_action;
+ friend class ChartsWidget;
};
class ChartsWidget : public QWidget {
@@ -107,7 +111,7 @@ signals:
private:
void resizeEvent(QResizeEvent *event) override;
- void alignCharts();
+ void alignCharts(bool force = false);
void newChart();
ChartView * createChart();
void removeChart(ChartView *chart);
@@ -129,7 +133,6 @@ private:
QAction *dock_btn;
QAction *reset_zoom_btn;
QAction *remove_all_btn;
- QTimer *align_charts_timer;
QGridLayout *charts_layout;
QList charts;
uint32_t max_chart_range = 0;
@@ -141,7 +144,7 @@ private:
QAction *columns_cb_action;
QComboBox *columns_cb;
int column_count = 1;
- const int CHART_MIN_WIDTH = 300;
+ int align_to = 0;
};
class SeriesSelector : public QDialog {