diff --git a/Telegram/SourceFiles/statistics/point_details_widget.cpp b/Telegram/SourceFiles/statistics/point_details_widget.cpp index c9431f9655..21bb455760 100644 --- a/Telegram/SourceFiles/statistics/point_details_widget.cpp +++ b/Telegram/SourceFiles/statistics/point_details_widget.cpp @@ -279,7 +279,7 @@ void PointDetailsWidget::setXIndex(int xIndex) { _lines.reserve(_chartData.lines.size()); auto hasPositiveValues = false; const auto parts = _maxPercentageWidth - ? PiePartsPercentage( + ? PiePartsPercentageByIndices( _chartData, nullptr, { float64(xIndex), float64(xIndex) }).parts diff --git a/Telegram/SourceFiles/statistics/view/stack_linear_chart_common.cpp b/Telegram/SourceFiles/statistics/view/stack_linear_chart_common.cpp index 4fece7a82d..a2ca3ff01d 100644 --- a/Telegram/SourceFiles/statistics/view/stack_linear_chart_common.cpp +++ b/Telegram/SourceFiles/statistics/view/stack_linear_chart_common.cpp @@ -14,25 +14,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Statistic { PiePartData PiePartsPercentage( - const Data::StatisticalChart &chartData, - const std::shared_ptr &linesFilter, - const Limits &xIndices) { + const std::vector &sums, + float64 totalSum, + bool round) { 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); - } + result.parts.reserve(sums.size()); auto stackedPercentage = 0.; auto sumPercDiffs = 0.; @@ -46,7 +32,9 @@ PiePartData PiePartsPercentage( 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.); + const auto rounded = round + ? (0.01 * std::round(rawPercentage * 100.)) + : rawPercentage; roundedPercentagesSum += rounded; const auto diff = rawPercentage - rounded; sumPercDiffs += diff; @@ -68,7 +56,7 @@ PiePartData PiePartsPercentage( }); result.pieHasSinglePart |= (rounded == 1.); } - { + if (round) { const auto index = (roundedPercentagesSum > 1.) ? maxPercDiffIndex : minPercDiffIndex; @@ -85,4 +73,25 @@ PiePartData PiePartsPercentage( return result; } +PiePartData PiePartsPercentageByIndices( + const Data::StatisticalChart &chartData, + const std::shared_ptr &linesFilter, + const Limits &xIndices) { + 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); + } + return PiePartsPercentage(sums, totalSum, true); +} + } // namespace Statistic diff --git a/Telegram/SourceFiles/statistics/view/stack_linear_chart_common.h b/Telegram/SourceFiles/statistics/view/stack_linear_chart_common.h index fb481de351..5e21a540a5 100644 --- a/Telegram/SourceFiles/statistics/view/stack_linear_chart_common.h +++ b/Telegram/SourceFiles/statistics/view/stack_linear_chart_common.h @@ -27,6 +27,11 @@ struct PiePartData final { }; [[nodiscard]] PiePartData PiePartsPercentage( + const std::vector &sums, + float64 totalSum, + bool round); + +[[nodiscard]] PiePartData PiePartsPercentageByIndices( const Data::StatisticalChart &chartData, const std::shared_ptr &linesFilter, const Limits &xIndices); diff --git a/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.cpp b/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.cpp index 98c01639ac..18249e95ea 100644 --- a/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.cpp +++ b/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.cpp @@ -87,6 +87,57 @@ inline float64 InterpolationRatio(float64 from, float64 to, float64 result) { } // namespace +void StackLinearChartView::ChangingPiePartController::setParts( + const std::vector &was, + const std::vector &now) { + if (_animValues.size() != was.size()) { + _animValues = std::vector(was.size(), anim::value()); + for (auto i = 0; i < was.size(); i++) { + _animValues[i] = anim::value( + was[i].roundedPercentage, + now[i].roundedPercentage); + } + } else { + for (auto i = 0; i < was.size(); i++) { + _animValues[i] = anim::value( + _animValues[i].current(), + now[i].roundedPercentage); + } + } + _startedAt = crl::now(); + _isFinished = false; +} + +void StackLinearChartView::ChangingPiePartController::update() { + const auto progress = std::clamp( + (crl::now() - _startedAt) / float64(st::slideWrapDuration), + 0., + 1.); + auto totalSum = 0.; + auto finished = true; + auto result = std::vector(); + result.reserve(_animValues.size()); + for (auto &anim : _animValues) { + anim.update(progress, anim::easeOutCubic); + if (finished && (anim.current() != anim.to())) { + finished = false; + } + const auto value = anim.current(); + result.push_back(value); + totalSum += value; + } + _isFinished = finished; + _current = PiePartsPercentage(result, totalSum, false); +} + +PiePartData StackLinearChartView::ChangingPiePartController::current() const { + return _current; +} + +bool StackLinearChartView::ChangingPiePartController::isFinished() const { + return _isFinished; +} + StackLinearChartView::StackLinearChartView() { _piePartAnimation.init([=] { AbstractChartView::update(); }); } @@ -185,7 +236,7 @@ void StackLinearChartView::saveZoomRange(const PaintContext &c) { } void StackLinearChartView::savePieTextParts(const PaintContext &c) { - auto data = PiePartsPercentage( + auto data = PiePartsPercentageByIndices( c.chartData, linesFilterController(), _transition.zoomedInRangeXIndices); @@ -504,14 +555,33 @@ void StackLinearChartView::paintZoomed(QPainter &p, const PaintContext &c) { return; } + const auto wasZoomedInRangeXIndices = _transition.zoomedInRangeXIndices; saveZoomRange(c); - const auto partsData = PiePartsPercentage( + const auto &[zoomedStart, zoomedEnd] = _transition.zoomedInRangeXIndices; + const auto partsData = PiePartsPercentageByIndices( c.chartData, linesFilterController(), _transition.zoomedInRangeXIndices); + const auto xIndicesChanged = (wasZoomedInRangeXIndices.min != zoomedStart) + || (wasZoomedInRangeXIndices.max != zoomedEnd); + if (xIndicesChanged) { + const auto wasParts = PiePartsPercentageByIndices( + c.chartData, + linesFilterController(), + wasZoomedInRangeXIndices); + _changingPieController.setParts(wasParts.parts, partsData.parts); + if (!_piePartAnimation.animating()) { + _piePartAnimation.start(); + } + } + if (!_changingPieController.isFinished()) { + _changingPieController.update(); + } _pieHasSinglePart = partsData.pieHasSinglePart; - const auto &parts = partsData.parts; - applyParts(parts); + applyParts(partsData.parts); + const auto &parts = _changingPieController.isFinished() + ? partsData.parts + : _changingPieController.current().parts; p.fillRect(c.rect + QMargins(0, 0, 0, st::lineWidth), st::boxBg); const auto center = QPointF(c.rect.center()); @@ -547,14 +617,12 @@ void StackLinearChartView::paintZoomed(QPainter &p, const PaintContext &c) { selectedLineIndex = k; } } - if (_piePartController.isFinished()) { + if (_piePartController.isFinished() && _changingPieController.isFinished()) { _piePartAnimation.stop(); } paintPieText(p, c); if (selectedLineIndex >= 0) { - const auto &[zoomedStart, zoomedEnd] = - _transition.zoomedInRangeXIndices; const auto &line = c.chartData.lines[selectedLineIndex]; auto sum = 0; for (auto i = zoomedStart; i <= zoomedEnd; i++) { @@ -633,11 +701,13 @@ void StackLinearChartView::paintZoomedFooter( } void StackLinearChartView::paintPieText(QPainter &p, const PaintContext &c) { - constexpr auto kMinPercentage = 0.03; + constexpr auto kMinPercentage = 0.039; if (_transition.progress == 1.) { savePieTextParts(c); } - const auto &parts = _transition.textParts; + const auto &parts = _changingPieController.isFinished() + ? _transition.textParts + : _changingPieController.current().parts; const auto center = QPointF(c.rect.center()); const auto side = (c.rect.width() / 2.) * kCircleSizeRatio; diff --git a/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.h b/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.h index ec9a2d9e00..8e59798dca 100644 --- a/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.h +++ b/Telegram/SourceFiles/statistics/view/stack_linear_chart_view.h @@ -121,8 +121,28 @@ private: LineId _selected = -1; }; + + class ChangingPiePartController final { + public: + void setParts( + const std::vector &was, + const std::vector &now); + void update(); + [[nodiscard]] PiePartData current() const; + [[nodiscard]] bool isFinished() const; + + private: + crl::time _startedAt = 0;; + std::vector _animValues; + PiePartData _current; + bool _isFinished = true; + + }; + PiePartController _piePartController; + ChangingPiePartController _changingPieController; Ui::Animations::Basic _piePartAnimation; + bool _pieHasSinglePart = false; };