openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for over 200 supported car makes and models.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

275 lines
9.0 KiB

#include "tools/cabana/chartswidget.h"
#include <QGraphicsLayout>
#include <QLabel>
#include <QRubberBand>
#include <QStackedLayout>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
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<QValueAxis *>(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<QRubberBand *>()) {
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<QValueAxis *>(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<QValueAxis *>(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});
}