diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index d01a4882b2..ee1cdab6d2 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -5,9 +5,11 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -58,7 +60,7 @@ void ChartView::createToolButtons() { QMenu *menu = new QMenu(this); auto change_series_group = new QActionGroup(menu); change_series_group->setExclusive(true); - QStringList types{tr("line"), tr("Step Line"), tr("Scatter")}; + QStringList types{tr("Line"), tr("Step Line"), tr("Scatter")}; for (int i = 0; i < types.size(); ++i) { QAction *act = new QAction(types[i], change_series_group); act->setData(i); @@ -67,7 +69,8 @@ void ChartView::createToolButtons() { menu->addAction(act); } menu->addSeparator(); - menu->addAction(tr("Manage series"), this, &ChartView::manageSeries); + menu->addAction(tr("Manage Signals"), this, &ChartView::manageSignals); + split_chart_act = menu->addAction(tr("Split Chart"), [this]() { charts_widget->splitChart(this); }); QToolButton *manage_btn = new ToolButton("list", ""); manage_btn->setMenu(menu); @@ -90,10 +93,10 @@ QSize ChartView::sizeHint() const { void ChartView::setTheme(QChart::ChartTheme theme) { chart()->setTheme(theme); if (theme == QChart::ChartThemeDark) { - axis_x->setTitleBrush(palette().color(QPalette::Text)); - axis_x->setLabelsBrush(palette().color(QPalette::Text)); - axis_y->setTitleBrush(palette().color(QPalette::Text)); - axis_y->setLabelsBrush(palette().color(QPalette::Text)); + axis_x->setTitleBrush(palette().text()); + axis_x->setLabelsBrush(palette().text()); + axis_y->setTitleBrush(palette().text()); + axis_y->setLabelsBrush(palette().text()); chart()->legend()->setLabelColor(palette().color(QPalette::Text)); } for (auto &s : sigs) { @@ -101,18 +104,18 @@ void ChartView::setTheme(QChart::ChartTheme theme) { } } -void ChartView::addSeries(const MessageId &msg_id, const cabana::Signal *sig) { - if (hasSeries(msg_id, sig)) return; +void ChartView::addSignal(const MessageId &msg_id, const cabana::Signal *sig) { + if (hasSignal(msg_id, sig)) return; QXYSeries *series = createSeries(series_type, getColor(sig)); sigs.push_back({.msg_id = msg_id, .sig = sig, .series = series}); - updateTitle(); updateSeries(sig); updateSeriesPoints(); + updateTitle(); emit charts_widget->seriesChanged(); } -bool ChartView::hasSeries(const MessageId &msg_id, const cabana::Signal *sig) const { +bool ChartView::hasSignal(const MessageId &msg_id, const cabana::Signal *sig) const { return std::any_of(sigs.begin(), sigs.end(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; }); } @@ -139,7 +142,6 @@ void ChartView::removeIf(std::function predicate) { void ChartView::signalUpdated(const cabana::Signal *sig) { if (std::any_of(sigs.begin(), sigs.end(), [=](auto &s) { return s.sig == sig; })) { updateTitle(); - // TODO: don't update series if only name changed. updateSeries(sig); } } @@ -149,7 +151,7 @@ void ChartView::msgUpdated(MessageId id) { updateTitle(); } -void ChartView::manageSeries() { +void ChartView::manageSignals() { SignalSelector dlg(tr("Mange Chart"), this); for (auto &s : sigs) { dlg.addSelected(s.msg_id, s.sig); @@ -157,7 +159,7 @@ void ChartView::manageSeries() { if (dlg.exec() == QDialog::Accepted) { auto items = dlg.seletedItems(); for (auto s : items) { - addSeries(s->msg_id, s->sig); + addSignal(s->msg_id, s->sig); } removeIf([&](auto &s) { return std::none_of(items.cbegin(), items.cend(), [&](auto &it) { return s.msg_id == it->msg_id && s.sig == it->sig; }); @@ -172,7 +174,6 @@ void ChartView::resizeEvent(QResizeEvent *event) { close_btn_proxy->setPos(rect().right() - right - close_btn_proxy->size().width(), top); int x = close_btn_proxy->pos().x() - manage_btn_proxy->size().width() - style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing); manage_btn_proxy->setPos(x, top); - chart()->legend()->setGeometry({move_icon->sceneBoundingRect().topRight(), manage_btn_proxy->sceneBoundingRect().bottomLeft()}); if (align_to > 0) { updatePlotArea(align_to, true); } @@ -185,6 +186,7 @@ void ChartView::updatePlotArea(int left_pos, bool force) { qreal left, top, right, bottom; chart()->layout()->getContentsMargins(&left, &top, &right, &bottom); + chart()->legend()->setGeometry({move_icon->sceneBoundingRect().topRight(), manage_btn_proxy->sceneBoundingRect().bottomLeft()}); QSizeF x_label_size = QFontMetrics(axis_x->labelsFont()).size(Qt::TextSingleLine, QString::number(axis_x->max(), 'f', 2)); x_label_size += QSizeF{5, 5}; int adjust_top = chart()->legend()->geometry().height() + style()->pixelMetric(QStyle::PM_LayoutTopMargin); @@ -202,6 +204,7 @@ void ChartView::updateTitle() { auto decoration = s.series->isVisible() ? "none" : "line-through"; s.series->setName(QString("%2 %3 %4").arg(decoration, s.sig->name, msgName(s.msg_id), s.msg_id.toString())); } + split_chart_act->setEnabled(sigs.size() > 1); resetChartCache(); } @@ -279,7 +282,8 @@ void ChartView::updateSeries(const cabana::Signal *sig) { } } updateAxisY(); - chart_pixmap = QPixmap(); + // invoke resetChartCache in ui thread + QMetaObject::invokeMethod(this, &ChartView::resetChartCache, Qt::QueuedConnection); } // auto zoom on yaxis @@ -329,10 +333,16 @@ void ChartView::updateAxisY() { axis_y->setRange(min_y, max_y); axis_y->setTickCount(tick_count); - int title_spacing = unit.isEmpty() ? 0 : QFontMetrics(axis_y->titleFont()).size(Qt::TextSingleLine, unit).height(); - QFontMetrics fm(axis_y->labelsFont()); int n = qMax(int(-qFloor(std::log10((max_y - min_y) / (tick_count - 1)))), 0) + 1; - y_label_width = title_spacing + qMax(fm.width(QString::number(min_y, 'f', n)), fm.width(QString::number(max_y, 'f', n))) + 15; + int max_label_width = 0; + QFontMetrics fm(axis_y->labelsFont()); + for (int i = 0; i < tick_count; i++) { + qreal value = min_y + (i * (max_y - min_y) / (tick_count - 1)); + max_label_width = std::max(max_label_width, fm.width(QString::number(value, 'f', n))); + } + + int title_spacing = unit.isEmpty() ? 0 : QFontMetrics(axis_y->titleFont()).size(Qt::TextSingleLine, unit).height(); + y_label_width = title_spacing + max_label_width + 15; axis_y->setLabelFormat(QString("%.%1f").arg(n)); emit axisYLabelWidthChanged(y_label_width); } @@ -556,13 +566,12 @@ void ChartView::dropEvent(QDropEvent *event) { ChartView *source_chart = (ChartView *)event->source(); for (auto &s : source_chart->sigs) { source_chart->chart()->removeSeries(s.series); - chart()->addSeries(s.series); - s.series->attachAxis(axis_x); - s.series->attachAxis(axis_y); + addSeries(s.series); } sigs.append(source_chart->sigs); updateAxisY(); updateTitle(); + startAnimation(); source_chart->sigs.clear(); charts_widget->removeChart(source_chart); @@ -577,6 +586,17 @@ void ChartView::resetChartCache() { viewport()->update(); } +void ChartView::startAnimation() { + QGraphicsOpacityEffect *eff = new QGraphicsOpacityEffect(this); + viewport()->setGraphicsEffect(eff); + QPropertyAnimation *a = new QPropertyAnimation(eff, "opacity"); + a->setDuration(250); + a->setStartValue(0.3); + a->setEndValue(1); + a->setEasingCurve(QEasingCurve::InBack); + a->start(QPropertyAnimation::DeleteWhenStopped); +} + void ChartView::paintEvent(QPaintEvent *event) { if (!can->liveStreaming()) { if (chart_pixmap.isNull()) { @@ -684,6 +704,11 @@ QXYSeries *ChartView::createSeries(SeriesType type, QColor color) { pen.setWidthF(2.0 * devicePixelRatioF()); series->setPen(pen); #endif + addSeries(series); + return series; +} + +void ChartView::addSeries(QXYSeries *series) { chart()->addSeries(series); series->attachAxis(axis_x); series->attachAxis(axis_y); @@ -694,7 +719,6 @@ QXYSeries *ChartView::createSeries(SeriesType type, QColor color) { if (glwidget && !glwidget->testAttribute(Qt::WA_TransparentForMouseEvents)) { glwidget->setAttribute(Qt::WA_TransparentForMouseEvents); } - return series; } void ChartView::setSeriesType(SeriesType type) { diff --git a/tools/cabana/chart/chart.h b/tools/cabana/chart/chart.h index d00ead0f9f..fda9271560 100644 --- a/tools/cabana/chart/chart.h +++ b/tools/cabana/chart/chart.h @@ -25,14 +25,15 @@ class ChartView : public QChartView { public: ChartView(const std::pair &x_range, ChartsWidget *parent = nullptr); - void addSeries(const MessageId &msg_id, const cabana::Signal *sig); - bool hasSeries(const MessageId &msg_id, const cabana::Signal *sig) const; + 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); void updatePlot(double cur, double min, double max); void setSeriesType(SeriesType type); void updatePlotArea(int left, bool force = false); void showTip(double sec); void hideTip(); + void startAnimation(); struct SigItem { MessageId msg_id; @@ -52,7 +53,7 @@ signals: private slots: void signalUpdated(const cabana::Signal *sig); - void manageSeries(); + void manageSignals(); void handleMarkerClicked(); void msgUpdated(MessageId id); void msgRemoved(MessageId id) { removeIf([=](auto &s) { return s.msg_id == id; }); } @@ -60,6 +61,7 @@ private slots: private: void createToolButtons(); + void addSeries(QXYSeries *series); void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *ev) override; @@ -89,6 +91,7 @@ private: int align_to = 0; QValueAxis *axis_x; QValueAxis *axis_y; + QAction *split_chart_act; QGraphicsPixmapItem *move_icon; QGraphicsProxyWidget *close_btn_proxy; QGraphicsProxyWidget *manage_btn_proxy; diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index dab9687b5a..2700f6519e 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -235,7 +235,7 @@ void ChartsWidget::settingChanged() { ChartView *ChartsWidget::findChart(const MessageId &id, const cabana::Signal *sig) { for (auto c : charts) - if (c->hasSeries(id, sig)) return c; + if (c->hasSignal(id, sig)) return c; return nullptr; } @@ -256,12 +256,28 @@ void ChartsWidget::showChart(const MessageId &id, const cabana::Signal *sig, boo ChartView *chart = findChart(id, sig); if (show && !chart) { chart = merge && currentCharts().size() > 0 ? currentCharts().front() : createChart(); - chart->addSeries(id, sig); + chart->addSignal(id, sig); } else if (!show && chart) { chart->removeIf([&](auto &s) { return s.msg_id == id && s.sig == sig; }); } } +void ChartsWidget::splitChart(ChartView *src_chart) { + if (src_chart->sigs.size() > 1) { + for (auto it = src_chart->sigs.begin() + 1; it != src_chart->sigs.end(); /**/) { + auto c = createChart(); + src_chart->chart()->removeSeries(it->series); + c->addSeries(it->series); + c->sigs.push_back(*it); + c->updateAxisY(); + c->updateTitle(); + it = src_chart->sigs.erase(it); + } + src_chart->updateAxisY(); + src_chart->updateTitle(); + } +} + void ChartsWidget::setColumnCount(int n) { n = std::clamp(n, 1, MAX_COLUMN_COUNT); if (column_count != n) { @@ -346,7 +362,7 @@ void ChartsWidget::newChart() { if (!items.isEmpty()) { auto c = createChart(); for (auto it : items) { - c->addSeries(it->msg_id, it->sig); + c->addSignal(it->msg_id, it->sig); } } } @@ -460,6 +476,7 @@ void ChartsContainer::dropEvent(QDropEvent *event) { charts_widget->currentCharts().insert(to, chart); charts_widget->updateLayout(true); event->acceptProposedAction(); + chart->startAnimation(); } drawDropIndicator({}); } diff --git a/tools/cabana/chart/chartswidget.h b/tools/cabana/chart/chartswidget.h index 8fd0e87e51..5fe227b156 100644 --- a/tools/cabana/chart/chartswidget.h +++ b/tools/cabana/chart/chartswidget.h @@ -58,6 +58,7 @@ private: void newChart(); ChartView *createChart(); void removeChart(ChartView *chart); + void splitChart(ChartView *chart); void eventsMerged(); void updateState(); void zoomReset();