#include "tools/cabana/chartswidget.h" #include #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(QWidget *parent) : QWidget(parent) { main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); connect(parser, &Parser::showPlot, this, &ChartsWidget::addChart); connect(parser, &Parser::hidePlot, this, &ChartsWidget::removeChart); connect(parser, &Parser::signalRemoved, this, &ChartsWidget::removeChart); } 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); main_layout->addWidget(chart); charts[char_name] = chart; } } 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); } } 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); auto title = new QLabel(tr("%1 %2").arg(parser->getMsg(id)->name.c_str()).arg(id)); header_layout->addWidget(title); header_layout->addStretch(); zoom_label = new QLabel("", this); header_layout->addWidget(zoom_label); QPushButton *zoom_in = new QPushButton("↺", this); zoom_in->setToolTip(tr("reset zoom")); QObject::connect(zoom_in, &QPushButton::clicked, []() { parser->resetRange(); }); header_layout->addWidget(zoom_in); QPushButton *remove_btn = new QPushButton("✖", this); 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(chart, 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() { line_marker->update(); } 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); 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; 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); } 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::LineMarker(QChart *chart, QWidget *parent) : chart(chart), QWidget(parent) {} void LineMarker::paintEvent(QPaintEvent *event) { auto axis_x = dynamic_cast(chart->axisX()); if (axis_x->max() <= axis_x->min()) return; double x = chart->plotArea().left() + chart->plotArea().width() * (parser->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); QPainter p(this); QPen pen = QPen(Qt::black); pen.setWidth(2); p.setPen(pen); p.drawLine(QPointF{x, 50.}, QPointF{x, (qreal)height() - 11}); }