#include "tools/cabana/chartswidget.h" #include #include #include #include #include #include int64_t get_raw_value(uint8_t *data, size_t data_size, const Signal &sig) { int64_t ret = 0; int i = sig.msb / 8; int bits = sig.size; while (i >= 0 && i < data_size && bits > 0) { int lsb = (int)(sig.lsb / 8) == i ? sig.lsb : i * 8; int msb = (int)(sig.msb / 8) == i ? sig.msb : (i + 1) * 8 - 1; int size = msb - lsb + 1; uint64_t d = (data[i] >> (lsb - (i * 8))) & ((1ULL << size) - 1); ret |= d << (bits - size); bits -= size; i = sig.is_little_endian ? i - 1 : i + 1; } return ret; } // ChartsWidget ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); // title bar title_bar = new QWidget(this); QHBoxLayout *title_layout = new QHBoxLayout(title_bar); title_label = new QLabel(tr("Charts")); title_layout->addWidget(title_label); title_layout->addStretch(); reset_zoom_btn = new QPushButton("⟲", this); reset_zoom_btn->setVisible(false); reset_zoom_btn->setFixedSize(30, 30); reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)")); title_layout->addWidget(reset_zoom_btn); remove_all_btn = new QPushButton(tr("✖")); remove_all_btn->setVisible(false); remove_all_btn->setToolTip(tr("Remove all charts")); remove_all_btn->setFixedSize(30, 30); title_layout->addWidget(remove_all_btn); dock_btn = new QPushButton(); dock_btn->setFixedSize(30, 30); updateDockButton(); title_layout->addWidget(dock_btn); main_layout->addWidget(title_bar, 0, Qt::AlignTop); // charts QWidget *charts_container = new QWidget(this); QVBoxLayout *charts_main = new QVBoxLayout(charts_container); charts_layout = new QVBoxLayout(); charts_main->addLayout(charts_layout); charts_main->addStretch(); QScrollArea *charts_scroll = new QScrollArea(this); charts_scroll->setWidgetResizable(true); charts_scroll->setWidget(charts_container); charts_scroll->setFrameShape(QFrame::NoFrame); charts_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); main_layout->addWidget(charts_scroll); QObject::connect(parser, &Parser::showPlot, this, &ChartsWidget::addChart); QObject::connect(parser, &Parser::hidePlot, this, &ChartsWidget::removeChart); QObject::connect(parser, &Parser::signalRemoved, this, &ChartsWidget::removeChart); QObject::connect(reset_zoom_btn, &QPushButton::clicked, parser, &Parser::resetRange); QObject::connect(remove_all_btn, &QPushButton::clicked, this, &ChartsWidget::removeAll); QObject::connect(dock_btn, &QPushButton::clicked, [=]() { emit dock(!docking); docking = !docking; updateDockButton(); }); } void ChartsWidget::updateDockButton() { dock_btn->setText(docking ? "⬈" : "⬋"); dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); } void ChartsWidget::addChart(const QString &id, const QString &sig_name) { const QString char_name = id + sig_name; if (charts.find(char_name) == charts.end()) { auto chart = new ChartWidget(id, sig_name, this); charts_layout->insertWidget(0, chart); charts[char_name] = chart; } remove_all_btn->setVisible(true); reset_zoom_btn->setVisible(true); title_label->setText(tr("Charts (%1)").arg(charts.size())); } void ChartsWidget::removeChart(const QString &id, const QString &sig_name) { if (auto it = charts.find(id + sig_name); it != charts.end()) { it->second->deleteLater(); charts.erase(it); if (charts.empty()) { remove_all_btn->setVisible(false); reset_zoom_btn->setVisible(false); } } title_label->setText(tr("Charts (%1)").arg(charts.size())); } void ChartsWidget::removeAll() { for (auto [_, chart] : charts) chart->deleteLater(); charts.clear(); remove_all_btn->setVisible(false); reset_zoom_btn->setVisible(false); } // ChartWidget ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *parent) : id(id), sig_name(sig_name), QWidget(parent) { QStackedLayout *stacked = new QStackedLayout(this); stacked->setStackingMode(QStackedLayout::StackAll); QWidget *chart_widget = new QWidget(this); QVBoxLayout *chart_layout = new QVBoxLayout(chart_widget); chart_layout->setSpacing(0); chart_layout->setContentsMargins(0, 0, 0, 0); QWidget *header = new QWidget(this); header->setStyleSheet("background-color:white"); QHBoxLayout *header_layout = new QHBoxLayout(header); header_layout->setContentsMargins(11, 11, 11, 0); QLabel *title = new QLabel(tr("%1 %2").arg(parser->getMsg(id)->name.c_str()).arg(id)); header_layout->addWidget(title); header_layout->addStretch(); QPushButton *remove_btn = new QPushButton("✖", this); remove_btn->setFixedSize(30, 30); remove_btn->setToolTip(tr("Remove chart")); QObject::connect(remove_btn, &QPushButton::clicked, [=]() { emit parser->hidePlot(id, sig_name); }); header_layout->addWidget(remove_btn); chart_layout->addWidget(header); QLineSeries *series = new QLineSeries(); series->setUseOpenGL(true); auto chart = new QChart(); chart->setTitle(sig_name); chart->addSeries(series); chart->createDefaultAxes(); chart->legend()->hide(); QFont font; font.setBold(true); chart->setTitleFont(font); chart->setMargins({0, 0, 0, 0}); chart->layout()->setContentsMargins(0, 0, 0, 0); QObject::connect(dynamic_cast(chart->axisX()), &QValueAxis::rangeChanged, parser, &Parser::setRange); chart_view = new QChartView(chart); chart_view->setFixedHeight(300); chart_view->setRenderHint(QPainter::Antialiasing); chart_view->setRubberBand(QChartView::HorizontalRubberBand); if (auto rubber = chart_view->findChild()) { QPalette pal; pal.setBrush(QPalette::Base, QColor(0, 0, 0, 80)); rubber->setPalette(pal); } chart_layout->addWidget(chart_view); chart_layout->addStretch(); stacked->addWidget(chart_widget); line_marker = new LineMarker(this); stacked->addWidget(line_marker); line_marker->setAttribute(Qt::WA_TransparentForMouseEvents, true); line_marker->raise(); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); QObject::connect(parser, &Parser::updated, this, &ChartWidget::updateState); QObject::connect(parser, &Parser::rangeChanged, this, &ChartWidget::rangeChanged); QObject::connect(parser, &Parser::eventsMerged, this, &ChartWidget::updateSeries); updateSeries(); } void ChartWidget::updateState() { auto chart = chart_view->chart(); auto axis_x = dynamic_cast(chart->axisX()); if (axis_x->max() <= axis_x->min()) return; int x = chart->plotArea().left() + chart->plotArea().width() * (parser->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); if (line_marker_x != x) { line_marker->setX(x); line_marker_x = x; } } void ChartWidget::updateSeries() { const Signal *sig = parser->getSig(id, sig_name); auto events = parser->replay->events(); if (!sig || !events) return; auto l = id.split(':'); int bus = l[0].toInt(); uint32_t address = l[1].toUInt(nullptr, 16); vals.clear(); vals.reserve(3 * 60 * 100); double min_y = 0, max_y = 0; uint64_t route_start_time = parser->replay->routeStartTime(); for (auto &evt : *events) { if (evt->which == cereal::Event::Which::CAN) { for (auto c : evt->event.getCan()) { if (bus == c.getSrc() && address == c.getAddress()) { auto dat = c.getDat(); int64_t val = get_raw_value((uint8_t *)dat.begin(), dat.size(), *sig); if (sig->is_signed) { val -= ((val >> (sig->size - 1)) & 0x1) ? (1ULL << sig->size) : 0; } double value = val * sig->factor + sig->offset; if (value < min_y) min_y = value; if (value > max_y) max_y = value; double ts = (evt->mono_time - route_start_time) / (double)1e9; // seconds vals.push_back({ts, value}); } } } } QLineSeries *series = (QLineSeries *)chart_view->chart()->series()[0]; series->replace(vals); auto [begin, end] = parser->range(); chart_view->chart()->axisX()->setRange(begin, end); chart_view->chart()->axisY()->setRange(min_y * 0.95, max_y * 1.05); } void ChartWidget::rangeChanged(qreal min, qreal max) { auto axis_x = dynamic_cast(chart_view->chart()->axisX()); if (axis_x->min() != min || axis_x->max() != max) { axis_x->setRange(min, max); } // auto zoom on yaxis double min_y = 0, max_y = 0; for (auto &p : vals) { if (p.x() > max) break; if (p.x() >= min) { if (p.y() < min_y) min_y = p.y(); if (p.y() > max_y) max_y = p.y(); } } chart_view->chart()->axisY()->setRange(min_y * 0.95, max_y * 1.05); } // LineMarker void LineMarker::setX(double x) { x_pos = x; update(); } void LineMarker::paintEvent(QPaintEvent *event) { QPainter p(this); p.setPen(QPen(Qt::black, 2)); p.drawLine(QPointF{x_pos, 50.}, QPointF{x_pos, (qreal)height() - 11}); }