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 }