File indexing completed on 2024-05-12 05:09:21
0001 /*************************************************************************** 0002 Copyright (C) 2021 Robby Stephenson <robby@periapsis.org> 0003 ***************************************************************************/ 0004 0005 /*************************************************************************** 0006 * * 0007 * This program is free software; you can redistribute it and/or * 0008 * modify it under the terms of the GNU General Public License as * 0009 * published by the Free Software Foundation; either version 2 of * 0010 * the License or (at your option) version 3 or any later version * 0011 * accepted by the membership of KDE e.V. (or its successor approved * 0012 * by the membership of KDE e.V.), which shall act as a proxy * 0013 * defined in Section 14 of version 3 of the license. * 0014 * * 0015 * This program is distributed in the hope that it will be useful, * 0016 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0018 * GNU General Public License for more details. * 0019 * * 0020 * You should have received a copy of the GNU General Public License * 0021 * along with this program. If not, see <http://www.gnu.org/licenses/>. * 0022 * * 0023 ***************************************************************************/ 0024 0025 #include "groupsummaryreport.h" 0026 #include "barchart.h" 0027 #include "../document.h" 0028 #include "../entrygroup.h" 0029 #include "../models/models.h" 0030 #include "../models/entrygroupmodel.h" 0031 #include "../models/groupsortmodel.h" 0032 #include "../tellico_debug.h" 0033 0034 #include <KLocalizedString> 0035 0036 #include <QApplication> 0037 #include <QGridLayout> 0038 #include <QLabel> 0039 #include <QChartView> 0040 0041 namespace { 0042 static const int MAX_GROUP_VALUES = 5; 0043 static const int MIN_GROUP_VALUES = 3; 0044 static const int NUM_CHART_COLUMNS = 2; 0045 } 0046 0047 using namespace QtCharts; 0048 using Tellico::GroupSummaryWidget; 0049 using Tellico::GroupSummaryReport; 0050 0051 GroupSummaryWidget::GroupSummaryWidget(const QString& title_, int count_, QWidget* parent_) : QScrollArea(parent_) { 0052 auto widget = new QWidget; 0053 setWidget(widget); 0054 0055 m_layout = new QGridLayout(widget); 0056 m_layout->setMargin(0); 0057 m_layout->setSpacing(0); 0058 widget->setLayout(m_layout); 0059 0060 auto titleLabel = new QLabel(i18n("Group Summary: %1", title_), widget); 0061 titleLabel->setAlignment(Qt::AlignCenter); 0062 int fontSize = qRound(QApplication::font().pointSize() * 2.0); 0063 titleLabel->setStyleSheet(QStringLiteral("QLabel { font-size: %1pt }").arg(QString::number(fontSize))); 0064 m_layout->addWidget(titleLabel, 0, 0, 1, -1); 0065 0066 auto countLabel = new QLabel(i18n("Total number of entries: %1", count_), widget); 0067 countLabel->setAlignment(Qt::AlignCenter); 0068 fontSize = qRound(QApplication::font().pointSize() * 1.2); 0069 countLabel->setStyleSheet(QStringLiteral("QLabel { font-size: %1pt; font-style: italic }").arg(QString::number(fontSize))); 0070 m_layout->addWidget(countLabel, 1, 0, 1, -1); 0071 0072 m_layout->addItem(new QSpacerItem(1, 10), 2, 0, 1, -1); 0073 } 0074 0075 void GroupSummaryWidget::addChart(Tellico::Data::FieldPtr field_) { 0076 // first populate a model with the entry groups for this field 0077 Data::CollPtr coll = Data::Document::self()->collection(); 0078 Data::EntryGroupDict* dict = coll->entryGroupDictByName(field_->name()); 0079 // dict will be null for a field with no values 0080 if(!dict) { 0081 return; 0082 } 0083 0084 EntryGroupModel* groupModel = new EntryGroupModel(this); 0085 groupModel->addGroups(dict->values(), QString()); // TODO: make second value optional 0086 0087 GroupSortModel* model = new GroupSortModel(this); 0088 model->setSourceModel(groupModel); 0089 model->setSortRole(RowCountRole); 0090 model->sort(0, Qt::DescendingOrder); 0091 0092 // grab the top few values in the group 0093 QStringList groupNames; 0094 QList<qreal> groupCounts; 0095 int numGroups = qMin(MAX_GROUP_VALUES, model->rowCount()); 0096 for(int i = 0; i < numGroups; ++i) { 0097 Data::EntryGroup* group = model->data(model->index(i, 0), GroupPtrRole).value<Data::EntryGroup*>(); 0098 if(!group || group->isEmpty()) { 0099 continue; 0100 } 0101 if(group->hasEmptyGroupName()) { 0102 ++numGroups; // bump the limit to account for the "Empty" group showing up 0103 continue; 0104 } 0105 if(group->fieldName() == QStringLiteral("rating")) { 0106 const int rating = qBound(0, group->groupName().toInt(), 10); 0107 groupNames << QString::fromUtf8("⭐").repeated(rating); 0108 } else { 0109 groupNames << group->groupName(); 0110 } 0111 groupCounts << group->count(); 0112 } 0113 // if less than minimum, we don't care, skip this field 0114 if(groupCounts.count() < MIN_GROUP_VALUES) { 0115 return; 0116 } 0117 0118 auto chart = new BarChart(groupNames, groupCounts); 0119 m_charts << chart; 0120 chart->setTitle(i18n("Top Values: %1", field_->title())); 0121 chart->setTheme(qApp->palette().color(QPalette::Window).lightnessF() < 0.25 ? QtCharts::QChart::ChartThemeDark 0122 : QtCharts::QChart::ChartThemeLight); 0123 0124 auto chartView = new QChartView(chart, widget()); 0125 chartView->setMinimumHeight(groupNames.count() * 50); 0126 // avoid a transparent frame around the chart 0127 chartView->setBackgroundBrush(chart->backgroundBrush()); 0128 chartView->setRenderHint(QPainter::Antialiasing); 0129 0130 // start with the 3 rows for the top labels and spacer 0131 const int row = 3 + (m_charts.count()-1) / NUM_CHART_COLUMNS; 0132 const int col = (m_charts.count()-1) % NUM_CHART_COLUMNS; 0133 m_layout->addWidget(chartView, row, col); 0134 0135 // first time through, update the background color of the whole element 0136 if(m_charts.count() == 1) { 0137 QColor bg = chart->backgroundBrush().color(); 0138 if(!bg.isValid()) { 0139 auto grad = chart->backgroundBrush().gradient(); 0140 if(grad) { 0141 const auto stops = grad->stops(); 0142 bg = stops.first().second; // stops is a vector of QPair with second element the color 0143 } 0144 } 0145 if(bg.isValid()) { 0146 widget()->setStyleSheet(QStringLiteral("background-color:%1").arg(bg.name(QColor::HexArgb))); 0147 } 0148 } 0149 } 0150 0151 void GroupSummaryWidget::updateGeometry() { 0152 static bool updating = false; 0153 if(updating || m_charts.isEmpty()) { 0154 return; 0155 } 0156 updating = true; 0157 for(int col = 0; col < NUM_CHART_COLUMNS; ++col) { 0158 updateChartColumn(col); 0159 } 0160 updating = false; 0161 } 0162 0163 void GroupSummaryWidget::updateChartColumn(int column_) { 0164 BarChart* bestChart = nullptr; 0165 QRectF bestRect; 0166 for(int idx = column_; idx < m_charts.count(); idx += NUM_CHART_COLUMNS) { 0167 auto chart = m_charts.at(idx); 0168 if(!bestChart || chart->plotArea().left() > bestRect.left()) { 0169 bestChart = chart; 0170 bestRect = chart->plotArea(); 0171 chart->setMargins(QMargins(20, 0, 20, 0)); // reset margins to the basic default values 0172 } 0173 } 0174 if(!bestChart) { 0175 return; 0176 } 0177 0178 for(int idx = column_; idx < m_charts.count(); idx += NUM_CHART_COLUMNS) { 0179 auto chart = m_charts.at(idx); 0180 if(bestChart != chart) { 0181 const int left = chart->margins().left() + bestRect.left() - chart->plotArea().left(); 0182 const int right = chart->margins().right() - bestRect.right() + chart->plotArea().right(); 0183 chart->setMargins(QMargins(left, 0, right, 0)); 0184 } 0185 } 0186 } 0187 0188 GroupSummaryReport::GroupSummaryReport() : ChartReport() { 0189 } 0190 0191 QString GroupSummaryReport::title() const { 0192 return i18n("Group Summary Report"); 0193 } 0194 0195 QWidget* GroupSummaryReport::createWidget() { 0196 // create a new widget every time 0197 Data::CollPtr coll = Data::Document::self()->collection(); 0198 Q_ASSERT(coll); 0199 auto widget = new GroupSummaryWidget(coll->title(), coll->entryCount()); 0200 0201 foreach(Data::FieldPtr field, coll->fields()) { 0202 if(field && field->hasFlag(Data::Field::AllowGrouped)) { 0203 widget->addChart(field); 0204 } 0205 } 0206 widget->setVisible(true); 0207 widget->setWidgetResizable(true); 0208 QTimer::singleShot(0, widget, &GroupSummaryWidget::updateGeometry); 0209 return widget; 0210 }