From 3bb266d3d7542c6ab70c14bd754025fd7f76b50b Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Sat, 7 Mar 2020 16:44:13 +0100 Subject: [PATCH] Implemented statistics chart --- SprintPlanningTool.pro | 8 +++- chartperstory.cpp | 36 +++++++++++++++++ chartperstory.h | 18 +++++++++ donutbreakdownchart.cpp | 90 +++++++++++++++++++++++++++++++++++++++++ donutbreakdownchart.h | 22 ++++++++++ mainslice.cpp | 30 ++++++++++++++ mainslice.h | 27 +++++++++++++ mainwindow.cpp | 9 +++++ mainwindow.ui | 23 ++++++++++- stripsgrid.cpp | 14 +++++-- stripsgrid.h | 8 ++++ 11 files changed, 280 insertions(+), 5 deletions(-) create mode 100644 chartperstory.cpp create mode 100644 chartperstory.h create mode 100644 donutbreakdownchart.cpp create mode 100644 donutbreakdownchart.h create mode 100644 mainslice.cpp create mode 100644 mainslice.h diff --git a/SprintPlanningTool.pro b/SprintPlanningTool.pro index c34316e..cdeadd1 100644 --- a/SprintPlanningTool.pro +++ b/SprintPlanningTool.pro @@ -1,11 +1,14 @@ -QT = core gui widgets +QT = core gui widgets charts CONFIG += c++14 DEFINES += QT_DEPRECATED_WARNINGS QT_DISABLE_DEPRECATED_BEFORE=0x060000 SOURCES += main.cpp \ + chartperstory.cpp \ + donutbreakdownchart.cpp \ flowlayout.cpp \ + mainslice.cpp \ mainwindow.cpp \ stripsgrid.cpp \ stripwidget.cpp @@ -15,7 +18,10 @@ FORMS += \ stripwidget.ui HEADERS += \ + chartperstory.h \ + donutbreakdownchart.h \ flowlayout.h \ + mainslice.h \ mainwindow.h \ stripsgrid.h \ stripwidget.h diff --git a/chartperstory.cpp b/chartperstory.cpp new file mode 100644 index 0000000..cb7bb1e --- /dev/null +++ b/chartperstory.cpp @@ -0,0 +1,36 @@ +#include "chartperstory.h" + +#include +#include + +#include "donutbreakdownchart.h" + +QT_CHARTS_USE_NAMESPACE + +ChartPerStory::ChartPerStory(QWidget *parent) : + QChartView{parent} +{ + setRenderHint(QPainter::Antialiasing); +} + +void ChartPerStory::setPoints(const QMap > &points) +{ + auto chart = new DonutBreakdownChart; + chart->layout()->setContentsMargins(0, 0, 0, 0); + chart->setBackgroundRoundness(0); + chart->setTitle("Story points with their subtasks"); + chart->legend()->setAlignment(Qt::AlignBottom); + for (auto iter = std::begin(points); iter != std::end(points); iter++) + { + auto series = new QtCharts::QPieSeries(); + series->setName(iter.key()); + + for (auto iter_ = std::begin(iter.value()); iter_ != std::end(iter.value()); iter_++) + series->append(iter_.key(), iter_.value()); + + chart->addBreakdownSeries(series, Qt::red); + } + setChart(chart); + delete m_chart; + m_chart = chart; +} diff --git a/chartperstory.h b/chartperstory.h new file mode 100644 index 0000000..b1dff28 --- /dev/null +++ b/chartperstory.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +class DonutBreakdownChart; + +class ChartPerStory : public QtCharts::QChartView +{ + Q_OBJECT + +public: + explicit ChartPerStory(QWidget *parent = nullptr); + + void setPoints(const QMap> &points); + +private: + DonutBreakdownChart *m_chart{}; +}; diff --git a/donutbreakdownchart.cpp b/donutbreakdownchart.cpp new file mode 100644 index 0000000..ed0d43d --- /dev/null +++ b/donutbreakdownchart.cpp @@ -0,0 +1,90 @@ +#include "donutbreakdownchart.h" + +#include +#include +#include + +#include "mainslice.h" + +QT_CHARTS_USE_NAMESPACE + +DonutBreakdownChart::DonutBreakdownChart(QGraphicsItem *parent, Qt::WindowFlags wFlags) + : QChart(QChart::ChartTypeCartesian, parent, wFlags) +{ + // create the series for main center pie + m_mainSeries = new QPieSeries(); + m_mainSeries->setPieSize(0.7); + QChart::addSeries(m_mainSeries); +} + +void DonutBreakdownChart::addBreakdownSeries(QPieSeries *breakdownSeries, QColor color) +{ + QFont font("Arial", 8); + + // add breakdown series as a slice to center pie + MainSlice *mainSlice = new MainSlice(breakdownSeries); + mainSlice->setName(breakdownSeries->name()); + mainSlice->setValue(breakdownSeries->sum()); + m_mainSeries->append(mainSlice); + + // customize the slice + mainSlice->setBrush(color); + mainSlice->setLabelVisible(); + mainSlice->setLabelColor(Qt::white); + mainSlice->setLabelPosition(QPieSlice::LabelInsideHorizontal); + mainSlice->setLabelFont(font); + + // position and customize the breakdown series + breakdownSeries->setPieSize(0.8); + breakdownSeries->setHoleSize(0.7); + breakdownSeries->setLabelsVisible(); + const auto slices = breakdownSeries->slices(); + for (QPieSlice *slice : slices) { + color = color.lighter(115); + slice->setBrush(color); + slice->setLabelFont(font); + } + + // add the series to the chart + QChart::addSeries(breakdownSeries); + + // recalculate breakdown donut segments + recalculateAngles(); + + // update customize legend markers + updateLegendMarkers(); +} + +void DonutBreakdownChart::recalculateAngles() +{ + qreal angle = 0; + const auto slices = m_mainSeries->slices(); + for (QPieSlice *slice : slices) { + QPieSeries *breakdownSeries = qobject_cast(slice)->breakdownSeries(); + breakdownSeries->setPieStartAngle(angle); + angle += slice->percentage() * 360.0; // full pie is 360.0 + breakdownSeries->setPieEndAngle(angle); + } +} + +void DonutBreakdownChart::updateLegendMarkers() +{ + // go through all markers + const auto allseries = series(); + for (QAbstractSeries *series : allseries) { + const auto markers = legend()->markers(series); + for (QLegendMarker *marker : markers) { + QPieLegendMarker *pieMarker = qobject_cast(marker); + if (series == m_mainSeries) { + // hide markers from main series + pieMarker->setVisible(false); + } else { + // modify markers from breakdown series + pieMarker->setLabel(QString("%1 %2%") + .arg(pieMarker->slice()->label()) + .arg(pieMarker->slice()->percentage() * 100, 0, 'f', 2)); + pieMarker->setFont(QFont("Arial", 8)); + } + } + } +} diff --git a/donutbreakdownchart.h b/donutbreakdownchart.h new file mode 100644 index 0000000..73687b8 --- /dev/null +++ b/donutbreakdownchart.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +QT_CHARTS_BEGIN_NAMESPACE +class QPieSeries; +QT_CHARTS_END_NAMESPACE + +class DonutBreakdownChart : public QtCharts::QChart +{ +public: + explicit DonutBreakdownChart(QGraphicsItem *parent = nullptr, Qt::WindowFlags wFlags = Qt::WindowFlags()); + + void addBreakdownSeries(QtCharts::QPieSeries *series, QColor color); + +private: + void recalculateAngles(); + void updateLegendMarkers(); + +private: + QtCharts::QPieSeries *m_mainSeries; +}; diff --git a/mainslice.cpp b/mainslice.cpp new file mode 100644 index 0000000..c3a5556 --- /dev/null +++ b/mainslice.cpp @@ -0,0 +1,30 @@ +#include "mainslice.h" + +QT_CHARTS_USE_NAMESPACE + +MainSlice::MainSlice(QPieSeries *breakdownSeries, QObject *parent) + : QPieSlice(parent), + m_breakdownSeries(breakdownSeries) +{ + connect(this, &MainSlice::percentageChanged, this, &MainSlice::updateLabel); +} + +QPieSeries *MainSlice::breakdownSeries() const +{ + return m_breakdownSeries; +} + +void MainSlice::setName(QString name) +{ + m_name = name; +} + +QString MainSlice::name() const +{ + return m_name; +} + +void MainSlice::updateLabel() +{ + this->setLabel(QString("%1 %2%").arg(m_name).arg(percentage() * 100, 0, 'f', 2)); +} diff --git a/mainslice.h b/mainslice.h new file mode 100644 index 0000000..99a88f5 --- /dev/null +++ b/mainslice.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +QT_CHARTS_BEGIN_NAMESPACE +class QPieSeries; +QT_CHARTS_END_NAMESPACE + +class MainSlice : public QtCharts::QPieSlice +{ + Q_OBJECT + +public: + explicit MainSlice(QtCharts::QPieSeries *breakdownSeries, QObject *parent = 0); + + QtCharts::QPieSeries *breakdownSeries() const; + + void setName(QString name); + QString name() const; + +public Q_SLOTS: + void updateLabel(); + +private: + QtCharts::QPieSeries *m_breakdownSeries; + QString m_name; +}; diff --git a/mainwindow.cpp b/mainwindow.cpp index f32e756..3b002ea 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -6,6 +6,15 @@ MainWindow::MainWindow(QWidget *parent) : m_ui{std::make_unique()} { m_ui->setupUi(this); + + connect(m_ui->stripsGrid, &StripsGrid::pointsPerStoryChanged, m_ui->chartPerStory, &ChartPerStory::setPoints); + m_ui->chartPerStory->setPoints(m_ui->stripsGrid->pointsPerStory()); + + for (QDockWidget *dockWidget : findChildren()) + { + m_ui->menu_View->addAction(dockWidget->toggleViewAction()); + dockWidget->close(); + } } MainWindow::~MainWindow() = default; diff --git a/mainwindow.ui b/mainwindow.ui index 3d17997..5064c26 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -22,7 +22,7 @@ 0 0 - 798 + 739 558 @@ -43,9 +43,24 @@ + + + &View + + + + + + Points per story + + + 1 + + + &Quit @@ -58,6 +73,12 @@ QWidget
stripsgrid.h
+ + ChartPerStory + QWidget +
chartperstory.h
+ 1 +
diff --git a/stripsgrid.cpp b/stripsgrid.cpp index 524aacc..285e9b6 100644 --- a/stripsgrid.cpp +++ b/stripsgrid.cpp @@ -28,7 +28,7 @@ StripsGrid::StripsGrid(QWidget *parent) : QRandomGenerator random((qint32)(QDateTime::currentMSecsSinceEpoch())); - const auto stories = random.bounded(3, 10); + const auto stories = random.bounded(3, 6); int row{0}; { @@ -86,7 +86,7 @@ StripsGrid::StripsGrid(QWidget *parent) : auto &test = story.columns[column-1]; test.layout = new FlowLayout; - call_n_times(random.bounded(6), [&]() + call_n_times(random.bounded(4), [&]() { auto widget = new StripWidget{StripWidget::Subtask}; widget->setTitle(QString("ATC-%0").arg(random.bounded(1000, 5000))); @@ -210,20 +210,26 @@ void StripsGrid::mouseMoveEvent(QMouseEvent *event) void StripsGrid::updatePoints() { std::array sumsPerColumn{0,0,0,0,0}; + m_pointsPerStory.clear(); for (const auto &story : m_stories) { int storyPoints = 0; - sumsPerColumn[0] += story.widget->points(); + + auto &test = m_pointsPerStory[story.widget->title()]; for (int i = 0; i < 4; i++) { int columnSum = 0; for (const auto *widget : story.columns[i].widgets) + { columnSum += widget->points(); + test[widget->title()] = widget->points(); + } sumsPerColumn[i+1] += columnSum; storyPoints += columnSum; } + sumsPerColumn[0] += storyPoints; story.widget->setPoints(storyPoints); } @@ -237,4 +243,6 @@ void StripsGrid::updatePoints() for (int i = 0; i < 5; i++) m_tableHeader[i]->setText(texts[i].arg(sumsPerColumn[i])); + + emit pointsPerStoryChanged(m_pointsPerStory); } diff --git a/stripsgrid.h b/stripsgrid.h index 455f27d..b56d95f 100644 --- a/stripsgrid.h +++ b/stripsgrid.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -17,6 +18,11 @@ class StripsGrid : public QWidget public: explicit StripsGrid(QWidget *parent = nullptr); + const auto &pointsPerStory() const { return m_pointsPerStory; } + +signals: + void pointsPerStoryChanged(const QMap > &pointsPerStory); + protected: void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; @@ -46,4 +52,6 @@ private: }; std::vector m_stories; + + QMap > m_pointsPerStory; };