mempoolstats.cpp raw

   1  // Copyright (c) 2016 The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <qt/mempoolstats.h>
   6  #include <qt/forms/ui_mempoolstats.h>
   7  
   8  #include <qt/clientmodel.h>
   9  #include <qt/guiutil.h>
  10  #include <stats/stats.h>
  11  
  12  #include <math.h>
  13  
  14  #include <QApplication>
  15  #include <QColor>
  16  #include <QPalette>
  17  
  18  static const char *LABEL_FONT = "Arial";
  19  static int LABEL_TITLE_SIZE = 22;
  20  static int LABEL_KV_SIZE = 12;
  21  
  22  static const int TEN_MINS = 600;
  23  static const int ONE_HOUR = 3600;
  24  static const int ONE_DAY = ONE_HOUR*24;
  25  
  26  static const int LABEL_LEFT_SIZE = 30;
  27  static const int LABEL_RIGHT_SIZE = 30;
  28  static const int GRAPH_PADDING_LEFT = 30+LABEL_LEFT_SIZE;
  29  static const int GRAPH_PADDING_RIGHT = 30+LABEL_RIGHT_SIZE;
  30  static const int GRAPH_PADDING_TOP = 10;
  31  static const int GRAPH_PADDING_TOP_LABEL = 150;
  32  static const int GRAPH_PADDING_BOTTOM = 50;
  33  static const int LABEL_HEIGHT = 15;
  34  
  35  static const MempoolStats::ThemeColors LIGHT_THEME_COLORS = {
  36      .orange = QColor(216, 92, 1, 250),
  37      .green = QColor(0, 125, 50, 250),
  38      .blue = QColor(2, 61, 204, 250),
  39  };
  40  
  41  static const MempoolStats::ThemeColors DARK_THEME_COLORS = {
  42      .orange = QColor(247, 147, 26, 250),
  43      .green = QColor(69, 222, 181, 250),
  44      .blue = QColor(137, 170, 255, 250),
  45  };
  46  
  47  void ClickableTextItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
  48  {
  49      Q_EMIT objectClicked(this);
  50  }
  51  
  52  void ClickableTextItem::setEnabled(bool state)
  53  {
  54      if (state)
  55          // Use system default text color for active item
  56          setDefaultTextColor(QApplication::palette().color(QPalette::WindowText));
  57      else
  58          // Use system disabled color
  59          setDefaultTextColor(QApplication::palette().color(QPalette::Disabled, QPalette::WindowText));
  60  }
  61  
  62  MempoolStats::MempoolStats(QWidget *parent) :
  63  QWidget(parent, Qt::Window),
  64  timeFilter(TEN_MINS),
  65  ui(new Ui::MempoolStats)
  66  {
  67      ui->setupUi(this);
  68      if (parent) {
  69          parent->installEventFilter(this);
  70          raise();
  71      }
  72  
  73      // autoadjust font size
  74      QGraphicsTextItem testText("jY"); //screendesign expected 27.5 pixel in width for this string
  75      testText.setFont(QFont(LABEL_FONT, LABEL_TITLE_SIZE, QFont::Light));
  76      LABEL_TITLE_SIZE *= 27.5/testText.boundingRect().width();
  77      LABEL_KV_SIZE *= 27.5/testText.boundingRect().width();
  78  
  79      scene = new QGraphicsScene();
  80      ui->graphicsView->setScene(scene);
  81      ui->graphicsView->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
  82  
  83      updateThemeColors();
  84  
  85      if (clientModel)
  86          drawChart();
  87  }
  88  
  89  void MempoolStats::setClientModel(ClientModel *model)
  90  {
  91      clientModel = model;
  92  
  93      if (model)
  94          connect(model, SIGNAL(mempoolStatsDidUpdate()), this, SLOT(drawChart()));
  95  }
  96  
  97  void MempoolStats::drawChart()
  98  {
  99      if (!(isVisible() && clientModel))
 100          return;
 101  
 102      if (!titleItem)
 103      {
 104          // create labels (only once)
 105          titleItem = scene->addText(tr("Mempool Statistics"));
 106          titleItem->setFont(QFont(LABEL_FONT, LABEL_TITLE_SIZE, QFont::Light));
 107          titleLine = scene->addLine(0,0,100,100);
 108          titleLine->setPen(QPen(QColor(100,100,100, 200), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
 109  
 110          cbShowMemUsage = new QCheckBox("Dynamic Memory Usage");
 111          cbShowMemUsage->setChecked(true);
 112          dynMemUsageSwitch = scene->addWidget(cbShowMemUsage);
 113          connect(cbShowMemUsage, SIGNAL(stateChanged(int)), this, SLOT(drawChart()));
 114          cbShowMemUsage->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Light));
 115          dynMemUsageValueItem = scene->addText("N/A");
 116          dynMemUsageValueItem->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Bold));
 117  
 118          cbShowNumTxns = new QCheckBox("Amount of Transactions");
 119          cbShowNumTxns->setChecked(true);
 120          txCountSwitch = scene->addWidget(cbShowNumTxns);
 121          scene->addItem(txCountSwitch);
 122          connect(cbShowNumTxns, SIGNAL(stateChanged(int)), this, SLOT(drawChart()));
 123          cbShowNumTxns->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Light));
 124          txCountValueItem = scene->addText("N/A");
 125          txCountValueItem->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Bold));
 126  
 127          cbShowMinFeerate = new QCheckBox("Min relay fee per kB");
 128          cbShowMinFeerate->setChecked(false);
 129          minFeeSwitch = scene->addWidget(cbShowMinFeerate);
 130          scene->addItem(minFeeSwitch);
 131          connect(cbShowMinFeerate, SIGNAL(stateChanged(int)), this, SLOT(drawChart()));
 132          cbShowMinFeerate->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Light));
 133          minFeeValueItem = scene->addText(tr("N/A"));
 134          minFeeValueItem->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Bold));
 135  
 136          noDataItem = scene->addText(tr("No Data available"));
 137          noDataItem->setFont(QFont(LABEL_FONT, LABEL_TITLE_SIZE, QFont::Light));
 138          noDataItem->setDefaultTextColor(QColor(100,100,100, 200));
 139  
 140          last10MinLabel = new ClickableTextItem(); last10MinLabel->setPlainText(tr("Last 10 min"));
 141          scene->addItem(last10MinLabel);
 142          connect(last10MinLabel, SIGNAL(objectClicked(QGraphicsItem*)), this, SLOT(objectClicked(QGraphicsItem*)));
 143          last10MinLabel->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Light));
 144          lastHourLabel = new ClickableTextItem(); lastHourLabel->setPlainText(tr("Last Hour"));
 145          scene->addItem(lastHourLabel);
 146          connect(lastHourLabel, SIGNAL(objectClicked(QGraphicsItem*)), this, SLOT(objectClicked(QGraphicsItem*)));
 147          lastHourLabel->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Light));
 148          lastDayLabel = new ClickableTextItem(); lastDayLabel->setPlainText(tr("Last Day"));
 149          scene->addItem(lastDayLabel);
 150          connect(lastDayLabel, SIGNAL(objectClicked(QGraphicsItem*)), this, SLOT(objectClicked(QGraphicsItem*)));
 151          lastDayLabel->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Light));
 152          allDataLabel = new ClickableTextItem(); allDataLabel->setPlainText(tr("All Data"));
 153          scene->addItem(allDataLabel);
 154          connect(allDataLabel, SIGNAL(objectClicked(QGraphicsItem*)), this, SLOT(objectClicked(QGraphicsItem*)));
 155          allDataLabel->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Light));
 156      }
 157  
 158      // Use transparent background to respect system theme
 159      static const QString checkbox_style_base = QStringLiteral("background-color:transparent;");
 160  
 161      // Set checkbox colors based on theme, using HexRgb to intentionally omit alpha channel
 162      cbShowNumTxns->setStyleSheet(cbShowNumTxns->isChecked() ? (checkbox_style_base + "color:" + m_theme_colors->orange.name(QColor::HexRgb) + ";") : checkbox_style_base);
 163      cbShowMinFeerate->setStyleSheet(cbShowMinFeerate->isChecked() ? (checkbox_style_base + "color:" + m_theme_colors->green.name(QColor::HexRgb) + ";") : checkbox_style_base);
 164      cbShowMemUsage->setStyleSheet(cbShowMemUsage->isChecked() ? (checkbox_style_base + "color:" + m_theme_colors->blue.name(QColor::HexRgb) + ";") : checkbox_style_base);
 165  
 166      last10MinLabel->setEnabled((timeFilter == TEN_MINS));
 167      lastHourLabel->setEnabled((timeFilter == ONE_HOUR));
 168      lastDayLabel->setEnabled((timeFilter == ONE_DAY));
 169      allDataLabel->setEnabled((timeFilter == 0));
 170  
 171      // Update text items with system text color
 172      if (titleItem) titleItem->setDefaultTextColor(m_text_color);
 173      if (dynMemUsageValueItem) dynMemUsageValueItem->setDefaultTextColor(m_text_color);
 174      if (txCountValueItem) txCountValueItem->setDefaultTextColor(m_text_color);
 175      if (minFeeValueItem) minFeeValueItem->setDefaultTextColor(m_text_color);
 176  
 177      // remove the items which needs to be redrawn
 178      for (QGraphicsItem * item : redrawItems)
 179      {
 180          scene->removeItem(item);
 181          delete item;
 182      }
 183      redrawItems.clear();
 184  
 185      // get the samples
 186      QDateTime toDateTime = QDateTime::currentDateTime();
 187      QDateTime fromDateTime = toDateTime.addSecs(-timeFilter); //-1h
 188      if (timeFilter == 0)
 189      {
 190          // disable filter if timeFilter == 0
 191          toDateTime.setSecsSinceEpoch(0);
 192          fromDateTime.setSecsSinceEpoch(0);
 193      }
 194  
 195      mempoolSamples_t vSamples = clientModel->getMempoolStatsInRange(fromDateTime, toDateTime);
 196  
 197      // set the values into the overview labels
 198      if (vSamples.size())
 199      {
 200          dynMemUsageValueItem->setPlainText(GUIUtil::formatBytes((uint64_t)vSamples.back().m_dyn_mem_usage));
 201          txCountValueItem->setPlainText(QString::number(vSamples.back().m_tx_count));
 202          minFeeValueItem->setPlainText(QString::number(vSamples.back().m_min_fee_per_k));
 203      }
 204  
 205      // set dynamic label positions
 206      int maxValueSize = std::max(std::max(txCountValueItem->boundingRect().width(), dynMemUsageValueItem->boundingRect().width()), minFeeValueItem->boundingRect().width());
 207      maxValueSize = ceil(maxValueSize*0.11)*10; //use size steps of 10dip
 208  
 209      int rightPaddingLabels = std::max(std::max(dynMemUsageSwitch->boundingRect().width(), txCountSwitch->boundingRect().width()), minFeeSwitch->boundingRect().width())+maxValueSize;
 210      int rightPadding = 10;
 211      dynMemUsageSwitch->setPos(width()-rightPaddingLabels-rightPadding, 5);
 212  
 213      txCountSwitch->setPos(width()-rightPaddingLabels-rightPadding, dynMemUsageSwitch->pos().y()+dynMemUsageSwitch->boundingRect().height());
 214  
 215      minFeeSwitch->setPos(width()-rightPaddingLabels-rightPadding, txCountSwitch->pos().y()+txCountSwitch->boundingRect().height());
 216  
 217      dynMemUsageValueItem->setPos(width()-dynMemUsageValueItem->boundingRect().width()-rightPadding, dynMemUsageSwitch->pos().y());
 218      txCountValueItem->setPos(width()-txCountValueItem->boundingRect().width()-rightPadding, txCountSwitch->pos().y());
 219      minFeeValueItem->setPos(width()-minFeeValueItem->boundingRect().width()-rightPadding, minFeeSwitch->pos().y());
 220  
 221      titleItem->setPos(5,minFeeSwitch->pos().y()+minFeeSwitch->boundingRect().height()-titleItem->boundingRect().height()+10);
 222      titleLine->setLine(10, titleItem->pos().y()+titleItem->boundingRect().height(), width()-10, titleItem->pos().y()+titleItem->boundingRect().height());
 223  
 224      // center the optional "no data" label
 225      noDataItem->setPos(width()/2.0-noDataItem->boundingRect().width()/2.0, height()/2.0);
 226  
 227      // set the position of the filter icons
 228      static const int filterBottomPadding = 30;
 229      int totalWidth = last10MinLabel->boundingRect().width()+lastHourLabel->boundingRect().width()+lastDayLabel->boundingRect().width()+allDataLabel->boundingRect().width()+30;
 230      last10MinLabel->setPos((width()-totalWidth)/2.0,height()-filterBottomPadding);
 231      lastHourLabel->setPos((width()-totalWidth)/2.0+last10MinLabel->boundingRect().width()+10,height()-filterBottomPadding);
 232      lastDayLabel->setPos((width()-totalWidth)/2.0+last10MinLabel->boundingRect().width()+lastHourLabel->boundingRect().width()+20,height()-filterBottomPadding);
 233      allDataLabel->setPos((width()-totalWidth)/2.0+last10MinLabel->boundingRect().width()+lastHourLabel->boundingRect().width()+lastDayLabel->boundingRect().width()+30,height()-filterBottomPadding);
 234  
 235      // don't paint the grind/graph if there are no or only a single sample
 236      if (vSamples.size() < 2)
 237      {
 238          noDataItem->setVisible(true);
 239          return;
 240      }
 241      noDataItem->setVisible(false);
 242  
 243      int bottom = ui->graphicsView->size().height()-GRAPH_PADDING_BOTTOM;
 244      qreal maxwidth = ui->graphicsView->size().width()-GRAPH_PADDING_LEFT-GRAPH_PADDING_RIGHT;
 245      qreal maxheightG = ui->graphicsView->size().height()-GRAPH_PADDING_TOP-GRAPH_PADDING_TOP_LABEL-LABEL_HEIGHT;
 246      float paddingTopSizeFactor = 1.2;
 247      qreal step = maxwidth/(double)vSamples.size();
 248  
 249      // make sure we skip samples that would be drawn narrower then 1px
 250      // larger window can result in drawing more samples
 251      int samplesStep = 1;
 252      if (step < 1)
 253          samplesStep = ceil(1/samplesStep);
 254  
 255      // find maximum values
 256      int64_t maxDynMemUsage = 0;
 257      int64_t minDynMemUsage = std::numeric_limits<int64_t>::max();
 258      int64_t maxTxCount = 0;
 259      int64_t minTxCount = std::numeric_limits<int64_t>::max();
 260      int64_t maxMinFee = 0;
 261      uint32_t maxTimeDetla = vSamples.back().m_time_delta-vSamples.front().m_time_delta;
 262      for(const struct CStatsMempoolSample &sample : vSamples)
 263      {
 264          if (sample.m_dyn_mem_usage > maxDynMemUsage)
 265              maxDynMemUsage = sample.m_dyn_mem_usage;
 266  
 267          if (sample.m_dyn_mem_usage < minDynMemUsage)
 268              minDynMemUsage = sample.m_dyn_mem_usage;
 269  
 270          if (sample.m_tx_count > maxTxCount)
 271              maxTxCount = sample.m_tx_count;
 272  
 273          if (sample.m_tx_count < minTxCount)
 274              minTxCount = sample.m_tx_count;
 275  
 276          if (sample.m_min_fee_per_k > maxMinFee)
 277              maxMinFee = sample.m_min_fee_per_k;
 278      }
 279  
 280      int64_t dynMemUsagelog10Val = pow(10.0, floor(log10(maxDynMemUsage*paddingTopSizeFactor-minDynMemUsage)));
 281      int64_t topDynMemUsage = ceil((double)maxDynMemUsage*paddingTopSizeFactor/dynMemUsagelog10Val)*dynMemUsagelog10Val;
 282      int64_t bottomDynMemUsage = floor((double)minDynMemUsage/dynMemUsagelog10Val)*dynMemUsagelog10Val;
 283  
 284      int64_t txCountLog10Val = pow(10.0, floor(log10(maxTxCount*paddingTopSizeFactor-minTxCount)));
 285      int64_t topTxCount = ceil((double)maxTxCount*paddingTopSizeFactor/txCountLog10Val)*txCountLog10Val;
 286      int64_t bottomTxCount = floor((double)minTxCount/txCountLog10Val)*txCountLog10Val;
 287  
 288      qreal currentX = GRAPH_PADDING_LEFT;
 289      QPainterPath dynMemUsagePath(QPointF(currentX, bottom));
 290      QPainterPath txCountPath(QPointF(currentX, bottom));
 291      QPainterPath minFeePath(QPointF(currentX, bottom));
 292  
 293      // draw the three possible paths
 294      for (mempoolSamples_t::iterator it = vSamples.begin(); it != vSamples.end(); it+=samplesStep)
 295      {
 296          const struct CStatsMempoolSample &sample = (*it);
 297          qreal xPos = maxTimeDetla > 0 ? maxwidth/maxTimeDetla*(sample.m_time_delta-vSamples.front().m_time_delta) : maxwidth/(double)vSamples.size();
 298          if (sample.m_time_delta == vSamples.front().m_time_delta)
 299          {
 300              dynMemUsagePath.moveTo(GRAPH_PADDING_LEFT+xPos, bottom-maxheightG/(topDynMemUsage-bottomDynMemUsage)*(sample.m_dyn_mem_usage-bottomDynMemUsage));
 301              txCountPath.moveTo(GRAPH_PADDING_LEFT+xPos, bottom-maxheightG/(topTxCount-bottomTxCount)*(sample.m_tx_count-bottomTxCount));
 302              minFeePath.moveTo(GRAPH_PADDING_LEFT+xPos, bottom-maxheightG/maxMinFee*sample.m_min_fee_per_k);
 303          }
 304          else
 305          {
 306              dynMemUsagePath.lineTo(GRAPH_PADDING_LEFT+xPos, bottom-maxheightG/(topDynMemUsage-bottomDynMemUsage)*(sample.m_dyn_mem_usage-bottomDynMemUsage));
 307              txCountPath.lineTo(GRAPH_PADDING_LEFT+xPos, bottom-maxheightG/(topTxCount-bottomTxCount)*(sample.m_tx_count-bottomTxCount));
 308              minFeePath.lineTo(GRAPH_PADDING_LEFT+xPos, bottom-maxheightG/maxMinFee*sample.m_min_fee_per_k);
 309          }
 310      }
 311  
 312      // copy the path for the fill
 313      QPainterPath dynMemUsagePathFill(dynMemUsagePath);
 314  
 315      // close the path for the fill
 316      dynMemUsagePathFill.lineTo(GRAPH_PADDING_LEFT+maxwidth, bottom);
 317      dynMemUsagePathFill.lineTo(GRAPH_PADDING_LEFT, bottom);
 318  
 319      QPainterPath dynMemUsageGridPath(QPointF(currentX, bottom));
 320  
 321      // draw horizontal grid
 322      int amountOfLinesH = 5;
 323      QFont gridFont;
 324      gridFont.setPointSize(8);
 325      for (int i=0; i < amountOfLinesH; i++)
 326      {
 327          qreal lY = bottom-i*(maxheightG/(amountOfLinesH-1));
 328          dynMemUsageGridPath.moveTo(GRAPH_PADDING_LEFT, lY);
 329          dynMemUsageGridPath.lineTo(GRAPH_PADDING_LEFT+maxwidth, lY);
 330  
 331          size_t gridDynSize = (float)i*(topDynMemUsage-bottomDynMemUsage)/(amountOfLinesH-1) + bottomDynMemUsage;
 332          size_t gridTxCount = (float)i*(topTxCount-bottomTxCount)/(amountOfLinesH-1) + bottomTxCount;
 333  
 334          QGraphicsTextItem *itemDynSize = scene->addText(GUIUtil::formatBytes(gridDynSize), gridFont);
 335          QGraphicsTextItem *itemTxCount = scene->addText(QString::number(gridTxCount), gridFont);
 336          itemDynSize->setDefaultTextColor(m_text_color);
 337          itemTxCount->setDefaultTextColor(m_text_color);
 338  
 339          itemDynSize->setPos(GRAPH_PADDING_LEFT-itemDynSize->boundingRect().width(), lY-(itemDynSize->boundingRect().height()/2));
 340          itemTxCount->setPos(GRAPH_PADDING_LEFT+maxwidth, lY-(itemDynSize->boundingRect().height()/2));
 341          redrawItems.append(itemDynSize);
 342          redrawItems.append(itemTxCount);
 343      }
 344  
 345      // draw vertical grid
 346      int amountOfLinesV = 4;
 347      QDateTime drawTime(fromDateTime);
 348      std::string fromS = fromDateTime.toString().toStdString();
 349      std::string toS = toDateTime.toString().toStdString();
 350      qint64 secsTotal = fromDateTime.secsTo(toDateTime);
 351      for (int i=0; i <= amountOfLinesV; i++)
 352      {
 353          qreal lX = i*(maxwidth/(amountOfLinesV));
 354          dynMemUsageGridPath.moveTo(GRAPH_PADDING_LEFT+lX, bottom);
 355          dynMemUsageGridPath.lineTo(GRAPH_PADDING_LEFT+lX, bottom-maxheightG);
 356  
 357          QGraphicsTextItem *item = scene->addText(drawTime.toString("HH:mm"), gridFont);
 358          item->setDefaultTextColor(m_text_color);
 359          item->setPos(GRAPH_PADDING_LEFT+lX-(item->boundingRect().width()/2), bottom);
 360          redrawItems.append(item);
 361          qint64 step = secsTotal/amountOfLinesV;
 362          drawTime = drawTime.addSecs(step);
 363      }
 364  
 365      // materialize path
 366      QPen gridPen(QColor(100,100,100, 200), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
 367      redrawItems.append(scene->addPath(dynMemUsageGridPath, gridPen));
 368  
 369      // draw semi-transparent gradient for the dynamic memory size fill
 370      QLinearGradient gradient(currentX, bottom, currentX, 0);
 371      gradient.setColorAt(1.0, m_theme_colors->blue);
 372      QColor gradientBase = m_bg_color;
 373      gradientBase.setAlpha(0);
 374      gradient.setColorAt(0, gradientBase);
 375      QBrush graBru(gradient);
 376  
 377      QPen linePenBlue(m_theme_colors->blue, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
 378      QPen linePenRed(m_theme_colors->orange, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
 379      QPen linePenGreen(m_theme_colors->green, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
 380  
 381      // Draw gradient fill first (underneath everything)
 382      if (cbShowMemUsage->isChecked()) {
 383          redrawItems.append(scene->addPath(dynMemUsagePathFill, QPen(Qt::NoPen), graBru));
 384          redrawItems.append(scene->addPath(dynMemUsagePath, linePenBlue));
 385      }
 386      if (cbShowNumTxns->isChecked())
 387          redrawItems.append(scene->addPath(txCountPath, linePenRed));
 388      if (cbShowMinFeerate->isChecked())
 389          redrawItems.append(scene->addPath(minFeePath, linePenGreen));
 390  }
 391  
 392  // We override the virtual resizeEvent of the QWidget to adjust tables column
 393  // sizes as the tables width is proportional to the dialogs width.
 394  void MempoolStats::resizeEvent(QResizeEvent *event)
 395  {
 396      QWidget::resizeEvent(event);
 397      ui->graphicsView->resize(size());
 398      ui->graphicsView->scene()->setSceneRect(rect());
 399      drawChart();
 400  }
 401  
 402  void MempoolStats::showEvent(QShowEvent *event)
 403  {
 404      QWidget::showEvent(event);
 405      if (clientModel)
 406          drawChart();
 407  }
 408  
 409  void MempoolStats::changeEvent(QEvent* e)
 410  {
 411      if (e->type() == QEvent::PaletteChange) {
 412          updateThemeColors();
 413          drawChart();  // Redraw with new theme colors
 414      }
 415      QWidget::changeEvent(e);
 416  }
 417  
 418  void MempoolStats::objectClicked(QGraphicsItem *item)
 419  {
 420      if (item == last10MinLabel)
 421          timeFilter = 600;
 422  
 423      if (item == lastHourLabel)
 424          timeFilter = 3600;
 425  
 426      if (item == lastDayLabel)
 427          timeFilter = 24*3600;
 428  
 429      if (item == allDataLabel)
 430          timeFilter = 0;
 431  
 432      drawChart();
 433  }
 434  
 435  void MempoolStats::updateThemeColors()
 436  {
 437      // Detect dark mode for color palette selection
 438      const QColor bg_colour = palette().color(backgroundRole());
 439      const bool dark_mode = GUIUtil::isDarkMode(bg_colour);
 440  
 441      // Store background color for gradient use
 442      m_bg_color = bg_colour;
 443  
 444      // Store text color for labels
 445      m_text_color = palette().color(foregroundRole());
 446  
 447      m_theme_colors = dark_mode ? &DARK_THEME_COLORS : &LIGHT_THEME_COLORS;
 448  }
 449  
 450  MempoolStats::~MempoolStats()
 451  {
 452      if (titleItem)
 453      {
 454          for (QGraphicsItem * item : redrawItems)
 455          {
 456              scene->removeItem(item);
 457              delete item;
 458          }
 459          redrawItems.clear();
 460  
 461          delete titleItem;
 462          delete titleLine;
 463          delete noDataItem;
 464          delete dynMemUsageValueItem;
 465          delete txCountValueItem;
 466          delete minFeeValueItem;
 467          delete last10MinLabel;
 468          delete lastHourLabel;
 469          delete lastDayLabel;
 470          delete allDataLabel;
 471          delete txCountSwitch;
 472          delete minFeeSwitch;
 473          delete dynMemUsageSwitch;
 474          delete scene;
 475      }
 476  }
 477