Implemented statistics chart
This commit is contained in:
@@ -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
36
chartperstory.cpp
Normal 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
18
chartperstory.h
Normal 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
90
donutbreakdownchart.cpp
Normal 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
22
donutbreakdownchart.h
Normal 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
30
mainslice.cpp
Normal 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
27
mainslice.h
Normal 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;
|
||||
};
|
@@ -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;
|
||||
|
@@ -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>&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>&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>
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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;
|
||||
};
|
||||
|
Reference in New Issue
Block a user