  #include <QTableWidget>
  #include <QTreeView>
  
  #include "tools/cabana/chart/chartswidget.h"
  
  class SignalModel : public QAbstractItemModel {
    Q_OBJECT
  public:
    struct Item {
      enum Type {Root, Sig, Name, Size, Endian, Signed, Offset, Factor, ExtraInfo, Unit, Comment, Min, Max, Desc };
 
</s> remove   int value_width = 0;
 </s> add  </s> remove   model->updateState(nullptr);
 </s> add   updateState();
}

void SignalView::handleSignalUpdated(const cabana::Signal *sig) {
  if (int row = model->signalRow(sig); row != -1) {
    auto item = model->getItem(model->index(row, 1));
    // invalidate the sparkline
    item->sparkline.last_ts = 0;
    updateState();
  }
}

void SignalView::updateState(const QHash<MessageId, CanData> *msgs) {
  if (model->rowCount() == 0 || (msgs && !msgs->contains(model->msg_id))) return;

  const auto &last_msg = can->lastMessage(model->msg_id);
  for (auto item : model->root->children) {
    double value = get_raw_value((uint8_t *)last_msg.dat.constData(), last_msg.dat.size(), *item->sig);
    item->sig_val = QString::number(value, 'f', item->sig->precision);
    // Show unit
    if (!item->sig->unit.isEmpty()) {
      item->sig_val += " " + item->sig->unit;
    }
    // Show enum string
    for (auto &[val, desc] : item->sig->val_desc) {
      if (std::abs(value - val.toInt()) < 1e-6) {
        item->sig_val = desc;
      }
    }
    max_value_width = std::max(max_value_width, fontMetrics().width(item->sig_val));
  }

  // update visible sparkline
  QSize size(tree->columnWidth(1) - delegate->button_size.width(), delegate->button_size.height());
  int min_max_width = std::min(size.width() - 10, QFontMetrics(delegate->minmax_font).width("-000.00") + 5);
  int value_width = std::min<int>(max_value_width, size.width() * 0.35);
  size -= {value_width + min_max_width, style()->pixelMetric(QStyle::PM_FocusFrameVMargin) * 2};

  QModelIndex top = tree->indexAt(QPoint(0, 0));
  QModelIndex bottom = tree->indexAt(tree->viewport()->rect().bottomLeft());
  int start_row = top.parent().isValid() ? top.parent().row() + 1 : top.row();
  int end_row = model->rowCount() - 1;
  if (bottom.isValid()) {
    end_row = bottom.parent().isValid() ? bottom.parent().row() : bottom.row();
  }
  QFutureSynchronizer<void> synchronizer;
  for (int i = start_row; i <= end_row; ++i) {
    auto item = model->getItem(model->index(i, 1));
    auto &s = item->sparkline;
    if (s.last_ts != last_msg.ts || s.size() != size || s.time_range != settings.sparkline_range) {
      synchronizer.addFuture(QtConcurrent::run(
          &s, &Sparkline::update, model->msg_id, item->sig, last_msg.ts, settings.sparkline_range, size));
    }
  }
  synchronizer.waitForFinished();

  for (int i = 0; i < model->rowCount(); ++i) {
    emit model->dataChanged(model->index(i, 1), model->index(i, 1), {Qt::DisplayRole});
  }
}

void SignalView::resizeEvent(QResizeEvent* event) {
  updateState();
  QFrame::resizeEvent(event); </s> remove   QObject::connect(can, &AbstractStream::msgsReceived, this, &SignalModel::updateState);
 </s> add  </s> remove     bool chart_opened = charts->hasSignal(msg_id, item->sig);
 </s> add     bool chart_opened = charts->hasSignal(model->msg_id, item->sig);
#include "tools/cabana/chart/sparkline.h"
  #include <QTableWidget>
  #include <QTreeView>
  
  #include "tools/cabana/chart/chartswidget.h"
#include "tools/cabana/chart/sparkline.h"
  
  class SignalModel : public QAbstractItemModel {
    Q_OBJECT
  public:
    struct Item {
      enum Type {Root, Sig, Name, Size, Endian, Signed, Offset, Factor, ExtraInfo, Unit, Comment, Min, Max, Desc };
keep keep keep add keep keep keep keep keep keep
 cabana: paint sparkline in threads (#27925)

* multiple threading realtime sparkline

* delay visible

* remove msg_id

* cleanup 
tools/cabana/signalview.h