diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index f0a81fe65d..e49d28db44 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -178,6 +179,7 @@ void ChartsWidget::settingChanged() { range_slider->setRange(1, settings.max_cached_minutes * 60); for (auto c : charts) { c->setFixedHeight(settings.chart_height); + c->setSeriesType(settings.chart_series_type == 0 ? QAbstractSeries::SeriesTypeLine : QAbstractSeries::SeriesTypeScatter); } } @@ -297,6 +299,8 @@ bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { // ChartView 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); axis_x = new QValueAxis(this); @@ -317,9 +321,20 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { close_btn_proxy->setZValue(chart->zValue() + 11); QToolButton *manage_btn = new QToolButton(); + manage_btn->setToolButtonStyle(Qt::ToolButtonIconOnly); manage_btn->setIcon(utils::icon("gear")); manage_btn->setAutoRaise(true); - manage_btn->setToolTip(tr("Manage series")); + QMenu *menu = new QMenu(this); + line_series_action = menu->addAction(tr("Line"), [this]() { setSeriesType(QAbstractSeries::SeriesTypeLine); }); + line_series_action->setCheckable(true); + line_series_action->setChecked(series_type == QAbstractSeries::SeriesTypeLine); + scatter_series_action = menu->addAction(tr("Scatter"), [this]() { setSeriesType(QAbstractSeries::SeriesTypeScatter); }); + scatter_series_action->setCheckable(true); + scatter_series_action->setChecked(series_type == QAbstractSeries::SeriesTypeScatter); + menu->addSeparator(); + menu->addAction(tr("Manage series"), this, &ChartView::manageSeries); + manage_btn->setMenu(menu); + manage_btn->setPopupMode(QToolButton::InstantPopup); manage_btn_proxy = new QGraphicsProxyWidget(chart); manage_btn_proxy->setWidget(manage_btn); manage_btn_proxy->setZValue(chart->zValue() + 11); @@ -334,7 +349,6 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { QObject::connect(dbc(), &DBCManager::msgRemoved, this, &ChartView::msgRemoved); QObject::connect(dbc(), &DBCManager::msgUpdated, this, &ChartView::msgUpdated); QObject::connect(remove_btn, &QToolButton::clicked, this, &ChartView::remove); - QObject::connect(manage_btn, &QToolButton::clicked, this, &ChartView::manageSeries); } qreal ChartView::getYAsixLabelWidth() const { @@ -354,8 +368,7 @@ void ChartView::setPlotAreaLeftPosition(int pos) { } void ChartView::addSeries(const QString &msg_id, const Signal *sig) { - QLineSeries *series = new QLineSeries(this); - + QXYSeries *series = createSeries(series_type); chart()->addSeries(series); series->attachAxis(axis_x); series->attachAxis(axis_y); @@ -478,22 +491,26 @@ void ChartView::updatePlot(double cur, double min, double max) { int num_points = std::max(end - begin, 1); int pixels_per_point = width() / num_points; - s.series->setPointsVisible(pixels_per_point > 20); + if (series_type == QAbstractSeries::SeriesTypeScatter) { + ((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 3, 1, 8)); + } else { + s.series->setPointsVisible(pixels_per_point > 20); - // TODO: On MacOS QChartWidget doesn't work with the OpenGL settings that CameraWidget needs. + // TODO: On MacOS QChartWidget doesn't work with the OpenGL settings that CameraWidget needs. #ifndef __APPLE - // OpenGL mode lacks certain features (such as showing points), only use when drawing many points - bool use_opengl = pixels_per_point < 1; - s.series->setUseOpenGL(use_opengl); + // OpenGL mode lacks certain features (such as showing points), only use when drawing many points + bool use_opengl = pixels_per_point < 1; + s.series->setUseOpenGL(use_opengl); - // Qt doesn't properly apply device pixel ratio in OpenGL mode - QApplication* application = static_cast(QApplication::instance()); - float scale = use_opengl ? application->devicePixelRatio() : 1.0; + // Qt doesn't properly apply device pixel ratio in OpenGL mode + QApplication *application = static_cast(QApplication::instance()); + float scale = use_opengl ? application->devicePixelRatio() : 1.0; - QPen pen = s.series->pen(); - pen.setWidth(2.0 * scale); - s.series->setPen(pen); + QPen pen = s.series->pen(); + pen.setWidth(2.0 * scale); + s.series->setPen(pen); #endif + } } } @@ -733,6 +750,43 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) { } } +QXYSeries *ChartView::createSeries(QAbstractSeries::SeriesType type) { + QXYSeries *series = nullptr; + if (type == QAbstractSeries::SeriesTypeLine) { + series = new QLineSeries(this); + } else { + series = new QScatterSeries(this); + } + // TODO: Due to a bug in CameraWidget the camera frames + // are drawn instead of the graphs on MacOS. Re-enable OpenGL when fixed +#ifndef __APPLE__ + series->setUseOpenGL(true); +#endif + return series; +} + +void ChartView::setSeriesType(QAbstractSeries::SeriesType type) { + if (type != series_type) { + series_type = type; + line_series_action->setChecked(type == QAbstractSeries::SeriesTypeLine); + scatter_series_action->setChecked(type == QAbstractSeries::SeriesTypeScatter); + + for (auto &s : sigs) { + chart()->removeSeries(s.series); + s.series->deleteLater(); + } + for (auto &s : sigs) { + auto series = createSeries(series_type); + chart()->addSeries(series); + series->attachAxis(axis_x); + series->attachAxis(axis_y); + series->replace(s.vals); + s.series = series; + } + updateTitle(); + } +} + // SeriesSelector SeriesSelector::SeriesSelector(QWidget *parent) { diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 41b2ddffcc..9b2afd45a9 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "tools/cabana/dbcmanager.h" @@ -30,13 +31,14 @@ public: void updatePlot(double cur, double min, double max); void setPlotAreaLeftPosition(int pos); qreal getYAsixLabelWidth() const; + void setSeriesType(QAbstractSeries::SeriesType type); struct SigItem { QString msg_id; uint8_t source = 0; uint32_t address = 0; const Signal *sig = nullptr; - QLineSeries *series = nullptr; + QXYSeries *series = nullptr; QVector vals; uint64_t last_value_mono_time = 0; }; @@ -70,6 +72,7 @@ private: void drawForeground(QPainter *painter, const QRectF &rect) override; void applyNiceNumbers(qreal min, qreal max); qreal niceNumber(qreal x, bool ceiling); + QXYSeries *createSeries(QAbstractSeries::SeriesType type); QValueAxis *axis_x; QValueAxis *axis_y; @@ -79,6 +82,9 @@ private: 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; }; class ChartsWidget : public QWidget { diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index 78969c76c3..22c7a941ab 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -25,6 +25,7 @@ void Settings::save() { s.setValue("video_splitter_state", video_splitter_state); s.setValue("recent_files", recent_files); s.setValue("message_header_state", message_header_state); + s.setValue("chart_series_type", chart_series_type); } void Settings::load() { @@ -40,6 +41,7 @@ void Settings::load() { video_splitter_state = s.value("video_splitter_state").toByteArray(); recent_files = s.value("recent_files").toStringList(); message_header_state = s.value("message_header_state").toByteArray(); + chart_series_type = s.value("chart_series_type", 0).toInt(); } // SettingsDlg @@ -60,6 +62,11 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { cached_minutes->setValue(settings.max_cached_minutes); form_layout->addRow(tr("Max Cached Minutes"), cached_minutes); + chart_series_type = new QComboBox(this); + chart_series_type->addItems({tr("Line"), tr("Scatter")}); + chart_series_type->setCurrentIndex(settings.chart_series_type); + form_layout->addRow(tr("Chart Default Series Type"), chart_series_type); + chart_height = new QSpinBox(this); chart_height->setRange(100, 500); chart_height->setSingleStep(10); @@ -77,6 +84,7 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { void SettingsDlg::save() { settings.fps = fps->value(); settings.max_cached_minutes = cached_minutes->value(); + settings.chart_series_type = chart_series_type->currentIndex(); settings.chart_height = chart_height->value(); settings.save(); accept(); diff --git a/tools/cabana/settings.h b/tools/cabana/settings.h index 56c6a992ae..7abad81c29 100644 --- a/tools/cabana/settings.h +++ b/tools/cabana/settings.h @@ -18,6 +18,7 @@ public: int chart_height = 200; int chart_column_count = 1; int chart_range = 3 * 60; // e minutes + int chart_series_type = 0; QString last_dir; QByteArray geometry; QByteArray video_splitter_state; @@ -38,6 +39,7 @@ public: QSpinBox *fps; QSpinBox *cached_minutes; QSpinBox *chart_height; + QComboBox *chart_series_type; }; extern Settings settings;