mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-04 21:12:07 +08:00
cabana: support splitting chart (#27887)
* split chart * fixed elided axisY label issues * fade in chart old-commit-hash: f63fe156379cc225f7685a692ed9e3d1965fc5dc
This commit is contained in:
+46
-22
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user