summaryrefslogtreecommitdiff
path: root/src/statistics
diff options
context:
space:
mode:
Diffstat (limited to 'src/statistics')
-rw-r--r--src/statistics/BaseStatPage.cpp38
-rw-r--r--src/statistics/BaseStatPage.h31
-rw-r--r--src/statistics/ProgressPage.cpp41
-rw-r--r--src/statistics/ProgressPage.h33
-rw-r--r--src/statistics/ScheduledPage.cpp35
-rw-r--r--src/statistics/ScheduledPage.h22
-rw-r--r--src/statistics/StatisticsParams.cpp2
-rw-r--r--src/statistics/StatisticsParams.h20
-rw-r--r--src/statistics/StatisticsView.cpp182
-rw-r--r--src/statistics/StatisticsView.h53
-rw-r--r--src/statistics/StudiedPage.cpp17
-rw-r--r--src/statistics/StudiedPage.h18
-rw-r--r--src/statistics/TimeChartPage.cpp54
-rw-r--r--src/statistics/TimeChartPage.h33
14 files changed, 579 insertions, 0 deletions
diff --git a/src/statistics/BaseStatPage.cpp b/src/statistics/BaseStatPage.cpp
new file mode 100644
index 0000000..a7a8764
--- /dev/null
+++ b/src/statistics/BaseStatPage.cpp
@@ -0,0 +1,38 @@
+#include "BaseStatPage.h"
+#include "StatisticsParams.h"
+
+BaseStatPage::BaseStatPage(const StatisticsParams* statParams):
+ statParams(statParams)
+{
+}
+
+void BaseStatPage::init()
+{
+ createUi();
+ updateDataSet();
+}
+
+void BaseStatPage::createUi()
+{
+ QVBoxLayout* mainLt = new QVBoxLayout;
+ mainLt->addWidget(createTitleLabel());
+ mainLt->addWidget(createChart());
+ mainLt->addWidget(createTotalReviewsLabel());
+ setLayout(mainLt);
+}
+
+QLabel* BaseStatPage::createTitleLabel()
+{
+ QLabel* titleLabel = new QLabel(getTitle());
+ titleLabel->setFont(QFont("Sans Serif", 18, QFont::Bold));
+ titleLabel->setAlignment(Qt::AlignCenter);
+ return titleLabel;
+}
+
+QWidget* BaseStatPage::createTotalReviewsLabel()
+{
+ totalReviewsLabel = new QLabel;
+ totalReviewsLabel->setFont(QFont("Sans Serif", 16));
+ totalReviewsLabel->setAlignment(Qt::AlignRight);
+ return totalReviewsLabel;
+}
diff --git a/src/statistics/BaseStatPage.h b/src/statistics/BaseStatPage.h
new file mode 100644
index 0000000..7ef91bd
--- /dev/null
+++ b/src/statistics/BaseStatPage.h
@@ -0,0 +1,31 @@
+#ifndef STATISTICS_PAGE_H
+#define STATISTICS_PAGE_H
+
+#include <QtWidgets>
+
+class StatisticsParams;
+
+class BaseStatPage: public QWidget
+{
+ Q_OBJECT
+public:
+ BaseStatPage(const StatisticsParams* statParams);
+ virtual void updateDataSet() = 0;
+ virtual QString getTitle() const = 0;
+ virtual bool usesTimePeriod() const = 0;
+
+protected:
+ void init();
+ virtual QWidget* createChart() = 0;
+
+private:
+ void createUi();
+ QLabel* createTitleLabel();
+ QWidget* createTotalReviewsLabel();
+
+protected:
+ const StatisticsParams* statParams;
+ QLabel* totalReviewsLabel;
+};
+
+#endif
diff --git a/src/statistics/ProgressPage.cpp b/src/statistics/ProgressPage.cpp
new file mode 100644
index 0000000..7676173
--- /dev/null
+++ b/src/statistics/ProgressPage.cpp
@@ -0,0 +1,41 @@
+#include "ProgressPage.h"
+#include "StatisticsParams.h"
+#include "../charts/PieChart.h"
+#include "../dictionary/CardPack.h"
+
+ProgressPage::ProgressPage(const StatisticsParams* statParams):
+ BaseStatPage(statParams)
+{
+ init();
+}
+
+QWidget* ProgressPage::createChart()
+{
+ chart = new PieChart;
+ chart->setColors({"#39c900", "#ece900", "#ff0000"});
+ return chart;
+}
+
+void ProgressPage::updateDataSet()
+{
+ updateCardsNumbers();
+ totalReviewsLabel->setText(tr("Total: %1").arg(allCardsNum));
+ chart->setDataSet(getDataSet());
+}
+
+void ProgressPage::updateCardsNumbers()
+{
+ const CardPack* pack = statParams->getCardPack();
+ allCardsNum = pack->cardsNum();
+ newCardsNum = pack->getNewCards().size();
+ toBeRepeated = pack->getActiveCards().size();
+}
+
+QList<DataPoint> ProgressPage::getDataSet() const
+{
+ QList<DataPoint> dataSet;
+ dataSet << DataPoint(tr("Studied"), allCardsNum - newCardsNum - toBeRepeated);
+ dataSet << DataPoint(tr("Scheduled for today"), toBeRepeated);
+ dataSet << DataPoint(tr("New"), newCardsNum);
+ return dataSet;
+}
diff --git a/src/statistics/ProgressPage.h b/src/statistics/ProgressPage.h
new file mode 100644
index 0000000..9adb4cc
--- /dev/null
+++ b/src/statistics/ProgressPage.h
@@ -0,0 +1,33 @@
+#ifndef PROGRESS_PAGE_H
+#define PROGRESS_PAGE_H
+
+#include <QtWidgets>
+#include "BaseStatPage.h"
+#include "../charts/DataPoint.h"
+
+class PieChart;
+
+class ProgressPage: public BaseStatPage
+{
+ Q_OBJECT
+public:
+ ProgressPage(const StatisticsParams* statParams);
+ void updateDataSet();
+ QString getTitle() const { return tr("Study progress"); }
+ bool usesTimePeriod() const {return false;}
+
+protected:
+ QWidget* createChart();
+
+private:
+ void updateCardsNumbers();
+ QList<DataPoint> getDataSet() const;
+
+private:
+ PieChart* chart;
+ int allCardsNum;
+ int newCardsNum;
+ int toBeRepeated;
+};
+
+#endif
diff --git a/src/statistics/ScheduledPage.cpp b/src/statistics/ScheduledPage.cpp
new file mode 100644
index 0000000..5e2b558
--- /dev/null
+++ b/src/statistics/ScheduledPage.cpp
@@ -0,0 +1,35 @@
+#include "ScheduledPage.h"
+#include "../dictionary/CardPack.h"
+
+ScheduledPage::ScheduledPage(const StatisticsParams* statParams):
+ TimeChartPage(statParams)
+{
+ init();
+}
+
+QList<QDateTime> ScheduledPage::getDates(const CardPack* pack) const
+{
+ QList<QDateTime> scheduled = pack->getScheduledDates();
+ adjustScheduledRecords(scheduled);
+ return scheduled;
+}
+
+void ScheduledPage::adjustScheduledRecords(QList<QDateTime>& scheduled)
+{
+ const QDate curDate = QDate::currentDate();
+ const QTime zeroTime = QTime(0, 0);
+ for(int i = 0; i < scheduled.size(); i++)
+ {
+ QDateTime& dateTime = scheduled[i];
+ if(dateTime.date() < curDate)
+ dateTime = QDateTime(curDate, zeroTime);
+ else if(laterThisDay(dateTime))
+ dateTime = QDateTime(curDate.addDays(1), zeroTime);
+ }
+}
+
+bool ScheduledPage::laterThisDay(QDateTime& dateTime)
+{
+ return dateTime.date() == QDate::currentDate() &&
+ dateTime > QDateTime::currentDateTime();
+}
diff --git a/src/statistics/ScheduledPage.h b/src/statistics/ScheduledPage.h
new file mode 100644
index 0000000..f5a4eec
--- /dev/null
+++ b/src/statistics/ScheduledPage.h
@@ -0,0 +1,22 @@
+#ifndef SCHEDULED_PAGE_H
+#define SCHEDULED_PAGE_H
+
+#include "TimeChartPage.h"
+
+class ScheduledPage: public TimeChartPage
+{
+ Q_OBJECT
+public:
+ ScheduledPage(const StatisticsParams* statParams);
+ QString getTitle() const { return tr("Scheduled cards"); }
+
+protected:
+ QList<QDateTime> getDates(const CardPack* pack) const;
+ int getDataDirection() const { return 1; }
+
+private:
+ static void adjustScheduledRecords(QList<QDateTime>& scheduled);
+ static bool laterThisDay(QDateTime& dateTime);
+};
+
+#endif
diff --git a/src/statistics/StatisticsParams.cpp b/src/statistics/StatisticsParams.cpp
new file mode 100644
index 0000000..4e5d80f
--- /dev/null
+++ b/src/statistics/StatisticsParams.cpp
@@ -0,0 +1,2 @@
+#include "StatisticsParams.h"
+#include "../dictionary/CardPack.h"
diff --git a/src/statistics/StatisticsParams.h b/src/statistics/StatisticsParams.h
new file mode 100644
index 0000000..9a805c6
--- /dev/null
+++ b/src/statistics/StatisticsParams.h
@@ -0,0 +1,20 @@
+#ifndef STATISTICS_PARAMS_H
+#define STATISTICS_PARAMS_H
+
+class CardPack;
+
+class StatisticsParams
+{
+public:
+ static const int Week = 7;
+
+public:
+ const CardPack* getCardPack() const { return cardPack; }
+ int getTimePeriod() const { return timePeriod; }
+
+protected:
+ const CardPack* cardPack;
+ int timePeriod;
+};
+
+#endif
diff --git a/src/statistics/StatisticsView.cpp b/src/statistics/StatisticsView.cpp
new file mode 100644
index 0000000..de1b16d
--- /dev/null
+++ b/src/statistics/StatisticsView.cpp
@@ -0,0 +1,182 @@
+#include "StatisticsView.h"
+#include "StudiedPage.h"
+#include "ScheduledPage.h"
+#include "ProgressPage.h"
+#include "../dictionary/Dictionary.h"
+#include "../dictionary/CardPack.h"
+
+const QSize StatisticsView::GridSize(150, 110);
+
+StatisticsView::StatisticsView(const Dictionary* dict):
+ dict(dict), periodLabel(NULL), periodBox(NULL)
+{
+ init();
+ createContentsList();
+ createPages();
+ createListItems();
+ createUi();
+ loadSettings();
+}
+
+void StatisticsView::init()
+{
+ cardPack = dict->cardPacks().first();
+ timePeriod = Week;
+ setWindowTitle(tr("Statistics") + " - " + dict->shortName());
+ setWindowIcon(QIcon(":/images/statistics.png"));
+}
+
+void StatisticsView::closeEvent(QCloseEvent* /*event*/)
+{
+ saveSettings();
+}
+
+void StatisticsView::createPages()
+{
+ pagesWidget = new QStackedWidget;
+ pagesWidget->addWidget(new ProgressPage(this));
+ pagesWidget->addWidget(new StudiedPage(this));
+ pagesWidget->addWidget(new ScheduledPage(this));
+}
+
+void StatisticsView::createContentsList()
+{
+ contentsWidget = new QListWidget;
+ contentsWidget->setViewMode(QListView::IconMode);
+ contentsWidget->setGridSize(GridSize);
+ contentsWidget->setIconSize(QSize(IconSize, IconSize));
+ contentsWidget->setMovement(QListView::Static);
+ contentsWidget->setFixedWidth(GridSize.width() + 4);
+}
+
+void StatisticsView::createListItems()
+{
+ QStringList icons = {"pie-chart-3d", "chart-past", "chart-future"};
+ for(int i = 0; i < icons.size(); i++)
+ new QListWidgetItem( QIcon(QString(":/images/%1.png").arg(icons[i])),
+ static_cast<BaseStatPage*>(pagesWidget->widget(i))->getTitle(),
+ contentsWidget);
+ connect(contentsWidget,
+ SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)),
+ SLOT(changePage(QListWidgetItem*, QListWidgetItem*)));
+ contentsWidget->setCurrentRow(0);
+}
+
+void StatisticsView::changePage(QListWidgetItem* curPage, QListWidgetItem* prevPage)
+{
+ if(!curPage)
+ curPage = prevPage;
+ pagesWidget->setCurrentIndex(contentsWidget->row(curPage));
+ updateChart();
+ updatePeriodBox();
+}
+
+void StatisticsView::createUi()
+{
+ QVBoxLayout* verLt = new QVBoxLayout;
+ verLt->addLayout(createControlLayout());
+ verLt->addWidget(pagesWidget);
+
+ QHBoxLayout* horLt = new QHBoxLayout;
+ horLt->addWidget(contentsWidget);
+ horLt->addLayout(verLt);
+ setLayout(horLt);
+}
+
+void StatisticsView::loadSettings()
+{
+ QSettings settings;
+ QVariant pointVar = settings.value("stats-pos");
+ if(pointVar.isNull())
+ return;
+ move(pointVar.toPoint());
+ resize(settings.value("stats-size").toSize());
+}
+
+void StatisticsView::saveSettings()
+{
+ QSettings settings;
+ settings.setValue("stats-pos", pos());
+ settings.setValue("stats-size", size());
+}
+
+QBoxLayout* StatisticsView::createControlLayout()
+{
+ QHBoxLayout* lt = new QHBoxLayout;
+ lt->addWidget(new QLabel(tr("Card pack:")));
+ lt->addWidget(createPacksBox());
+ lt->addStretch();
+ periodLabel = new QLabel(tr("Period:"));
+ lt->addWidget(periodLabel);
+ lt->addWidget(createPeriodBox());
+ return lt;
+}
+
+QComboBox* StatisticsView::createPacksBox()
+{
+ QComboBox* packsBox = new QComboBox;
+ foreach(CardPack* pack, dict->cardPacks())
+ packsBox->addItem(pack->id());
+ connect(packsBox, SIGNAL(activated(int)), SLOT(setPack(int)));
+ return packsBox;
+}
+
+QComboBox* StatisticsView::createPeriodBox()
+{
+ periodBox = new QComboBox;
+ QPair<QString, int> period;
+ foreach(period, getPeriodsList())
+ periodBox->addItem(period.first, period.second);
+ periodBox->setMaxVisibleItems(12);
+ connect(periodBox, SIGNAL(activated(int)), SLOT(setPeriod(int)));
+ updatePeriodBox();
+ return periodBox;
+}
+
+QList<QPair<QString, int>> StatisticsView::getPeriodsList()
+{
+ static const int Week = 7;
+ static const int Month = 30;
+ static const int Year = 365;
+ return {
+ qMakePair(tr("%n week(s)", 0, 1), Week),
+ qMakePair(tr("%n week(s)", 0, 2), 2 * Week),
+ qMakePair(tr("%n week(s)", 0, 4), 4 * Week),
+ qMakePair(tr("%n month(s)", 0, 1), Month + 1),
+ qMakePair(tr("%n month(s)", 0, 2), 2 * Month + 1),
+ qMakePair(tr("%n month(s)", 0, 3), 3 * Month + 2),
+ qMakePair(tr("%n month(s)", 0, 6), 6 * Month + 3),
+ qMakePair(tr("%n year(s)", 0, 1), Year),
+ qMakePair(tr("%n year(s)", 0, 2), 2 * Year),
+ qMakePair(tr("%n year(s)", 0, 3), 3 * Year),
+ qMakePair(tr("All time"), -1)};
+}
+
+void StatisticsView::setPack(int packIndex)
+{
+ cardPack = dict->cardPacks().at(packIndex);
+ updateChart();
+}
+
+void StatisticsView::setPeriod(int index)
+{
+ timePeriod = periodBox->itemData(index).toInt();
+ updateChart();
+}
+
+void StatisticsView::updateChart()
+{
+ static_cast<BaseStatPage*>(pagesWidget->currentWidget())->updateDataSet();
+}
+
+void StatisticsView::updatePeriodBox()
+{
+ bool visiblePeriod = static_cast<BaseStatPage*>(
+ pagesWidget->currentWidget())->usesTimePeriod();
+ if(periodBox)
+ {
+ periodLabel->setVisible(visiblePeriod);
+ periodBox->setVisible(visiblePeriod);
+ }
+}
+
diff --git a/src/statistics/StatisticsView.h b/src/statistics/StatisticsView.h
new file mode 100644
index 0000000..560f8b8
--- /dev/null
+++ b/src/statistics/StatisticsView.h
@@ -0,0 +1,53 @@
+#ifndef STATISTICS_VIEW_H
+#define STATISTICS_VIEW_H
+
+#include <QtCore>
+#include <QtWidgets>
+
+#include "StatisticsParams.h"
+
+class Dictionary;
+class CardPack;
+
+class StatisticsView: public QDialog, public StatisticsParams
+{
+ Q_OBJECT
+public:
+ StatisticsView(const Dictionary* dict);
+
+private:
+ static QList<QPair<QString, int>> getPeriodsList();
+
+private:
+ void init();
+ void closeEvent(QCloseEvent *event);
+ void createPages();
+ void createContentsList();
+ void createListItems();
+ void createUi();
+ void loadSettings();
+ void saveSettings();
+ QBoxLayout* createControlLayout();
+ QComboBox* createPacksBox();
+ QComboBox* createPeriodBox();
+ void updateChart();
+ void updatePeriodBox();
+
+private slots:
+ void changePage(QListWidgetItem* curPage, QListWidgetItem* prevPage);
+ void setPack(int packIndex);
+ void setPeriod(int index);
+
+private:
+ static const QSize GridSize;
+ static const int IconSize = 75;
+
+private:
+ const Dictionary* dict;
+ QListWidget* contentsWidget;
+ QLabel* periodLabel;
+ QComboBox* periodBox;
+ QStackedWidget* pagesWidget;
+};
+
+#endif
diff --git a/src/statistics/StudiedPage.cpp b/src/statistics/StudiedPage.cpp
new file mode 100644
index 0000000..5dbe83b
--- /dev/null
+++ b/src/statistics/StudiedPage.cpp
@@ -0,0 +1,17 @@
+#include "StudiedPage.h"
+#include "../dictionary/CardPack.h"
+#include "../study/StudyRecord.h"
+
+StudiedPage::StudiedPage(const StatisticsParams* statParams):
+ TimeChartPage(statParams)
+{
+ init();
+}
+
+QList<QDateTime> StudiedPage::getDates(const CardPack* pack) const
+{
+ QList<QDateTime> res;
+ foreach(StudyRecord record, pack->getStudyRecords())
+ res << record.date;
+ return res;
+}
diff --git a/src/statistics/StudiedPage.h b/src/statistics/StudiedPage.h
new file mode 100644
index 0000000..5ccb59b
--- /dev/null
+++ b/src/statistics/StudiedPage.h
@@ -0,0 +1,18 @@
+#ifndef STUDIED_PAGE_H
+#define STUDIED_PAGE_H
+
+#include "TimeChartPage.h"
+
+class StudiedPage: public TimeChartPage
+{
+ Q_OBJECT
+public:
+ StudiedPage(const StatisticsParams* statParams);
+ QString getTitle() const { return tr("Studied cards"); }
+
+protected:
+ QList<QDateTime> getDates(const CardPack* pack) const;
+ int getDataDirection() const { return -1; }
+};
+
+#endif
diff --git a/src/statistics/TimeChartPage.cpp b/src/statistics/TimeChartPage.cpp
new file mode 100644
index 0000000..62ca63b
--- /dev/null
+++ b/src/statistics/TimeChartPage.cpp
@@ -0,0 +1,54 @@
+#include "TimeChartPage.h"
+#include "StatisticsParams.h"
+#include "../charts/TimeChart.h"
+#include "../dictionary/CardPack.h"
+
+TimeChartPage::TimeChartPage(const StatisticsParams* statParams):
+ BaseStatPage(statParams)
+{
+}
+
+QWidget* TimeChartPage::createChart()
+{
+ chart = new TimeChart;
+ chart->setLabels(tr("Date"), tr("Cards"));
+ return chart;
+}
+
+void TimeChartPage::updateDataSet()
+{
+ QList<QDateTime> dates = getDates(statParams->getCardPack());
+ int period = statParams->getTimePeriod();
+ if(period == -1)
+ period = getStudyPeriodLength(dates);
+ int reviewsNum = getReviewsNum(dates, period);
+ totalReviewsLabel->setText(tr("Total: %1").arg(reviewsNum));
+ chart->setDates(dates, period, getDataDirection());
+}
+
+int TimeChartPage::getStudyPeriodLength(const QList<QDateTime>& dates) const
+{
+ const int minPeriod = 7;
+ QDateTime minDate = QDateTime::currentDateTime();
+ QDateTime maxDate = minDate;
+ foreach(QDateTime date, dates)
+ if(date < minDate)
+ minDate = date;
+ else if(date > maxDate)
+ maxDate = date;
+ int res = minDate.daysTo(maxDate) + 1;
+ if(res < minPeriod)
+ res = minPeriod;
+ return res;
+}
+
+int TimeChartPage::getReviewsNum(const QList<QDateTime>& dates,
+ int period) const
+{
+ QDateTime curDate = QDateTime::currentDateTime();
+ int res = 0;
+ foreach(QDateTime date, dates)
+ if(qAbs(date.daysTo(curDate)) < period)
+ res++;
+ return res;
+}
diff --git a/src/statistics/TimeChartPage.h b/src/statistics/TimeChartPage.h
new file mode 100644
index 0000000..5e48d4c
--- /dev/null
+++ b/src/statistics/TimeChartPage.h
@@ -0,0 +1,33 @@
+#ifndef TIME_CHART_PAGE_H
+#define TIME_CHART_PAGE_H
+
+#include <QtWidgets>
+#include "BaseStatPage.h"
+#include "../charts/DataPoint.h"
+
+class TimeChart;
+class StatisticsParams;
+class CardPack;
+
+class TimeChartPage: public BaseStatPage
+{
+ Q_OBJECT
+public:
+ TimeChartPage(const StatisticsParams* statParams);
+ void updateDataSet();
+ bool usesTimePeriod() const {return true;}
+
+protected:
+ virtual QList<QDateTime> getDates(const CardPack* pack) const = 0;
+ virtual int getDataDirection() const = 0;
+ QWidget* createChart();
+
+private:
+ int getStudyPeriodLength(const QList<QDateTime>& dates) const;
+ int getReviewsNum(const QList<QDateTime>& dates, int period) const;
+
+private:
+ TimeChart* chart;
+};
+
+#endif