Added support of percentages display to details widget.

This commit is contained in:
23rd 2023-10-05 18:45:48 +03:00 committed by John Preston
parent 515850ec9b
commit fc3acff5d6
9 changed files with 187 additions and 80 deletions

View File

@ -63,6 +63,7 @@ struct StatisticalChart {
float64 timeStep = 0.;
bool isFooterHidden = false;
bool hasPercentages = false;
};

View File

@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "statistics/point_details_widget.h"
#include "ui/cached_round_corners.h"
#include "statistics/statistics_common.h"
#include "statistics/view/stack_linear_chart_common.h"
#include "ui/effects/ripple_animation.h"
#include "ui/painter.h"
#include "ui/rect.h"
@ -155,6 +157,16 @@ PointDetailsWidget::PointDetailsWidget(
}, lifetime());
}
_maxPercentageWidth = [&] {
if (_chartData.hasPercentages) {
const auto maxPercentageText = Ui::Text::String(
_textStyle,
u"10000%"_q);
return maxPercentageText.maxWidth();
}
return 0;
}();
const auto calculatedWidth = [&]{
const auto maxValueText = Ui::Text::String(
_textStyle,
@ -186,7 +198,8 @@ PointDetailsWidget::PointDetailsWidget(
+ rect::m::sum::h(st::statisticsDetailsPopupMargins)
+ rect::m::sum::h(st::statisticsDetailsPopupPadding)
+ st::statisticsDetailsPopupPadding.left() // Between strings.
+ maxNameTextWidth;
+ maxNameTextWidth
+ _maxPercentageWidth;
}();
sizeValue(
) | rpl::start_with_next([=](const QSize &s) {
@ -245,9 +258,19 @@ void PointDetailsWidget::setXIndex(int xIndex) {
_lines.clear();
_lines.reserve(_chartData.lines.size());
auto hasPositiveValues = false;
for (const auto &dataLine : _chartData.lines) {
const auto parts = _maxPercentageWidth
? PiePartsPercentage(
_chartData,
nullptr,
{ float64(xIndex), float64(xIndex) }).parts
: std::vector<PiePartData::Part>();
for (auto i = 0; i < _chartData.lines.size(); i++) {
const auto &dataLine = _chartData.lines[i];
auto textLine = Line();
textLine.id = dataLine.id;
if (_maxPercentageWidth) {
textLine.percentage.setText(_textStyle, parts[i].percentageText);
}
textLine.name.setText(_textStyle, dataLine.name);
textLine.value.setText(
_textStyle,
@ -353,12 +376,22 @@ void PointDetailsWidget::paintEvent(QPaintEvent *e) {
.availableWidth = valueWidth,
};
const auto nameContext = Ui::Text::PaintContext{
.position = QPoint(_textRect.x(), lineY),
.position = QPoint(
_textRect.x() + _maxPercentageWidth,
lineY),
.outerWidth = _textRect.width(),
.availableWidth = _textRect.width() - valueWidth,
};
p.setOpacity(line.alpha * line.alpha);
p.setPen(st::boxTextFg);
if (_maxPercentageWidth) {
const auto percentageContext = Ui::Text::PaintContext{
.position = QPoint(_textRect.x(), lineY),
.outerWidth = _textRect.width(),
.availableWidth = _textRect.width() - valueWidth,
};
line.percentage.draw(p, percentageContext);
}
line.name.draw(p, nameContext);
p.setPen(line.valueColor);
line.value.draw(p, valueContext);

View File

@ -60,10 +60,13 @@ private:
int id = 0;
Ui::Text::String name;
Ui::Text::String value;
Ui::Text::String percentage;
QColor valueColor;
float64 alpha = 1.;
};
int _maxPercentageWidth = 0;
QRect _innerRect;
QRect _textRect;
QImage _arrow;

View File

@ -109,6 +109,15 @@ Data::StatisticalChart StatisticalChartFromJSON(const QByteArray &json) {
result.defaultZoomXIndex.min = std::min(min, max);
result.defaultZoomXIndex.max = std::max(min, max);
}
{
const auto percentageShowIt = root.constFind(u"percentage"_q);
if (percentageShowIt != root.constEnd()) {
if (percentageShowIt->isBool()) {
result.hasPercentages = (percentageShowIt->toBool());
}
}
}
const auto colors = root.value(u"colors"_q).toObject();
const auto names = root.value(u"names"_q).toObject();

View File

@ -0,0 +1,88 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "statistics/view/stack_linear_chart_common.h"
#include "data/data_statistics_chart.h"
#include "statistics/chart_lines_filter_controller.h"
#include "statistics/statistics_common.h"
namespace Statistic {
PiePartData PiePartsPercentage(
const Data::StatisticalChart &chartData,
const std::shared_ptr<LinesFilterController> &linesFilter,
const Limits &xIndices) {
auto result = PiePartData();
result.parts.reserve(chartData.lines.size());
auto sums = std::vector<float64>();
sums.reserve(chartData.lines.size());
auto totalSum = 0.;
for (const auto &line : chartData.lines) {
auto sum = 0;
for (auto i = xIndices.min; i <= xIndices.max; i++) {
sum += line.y[i];
}
if (linesFilter) {
sum *= linesFilter->alpha(line.id);
}
totalSum += sum;
sums.push_back(sum);
}
auto stackedPercentage = 0.;
auto sumPercDiffs = 0.;
auto maxPercDiff = 0.;
auto minPercDiff = 0.;
auto maxPercDiffIndex = int(-1);
auto minPercDiffIndex = int(-1);
auto roundedPercentagesSum = 0.;
result.pieHasSinglePart = false;
constexpr auto kPerChar = '%';
for (auto k = 0; k < sums.size(); k++) {
const auto rawPercentage = totalSum ? (sums[k] / totalSum) : 0.;
const auto rounded = 0.01 * std::round(rawPercentage * 100.);
roundedPercentagesSum += rounded;
const auto diff = rawPercentage - rounded;
sumPercDiffs += diff;
const auto diffAbs = std::abs(diff);
if (maxPercDiff < diffAbs) {
maxPercDiff = diffAbs;
maxPercDiffIndex = k;
}
if (minPercDiff < diffAbs) {
minPercDiff = diffAbs;
minPercDiffIndex = k;
}
stackedPercentage += rounded;
result.parts.push_back({
rounded,
stackedPercentage * 360. - 180.,
QString::number(int(rounded * 100)) + kPerChar,
});
result.pieHasSinglePart |= (rounded == 1.);
}
{
const auto index = (roundedPercentagesSum > 1.)
? maxPercDiffIndex
: minPercDiffIndex;
if (index >= 0) {
result.parts[index].roundedPercentage += sumPercDiffs;
result.parts[index].percentageText = QString::number(
int(result.parts[index].roundedPercentage * 100)) + kPerChar;
const auto angleShrink = (sumPercDiffs) * 360.;
for (auto &part : result.parts) {
part.stackedAngle += angleShrink;
}
}
}
return result;
}
} // namespace Statistic

View File

@ -0,0 +1,34 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Data {
struct StatisticalChart;
} // namespace Data
namespace Statistic {
struct Limits;
class LinesFilterController;
struct PiePartData final {
struct Part final {
float64 roundedPercentage = 0; // 0.XX.
float64 stackedAngle = 0.;
QString percentageText;
};
std::vector<Part> parts;
bool pieHasSinglePart = false;
};
[[nodiscard]] PiePartData PiePartsPercentage(
const Data::StatisticalChart &chartData,
const std::shared_ptr<LinesFilterController> &linesFilter,
const Limits &xIndices);
} // namespace Statistic

View File

@ -166,7 +166,8 @@ void StackLinearChartView::prepareZoom(
}
}
void StackLinearChartView::applyParts(const std::vector<PiePartData> &parts) {
void StackLinearChartView::applyParts(
const std::vector<PiePartData::Part> &parts) {
for (auto k = 0; k < parts.size(); k++) {
_transition.lines[k].angle = parts[k].stackedAngle;
}
@ -184,72 +185,12 @@ void StackLinearChartView::saveZoomRange(const PaintContext &c) {
}
void StackLinearChartView::savePieTextParts(const PaintContext &c) {
_transition.textParts = partsPercentage(
auto data = PiePartsPercentage(
c.chartData,
linesFilterController(),
_transition.zoomedInRangeXIndices);
}
auto StackLinearChartView::partsPercentage(
const Data::StatisticalChart &chartData,
const Limits &xIndices) -> std::vector<PiePartData> {
auto result = std::vector<PiePartData>();
result.reserve(chartData.lines.size());
auto sums = std::vector<float64>();
sums.reserve(chartData.lines.size());
auto totalSum = 0.;
const auto &linesFilter = linesFilterController();
for (const auto &line : chartData.lines) {
auto sum = 0;
for (auto i = xIndices.min; i <= xIndices.max; i++) {
sum += line.y[i];
}
sum *= linesFilter->alpha(line.id);
totalSum += sum;
sums.push_back(sum);
}
auto stackedPercentage = 0.;
auto sumPercDiffs = 0.;
auto maxPercDiff = 0.;
auto minPercDiff = 0.;
auto maxPercDiffIndex = int(-1);
auto minPercDiffIndex = int(-1);
auto roundedPercentagesSum = 0.;
_pieHasSinglePart = false;
for (auto k = 0; k < sums.size(); k++) {
const auto rawPercentage = totalSum ? (sums[k] / totalSum) : 0.;
const auto rounded = 0.01 * std::round(rawPercentage * 100.);
roundedPercentagesSum += rounded;
const auto diff = rawPercentage - rounded;
sumPercDiffs += diff;
const auto diffAbs = std::abs(diff);
if (maxPercDiff < diffAbs) {
maxPercDiff = diffAbs;
maxPercDiffIndex = k;
}
if (minPercDiff < diffAbs) {
minPercDiff = diffAbs;
minPercDiffIndex = k;
}
stackedPercentage += rounded;
result.push_back({ rounded, stackedPercentage * 360. - 180. });
_pieHasSinglePart |= (rounded == 1.);
}
{
const auto index = (roundedPercentagesSum > 1.)
? maxPercDiffIndex
: minPercDiffIndex;
if (index >= 0) {
result[index].roundedPercentage += sumPercDiffs;
const auto angleShrink = (sumPercDiffs) * 360.;
for (auto i = index; i < result.size(); i++) {
result[i].stackedAngle += angleShrink;
}
}
}
return result;
_transition.textParts = std::move(data.parts);
_pieHasSinglePart = data.pieHasSinglePart;
}
void StackLinearChartView::paintChartOrZoomAnimation(
@ -564,9 +505,12 @@ void StackLinearChartView::paintZoomed(QPainter &p, const PaintContext &c) {
}
saveZoomRange(c);
const auto parts = partsPercentage(
const auto partsData = PiePartsPercentage(
c.chartData,
linesFilterController(),
_transition.zoomedInRangeXIndices);
_pieHasSinglePart = partsData.pieHasSinglePart;
const auto &parts = partsData.parts;
applyParts(parts);
p.fillRect(c.rect + QMargins(0, 0, 0, st::lineWidth), st::boxBg);
@ -726,7 +670,7 @@ void StackLinearChartView::paintPieText(QPainter &p, const PaintContext &c) {
const auto scale = (maxScale == minScale)
? 0.
: (minScale) + percentage * (maxScale - minScale);
const auto text = QString::number(int(percentage * 100)) + u"%"_q;
const auto text = parts[k].percentageText;
const auto textW = font->width(text);
const auto textH = font->height;
const auto textXShift = textW / 2.;

View File

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "statistics/segment_tree.h"
#include "statistics/statistics_common.h"
#include "statistics/view/abstract_chart_view.h"
#include "statistics/view/stack_linear_chart_common.h"
#include "ui/effects/animations.h"
#include "ui/effects/animation_value.h"
@ -65,19 +66,11 @@ private:
[[nodiscard]] bool skipSelectedTranslation() const;
struct PiePartData {
float64 roundedPercentage = 0; // 0.XX.
float64 stackedAngle = 0.;
};
void prepareZoom(const PaintContext &c, TransitionStep step);
void saveZoomRange(const PaintContext &c);
void savePieTextParts(const PaintContext &c);
void applyParts(const std::vector<PiePartData> &parts);
[[nodiscard]] std::vector<PiePartData> partsPercentage(
const Data::StatisticalChart &chartData,
const Limits &xIndices);
void applyParts(const std::vector<PiePartData::Part> &parts);
struct SelectedPoints final {
int lastXIndex = -1;
@ -107,7 +100,7 @@ private:
Limits zoomedInRange;
Limits zoomedInRangeXIndices;
std::vector<PiePartData> textParts;
std::vector<PiePartData::Part> textParts;
} _transition;
std::vector<bool> _skipPoints;

View File

@ -189,6 +189,8 @@ PRIVATE
statistics/view/stack_chart_common.h
statistics/view/stack_chart_view.cpp
statistics/view/stack_chart_view.h
statistics/view/stack_linear_chart_common.cpp
statistics/view/stack_linear_chart_common.h
statistics/view/stack_linear_chart_view.cpp
statistics/view/stack_linear_chart_view.h