File indexing completed on 2024-12-15 03:45:01

0001 /*
0002     SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: MIT
0005 */
0006 
0007 #include "ratiosetaggregationmodel.h"
0008 #include <model/timeaggregationmodel.h>
0009 #include <core/sample.h>
0010 
0011 #include <QSet>
0012 
0013 using namespace KUserFeedback::Console;
0014 
0015 RatioSetAggregationModel::RatioSetAggregationModel(QObject *parent) :
0016     QAbstractTableModel(parent)
0017 {
0018 }
0019 
0020 RatioSetAggregationModel::~RatioSetAggregationModel()
0021 {
0022     delete[] m_data;
0023 }
0024 
0025 void RatioSetAggregationModel::setSourceModel(QAbstractItemModel* model)
0026 {
0027     Q_ASSERT(model);
0028     m_sourceModel = model;
0029     connect(model, &QAbstractItemModel::modelReset, this, &RatioSetAggregationModel::recompute);
0030     recompute();
0031 }
0032 
0033 void RatioSetAggregationModel::setAggregationValue(const QString& aggrValue)
0034 {
0035     m_aggrValue = aggrValue;
0036     recompute();
0037 }
0038 
0039 int RatioSetAggregationModel::columnCount(const QModelIndex& parent) const
0040 {
0041     Q_UNUSED(parent);
0042     return m_categories.size() + 1;
0043 }
0044 
0045 int RatioSetAggregationModel::rowCount(const QModelIndex& parent) const
0046 {
0047     if (parent.isValid() || !m_sourceModel)
0048         return 0;
0049     return m_sourceModel->rowCount();
0050 }
0051 
0052 QVariant RatioSetAggregationModel::data(const QModelIndex& index, int role) const
0053 {
0054     if (!index.isValid() || !m_sourceModel)
0055         return {};
0056 
0057     if (role == TimeAggregationModel::MaximumValueRole)
0058         return 1.0;
0059 
0060     if (index.column() == 0) {
0061         const auto srcIdx = m_sourceModel->index(index.row(), 0);
0062         return m_sourceModel->data(srcIdx, role);
0063     }
0064 
0065     const auto idx = index.row() * m_categories.size() + index.column() - 1;
0066     switch (role) {
0067         case TimeAggregationModel::AccumulatedDisplayRole:
0068             return m_data[idx];
0069         case Qt::DisplayRole:
0070         case TimeAggregationModel::DataDisplayRole:
0071             if (index.column() == 1)
0072                 return m_data[idx];
0073             return m_data[idx] - m_data[idx - 1];
0074     }
0075 
0076     return {};
0077 }
0078 
0079 QVariant RatioSetAggregationModel::headerData(int section, Qt::Orientation orientation, int role) const
0080 {
0081     if (orientation == Qt::Horizontal && m_sourceModel) {
0082         if (section == 0)
0083             return m_sourceModel->headerData(section, orientation, role);
0084         if (role == Qt::DisplayRole) {
0085             const auto cat = m_categories.at(section - 1);
0086             if (cat.isEmpty())
0087                 return tr("[empty]");
0088             return cat;
0089         }
0090     }
0091 
0092     return QAbstractTableModel::headerData(section, orientation, role);
0093 }
0094 
0095 void RatioSetAggregationModel::recompute()
0096 {
0097     if (!m_sourceModel)
0098         return;
0099 
0100     const auto rowCount = m_sourceModel->rowCount();
0101     beginResetModel();
0102     m_categories.clear();
0103     delete[] m_data;
0104     m_data = nullptr;
0105 
0106     if (rowCount <= 0 || m_aggrValue.isEmpty()) {
0107         endResetModel();
0108         return;
0109     }
0110 
0111     QSet<QString> categories;
0112     const auto allSamples = m_sourceModel->index(0, 0).data(TimeAggregationModel::AllSamplesRole).value<QVector<Sample>>();
0113     foreach (const auto &s, allSamples) {
0114         const auto rs = s.value(m_aggrValue).value<QVariantMap>();
0115         for (auto it = rs.begin(); it != rs.end(); ++it)
0116             categories.insert(it.key());
0117     }
0118     m_categories.reserve(categories.size());
0119     foreach (const auto &cat, categories)
0120         m_categories.push_back(cat);
0121     std::sort(m_categories.begin(), m_categories.end());
0122     const auto colCount = m_categories.size();
0123 
0124     // compute the counts per cell, we could do that on demand, but we need the maximum for QtCharts...
0125     m_data = new double[colCount * rowCount];
0126     auto rowData = new double[colCount];
0127     memset(m_data, 0, sizeof(double) * colCount * rowCount);
0128     for (int row = 0; row < rowCount; ++row) {
0129         const auto samples = m_sourceModel->index(row, 0).data(TimeAggregationModel::SamplesRole).value<QVector<Sample>>();
0130         int validSampleCount = 0;
0131         foreach (const auto &sample, samples) {
0132             // extract raw data for one sample
0133             memset(rowData, 0, sizeof(double) * colCount);
0134             const auto rs = sample.value(m_aggrValue).value<QVariantMap>();
0135             for (auto it = rs.begin(); it != rs.end(); ++it) {
0136                 const auto catIt = std::lower_bound(m_categories.constBegin(), m_categories.constEnd(), it.key());
0137                 Q_ASSERT(catIt != m_categories.constEnd());
0138                 const auto idx = std::distance(m_categories.constBegin(), catIt);
0139                 rowData[idx] += it.value().toMap().value(QLatin1String("property")).toDouble(); // TODO: make this configurable
0140             }
0141 
0142             // filter invalid samples, normalize to 1
0143             // TODO: deal with negative values
0144             const auto sampleSum = std::accumulate(rowData, rowData + colCount, 0.0);
0145             if (sampleSum <= 0.0)
0146                 continue;
0147             for (int i = 0; i < colCount; ++i)
0148                 rowData[i] /= sampleSum;
0149 
0150             // aggregate
0151             ++validSampleCount;
0152             for (int i = 0; i < colCount; ++i)
0153                 m_data[row * colCount + i] += rowData[i];
0154         }
0155 
0156         // normalize to 1 and accumulate per row for stacked plots
0157         if (!validSampleCount)
0158             continue;
0159         const double scale = validSampleCount;
0160         for (int col = 0; col < colCount; ++col) {
0161             const auto idx = colCount * row + col;
0162             m_data[idx] /= scale;
0163             if (col > 0)
0164                 m_data[idx] += m_data[idx - 1];
0165         }
0166 
0167         Q_ASSERT(m_data[row * colCount + colCount - 1] <= 1.0001);
0168     }
0169 
0170     delete[] rowData;
0171     endResetModel();
0172 }
0173 
0174 #include "moc_ratiosetaggregationmodel.cpp"