diff --git a/Telegram/SourceFiles/data/data_statistics_chart.h b/Telegram/SourceFiles/data/data_statistics_chart.h index 89b65e7da..5eebf0e07 100644 --- a/Telegram/SourceFiles/data/data_statistics_chart.h +++ b/Telegram/SourceFiles/data/data_statistics_chart.h @@ -63,6 +63,7 @@ struct StatisticalChart { float64 timeStep = 0.; bool isFooterHidden = false; + bool hasPercentages = false; }; diff --git a/Telegram/SourceFiles/statistics/point_details_widget.cpp b/Telegram/SourceFiles/statistics/point_details_widget.cpp index bb9c9c162..305d31b6b 100644 --- a/Telegram/SourceFiles/statistics/point_details_widget.cpp +++ b/Telegram/SourceFiles/statistics/point_details_widget.cpp @@ -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(); + 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); diff --git a/Telegram/SourceFiles/statistics/point_details_widget.h b/Telegram/SourceFiles/statistics/point_details_widget.h index 5446d3be8..8b066a5db 100644 --- a/Telegram/SourceFiles/statistics/point_details_widget.h +++ b/Telegram/SourceFiles/statistics/point_details_widget.h @@ -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; diff --git a/Telegram/SourceFiles/statistics/statistics_data_deserialize.cpp b/Telegram/SourceFiles/statistics/statistics_data_deserialize.cpp index 6a43f4fd2..b4dafe9e0 100644 --- a/Telegram/SourceFiles/statistics/statistics_data_deserialize.cpp +++ b/Telegram/SourceFiles/statistics/statistics_data_deserialize.cpp @@ -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(); diff --git a/Telegram/SourceFiles/statistics/view/stack_linear_chart_common.cpp b/Telegram/SourceFiles/statistics/view/stack_linear_chart_common.cpp new file mode 100644 index 000000000..4fece7a82 --- /dev/null +++ b/Telegram/SourceFiles/statistics/view/stack_linear_chart_common.cpp @@ -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 &linesFilter, + const Limits &xIndices) { + auto result = PiePartData(); + result.parts.reserve(chartData.lines.size()); + auto sums = std::vector(); + 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 diff --git a/Telegram/SourceFiles/statistics/view/stack_linear_chart_common.h b/Telegram/SourceFiles/statistics/view/stack_linear_chart_common.h new file mode 100644 index 000000000..fb481de35 --- /dev/null +++ b/Telegram/SourceFiles/statistics/view/stack_linear_chart_common.h @@ -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 parts; + bool pieHasSinglePart = false; +}; + +[[nodiscard]] PiePartData PiePartsPercentage( + const Data::StatisticalChart &chartData, + const std::shared_ptr &linesFilter, + const Limits &xIndices); + +} // namespace Statistic diff --git a/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.cpp b/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.cpp index c555a6824..98c01639a 100644 --- a/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.cpp +++ b/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.cpp @@ -166,7 +166,8 @@ void StackLinearChartView::prepareZoom( } } -void StackLinearChartView::applyParts(const std::vector &parts) { +void StackLinearChartView::applyParts( + const std::vector &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 { - auto result = std::vector(); - result.reserve(chartData.lines.size()); - auto sums = std::vector(); - 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.; diff --git a/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.h b/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.h index b43f714fc..ec9a2d9e0 100644 --- a/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.h +++ b/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.h @@ -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 &parts); - [[nodiscard]] std::vector partsPercentage( - const Data::StatisticalChart &chartData, - const Limits &xIndices); + void applyParts(const std::vector &parts); struct SelectedPoints final { int lastXIndex = -1; @@ -107,7 +100,7 @@ private: Limits zoomedInRange; Limits zoomedInRangeXIndices; - std::vector textParts; + std::vector textParts; } _transition; std::vector _skipPoints; diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 8b0d2559d..2d39aec0e 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -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