  
    // tree view
    tree = new TreeView(this);
    tree->setModel(model = new SignalModel(this));
    tree->setItemDelegate(new SignalItemDelegate(this));
    tree->setFrameShape(QFrame::NoFrame);
    tree->setHeaderHidden(true);
    tree->setMouseTracking(true);
    tree->setExpandsOnDoubleClick(false);
    tree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
 
</s> remove   QObject::connect(can, &AbstractStream::msgsReceived, this, &SignalModel::updateState);
 </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 void SignalItemDelegate::drawSparkline(QPainter *painter, const QRect &rect, const QStyleOptionViewItem &option, const QModelIndex &index) const {
  static std::vector<QPointF> points;
  const auto &msg_id = ((SignalView *)parent())->msg_id;
  const auto &msgs = can->events().at(msg_id);

  uint64_t ts = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9;
  auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), CanEvent{.mono_time = (uint64_t)std::max<int64_t>(ts - settings.sparkline_range * 1e9, 0)});
  auto last = std::upper_bound(first, msgs.cend(), CanEvent{.mono_time = ts});

  if (first != last) {
    double min = std::numeric_limits<double>::max();
    double max = std::numeric_limits<double>::lowest();
    const auto item = (const SignalModel::Item *)index.internalPointer();
    const auto sig = item->sig;
    points.clear();
    for (auto it = first; it != last; ++it) {
      double value = get_raw_value(it->dat, it->size, *sig);
      points.emplace_back((it->mono_time - first->mono_time) / 1e9, value);
      min = std::min(min, value);
      max = std::max(max, value);
    }
    if (min == max) {
      min -= 1;
      max += 1;
    }

    const double min_max_width = std::min(rect.width() - 10, QFontMetrics(minmax_font).width("000.00") + 5);
    const QRect r = rect.adjusted(0, 0, -min_max_width, 0);
    const double xscale = r.width() / (double)settings.sparkline_range;
    const double yscale = r.height() / (max - min);
    for (auto &pt : points) {
      pt.rx() = r.left() + pt.x() * xscale;
      pt.ry() = r.top() + std::abs(pt.y() - max) * yscale;
    }

    auto color = item->highlight ? getColor(sig).darker(125) : getColor(sig);
    painter->setPen(color);
    painter->drawPolyline(points.data(), points.size());
    if ((points.back().x() - points.front().x()) / points.size() > 10) {
      painter->setPen(Qt::NoPen);
      painter->setBrush(color);
      for (const auto &pt : points) {
        painter->drawEllipse(pt, 2, 2);
      }
    }

    if (item->highlight || option.state & QStyle::State_Selected) {
      painter->setFont(minmax_font);
      painter->setPen(option.state & QStyle::State_Selected ? option.palette.color(QPalette::HighlightedText) : Qt::darkGray);
      painter->drawLine(r.topRight(), r.bottomRight());
      QRect minmax_rect{r.right() + 5, r.top(), 1000, r.height()};
      painter->drawText(minmax_rect, Qt::AlignLeft | Qt::AlignTop, QString::number(max));
      painter->drawText(minmax_rect, Qt::AlignLeft | Qt::AlignBottom, QString::number(min));
    }
  }
}

 </s> add  </s> remove       current_charts[i]->setVisible(true);
 </s> add       if (current_charts[i]->sigs.isEmpty()) {
        // the chart will be resized after add signal. delay setVisible to reduce flicker.
        QTimer::singleShot(0, [c = current_charts[i]]() { c->setVisible(true); });
      } else {
        current_charts[i]->setVisible(true);
      }
  tree->setItemDelegate(delegate = new SignalItemDelegate(this));
  
    // tree view
    tree = new TreeView(this);
    tree->setModel(model = new SignalModel(this));
  tree->setItemDelegate(delegate = new SignalItemDelegate(this));
    tree->setFrameShape(QFrame::NoFrame);
    tree->setHeaderHidden(true);
    tree->setMouseTracking(true);
    tree->setExpandsOnDoubleClick(false);
    tree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
keep keep keep keep replace keep keep keep keep keep
 cabana: paint sparkline in threads (#27925)

* multiple threading realtime sparkline

* delay visible

* remove msg_id

* cleanup 
tools/cabana/signalview.cc