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"