cabana: support splitting chart (#27887)

* split chart

* fixed elided axisY label issues

* fade in chart
pull/27891/head
Dean Lee 2 years ago committed by GitHub
parent 36bd49ea97
commit f63fe15637
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 68
      tools/cabana/chart/chart.cc
  2. 9
      tools/cabana/chart/chart.h
  3. 23
      tools/cabana/chart/chartswidget.cc
  4. 1
      tools/cabana/chart/chartswidget.h

@ -5,9 +5,11 @@
#include <QDrag>
#include <QGraphicsLayout>
#include <QGraphicsDropShadowEffect>
#include <QGraphicsOpacityEffect>
#include <QMenu>
#include <QMimeData>
#include <QOpenGLWidget>
#include <QPropertyAnimation>
#include <QRubberBand>
#include <QtMath>
@ -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<bool(const SigItem &s)> 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("<span style=\"text-decoration:%1\"><b>%2</b> <font color=\"gray\">%3 %4</font></span>").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) {

@ -25,14 +25,15 @@ class ChartView : public QChartView {
public:
ChartView(const std::pair<double, double> &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;

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

@ -58,6 +58,7 @@ private:
void newChart();
ChartView *createChart();
void removeChart(ChartView *chart);
void splitChart(ChartView *chart);
void eventsMerged();
void updateState();
void zoomReset();

Loading…
Cancel
Save