aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Berthold Stoeger <bstoeger@mail.tuwien.ac.at>2021-01-02 22:16:11 +0100
committerGravatar Dirk Hohndel <dirk@hohndel.org>2021-01-03 13:41:15 -0800
commit319a7af31afe3b3b1ba03114b01e88c7067709f0 (patch)
treecf0a37d4e923c95413e6f72955a9192758f427a7
parentfbb17871c9aab2eee71da98163b802836c691476 (diff)
downloadsubsurface-319a7af31afe3b3b1ba03114b01e88c7067709f0.tar.gz
statistics: add a model that describes a list of charts
Qt's comboboxes are controlled by models, there's no way around that. To customize the chart-selection widget this must therefore be abstracted into a model. On the upside, this hopefully can be used for desktop and mobile. The model provides icons and paints a warning-symbol on it if the statistics core code deems the chart to be not recommended. Notably, when plotting a categorical bar chart against a numerical value (in such a case histograms are preferred). Includes a fix for a silly oversight in CMakelist.txt: add the statstranslations.h header. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
-rw-r--r--stats/CMakeLists.txt3
-rw-r--r--stats/chartlistmodel.cpp119
-rw-r--r--stats/chartlistmodel.h54
3 files changed, 176 insertions, 0 deletions
diff --git a/stats/CMakeLists.txt b/stats/CMakeLists.txt
index 31e809270..e0e2dc303 100644
--- a/stats/CMakeLists.txt
+++ b/stats/CMakeLists.txt
@@ -9,6 +9,8 @@ set(SUBSURFACE_STATS_SRCS
barseries.cpp
boxseries.h
boxseries.cpp
+ chartlistmodel.h
+ chartlistmodel.cpp
informationbox.h
informationbox.cpp
legend.h
@@ -25,6 +27,7 @@ set(SUBSURFACE_STATS_SRCS
statsseries.cpp
statsstate.h
statsstate.cpp
+ statstranslations.h
statsvariables.h
statsvariables.cpp
statsview.h
diff --git a/stats/chartlistmodel.cpp b/stats/chartlistmodel.cpp
new file mode 100644
index 000000000..9ae3dae6e
--- /dev/null
+++ b/stats/chartlistmodel.cpp
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "chartlistmodel.h"
+#include "core/metrics.h"
+#include "core/qthelper.h"
+#include <QIcon>
+#include <QFontMetrics>
+#include <QPainter>
+
+ChartListModel::ChartListModel() :
+ itemFont(defaultModelFont()),
+ headerFont(itemFont.family(), itemFont.pointSize(), itemFont.weight(), true)
+{
+ QFontMetrics fm(itemFont);
+ int fontHeight = fm.height();
+
+ int iconSize = fontHeight * 3;
+ warningPixmap = QPixmap::fromImage(renderSVGIcon(":chart-warning-icon", fontHeight, true));
+ initIcon(ChartSubType::Vertical, ":chart-bar-vertical-icon", iconSize);
+ initIcon(ChartSubType::VerticalGrouped, ":chart-bar-grouped-vertical-icon", iconSize);
+ initIcon(ChartSubType::VerticalStacked, ":chart-bar-stacked-vertical-icon", iconSize);
+ initIcon(ChartSubType::Horizontal, ":chart-bar-horizontal-icon", iconSize);
+ initIcon(ChartSubType::HorizontalGrouped, ":chart-bar-grouped-horizontal-icon", iconSize);
+ initIcon(ChartSubType::HorizontalStacked, ":chart-bar-stacked-horizontal-icon", iconSize);
+ initIcon(ChartSubType::Dots, ":chart-points-icon", iconSize);
+ initIcon(ChartSubType::Box, ":chart-box-icon", iconSize);
+ initIcon(ChartSubType::Pie, ":chart-pie-icon", iconSize);
+}
+
+ChartListModel::~ChartListModel()
+{
+}
+
+void ChartListModel::initIcon(ChartSubType type, const char *name, int iconSize)
+{
+ QPixmap icon = QPixmap::fromImage(renderSVGIcon(name, iconSize, true));
+ QPixmap iconWarning = icon.copy();
+ QPainter painter(&iconWarning);
+ painter.drawPixmap(0, 0, warningPixmap);
+ subTypeIcons[(size_t)type].normal = icon;
+ subTypeIcons[(size_t)type].warning = iconWarning;
+}
+
+const QPixmap &ChartListModel::getIcon(ChartSubType type, bool warning) const
+{
+ int idx = std::clamp((int)type, 0, (int)ChartSubType::Count);
+ return warning ? subTypeIcons[idx].warning : subTypeIcons[idx].normal;
+}
+
+int ChartListModel::rowCount(const QModelIndex &parent) const
+{
+ return parent.isValid() ? 0 : (int)items.size();
+}
+
+Qt::ItemFlags ChartListModel::flags(const QModelIndex &index) const
+{
+ int row = index.row();
+ if (index.parent().isValid() || row < 0 || row >= (int)items.size())
+ return Qt::NoItemFlags;
+ return items[row].isHeader ? Qt::ItemIsEnabled
+ : Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+}
+
+QVariant ChartListModel::data(const QModelIndex &index, int role) const
+{
+ int row = index.row();
+ if (index.parent().isValid() || row < 0 || row >= (int)items.size())
+ return QVariant();
+
+ switch (role) {
+ case Qt::FontRole:
+ return items[row].isHeader ? headerFont : itemFont;
+ case Qt::DisplayRole:
+ return items[row].fullName;
+ case Qt::DecorationRole:
+ return items[row].warning ? QVariant::fromValue(QIcon(warningPixmap))
+ : QVariant();
+ case IconRole:
+ return items[row].isHeader ? QVariant()
+ : QVariant::fromValue(getIcon(items[row].subtype, items[row].warning));
+ case IconSizeRole:
+ return items[row].isHeader ? QVariant()
+ : QVariant::fromValue(getIcon(items[row].subtype, items[row].warning).size());
+ case ChartNameRole:
+ return items[row].name;
+ case IsHeaderRole:
+ return items[row].isHeader;
+ case Qt::UserRole:
+ return items[row].id;
+ }
+ return QVariant();
+}
+
+int ChartListModel::update(const StatsState::ChartList &charts)
+{
+ // Sort non-recommended entries to the back
+ std::vector<StatsState::Chart> sorted;
+ sorted.reserve(charts.charts.size());
+ std::copy_if(charts.charts.begin(), charts.charts.end(), std::back_inserter(sorted),
+ [] (const StatsState::Chart &chart) { return !chart.warning; });
+ std::copy_if(charts.charts.begin(), charts.charts.end(), std::back_inserter(sorted),
+ [] (const StatsState::Chart &chart) { return chart.warning; });
+
+ beginResetModel();
+ items.clear();
+ QString act;
+ int res = -1;
+ for (const StatsState::Chart &chart: sorted) {
+ if (act != chart.name) {
+ items.push_back({ true, chart.name, QString(), (ChartSubType)-1, -1, false });
+ act = chart.name;
+ }
+ if (charts.selected == chart.id)
+ res = (int)items.size();
+ QString fullName = QString("%1 / %2").arg(chart.name, chart.subtypeName);
+ items.push_back({ false, chart.subtypeName, fullName, chart.subtype, chart.id, chart.warning });
+ }
+ endResetModel();
+ return res;
+}
diff --git a/stats/chartlistmodel.h b/stats/chartlistmodel.h
new file mode 100644
index 000000000..a33f6875a
--- /dev/null
+++ b/stats/chartlistmodel.h
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0
+// A model to feed to the chart-selection combobox
+#ifndef CHART_LIST_MODEL_H
+#define CHART_LIST_MODEL_H
+
+#include "statsstate.h"
+#include <vector>
+#include <QAbstractListModel>
+#include <QString>
+#include <QFont>
+#include <QIcon>
+#include <QPixmap>
+
+class ChartListModel : public QAbstractListModel {
+ Q_OBJECT
+public:
+ ChartListModel();
+ ~ChartListModel();
+
+ // Returns index of selected item
+ int update(const StatsState::ChartList &charts);
+
+ static const constexpr int ChartNameRole = Qt::UserRole + 1;
+ static const constexpr int IsHeaderRole = Qt::UserRole + 2;
+ static const constexpr int IconRole = Qt::UserRole + 3;
+ static const constexpr int IconSizeRole = Qt::UserRole + 4;
+private:
+ struct Item {
+ bool isHeader;
+ QString name;
+ QString fullName;
+ ChartSubType subtype;
+ int id;
+ bool warning;
+ };
+
+ struct SubTypeIcons {
+ QPixmap normal;
+ QPixmap warning;
+ };
+ QPixmap warningPixmap;
+ SubTypeIcons subTypeIcons[(size_t)ChartSubType::Count];
+
+ QFont itemFont;
+ QFont headerFont;
+ std::vector<Item> items;
+ int rowCount(const QModelIndex &parent) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+ void initIcon(ChartSubType type, const char *name, int iconSize);
+ const QPixmap &getIcon(ChartSubType type, bool warning) const;
+};
+
+#endif