Implemented statistics chart

This commit is contained in:
2020-03-07 16:44:13 +01:00
parent 05abbcbe8e
commit 3bb266d3d7
11 changed files with 280 additions and 5 deletions

View File

@@ -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

36
chartperstory.cpp Normal file
View File

@@ -0,0 +1,36 @@
#include "chartperstory.h"
#include <QPieSeries>
#include <QGraphicsLayout>
#include "donutbreakdownchart.h"
QT_CHARTS_USE_NAMESPACE
ChartPerStory::ChartPerStory(QWidget *parent) :
QChartView{parent}
{
setRenderHint(QPainter::Antialiasing);
}
void ChartPerStory::setPoints(const QMap<QString, QMap<QString, int> > &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;
}

18
chartperstory.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <QtCharts/QChartView>
class DonutBreakdownChart;
class ChartPerStory : public QtCharts::QChartView
{
Q_OBJECT
public:
explicit ChartPerStory(QWidget *parent = nullptr);
void setPoints(const QMap<QString, QMap<QString, int>> &points);
private:
DonutBreakdownChart *m_chart{};
};

90
donutbreakdownchart.cpp Normal file
View File

@@ -0,0 +1,90 @@
#include "donutbreakdownchart.h"
#include <QtCharts/QPieSeries>
#include <QtCharts/QPieSlice>
#include <QtCharts/QPieLegendMarker>
#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<MainSlice *>(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<QPieLegendMarker *>(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));
}
}
}
}

22
donutbreakdownchart.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <QtCharts/QChart>
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;
};

30
mainslice.cpp Normal file
View File

@@ -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));
}

27
mainslice.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <QtCharts/QPieSlice>
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;
};

View File

@@ -6,6 +6,15 @@ MainWindow::MainWindow(QWidget *parent) :
m_ui{std::make_unique<Ui::MainWindow>()}
{
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<QDockWidget*>())
{
m_ui->menu_View->addAction(dockWidget->toggleViewAction());
dockWidget->close();
}
}
MainWindow::~MainWindow() = default;

View File

@@ -22,7 +22,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>798</width>
<width>739</width>
<height>558</height>
</rect>
</property>
@@ -43,9 +43,24 @@
</property>
<addaction name="action_Quit"/>
</widget>
<widget class="QMenu" name="menu_View">
<property name="title">
<string>&amp;View</string>
</property>
</widget>
<addaction name="menu_File"/>
<addaction name="menu_View"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QDockWidget" name="dockWidget">
<property name="windowTitle">
<string>Points per story</string>
</property>
<attribute name="dockWidgetArea">
<number>1</number>
</attribute>
<widget class="ChartPerStory" name="chartPerStory"/>
</widget>
<action name="action_Quit">
<property name="text">
<string>&amp;Quit</string>
@@ -58,6 +73,12 @@
<extends>QWidget</extends>
<header>stripsgrid.h</header>
</customwidget>
<customwidget>
<class>ChartPerStory</class>
<extends>QWidget</extends>
<header>chartperstory.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>

View File

@@ -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<int, 5> 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);
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include <QWidget>
#include <QMap>
#include <vector>
@@ -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<QString, QMap<QString, int> > &pointsPerStory);
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
@@ -46,4 +52,6 @@ private:
};
std::vector<Story> m_stories;
QMap<QString, QMap<QString, int> > m_pointsPerStory;
};