From 90517161725a71eb335ad05f1a5f6907600e6ecc Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 25 Sep 2023 15:25:39 +0300 Subject: [PATCH] Added initial support of sync zoom to chart widget. --- .../SourceFiles/statistics/chart_widget.cpp | 130 +++++++++++++++++- .../SourceFiles/statistics/chart_widget.h | 3 + 2 files changed, 127 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/statistics/chart_widget.cpp b/Telegram/SourceFiles/statistics/chart_widget.cpp index 90cc3cc4b..9c3780301 100644 --- a/Telegram/SourceFiles/statistics/chart_widget.cpp +++ b/Telegram/SourceFiles/statistics/chart_widget.cpp @@ -38,6 +38,22 @@ inline float64 InterpolationRatio(float64 from, float64 to, float64 result) { return (result - from) / (to - from); }; +[[nodiscard]] QString HeaderRightInfo( + const Data::StatisticalChart &chartData, + const Limits &limits) { + return (limits.min == limits.max) + ? chartData.getDayString(limits.min) + : chartData.getDayString(limits.min) + + ' ' + + QChar(8212) + + ' ' + + chartData.getDayString(limits.max); +} + +[[nodiscard]] bool IsChartHasLocalZoom(ChartViewType type) { + return type == ChartViewType::StackLinear; +} + void PaintBottomLine( QPainter &p, const std::vector &dates, @@ -1014,11 +1030,7 @@ void ChartWidget::updateHeader() { return; } const auto indices = _animationController.currentXIndices(); - _header->setRightInfo(_chartData.getDayString(indices.min) - + ' ' - + QChar(8212) - + ' ' - + _chartData.getDayString(indices.max)); + _header->setRightInfo(HeaderRightInfo(_chartData, indices)); _header->update(); } @@ -1091,13 +1103,22 @@ void ChartWidget::setupDetails() { } return maxValue; }(); + if (hasLocalZoom()) { + _zoomEnabled = true; + } _details.widget = base::make_unique_q( this, _chartData, maxAbsoluteValue, _zoomEnabled); _details.widget->setClickedCallback([=] { - if (const auto index = _details.widget->xIndex(); index >= 0) { + const auto index = _details.widget->xIndex(); + if (index < 0) { + return; + } + if (hasLocalZoom()) { + processLocalZoom(index); + } else { _zoomRequests.fire_copy(_chartData.x[index]); } }); @@ -1178,6 +1199,103 @@ void ChartWidget::setupDetails() { }); } +bool ChartWidget::hasLocalZoom() const { + return _chartData + && _chartView->maybeLocalZoom({ + _chartData, + AbstractChartView::LocalZoomArgs::Type::CheckAvailability, + }).hasZoom; +} + +void ChartWidget::processLocalZoom(int xIndex) { + using Type = AbstractChartView::LocalZoomArgs::Type; + constexpr auto kFooterZoomDuration = crl::time(400); + const auto wasZoom = _footer->xPercentageLimits(); + + const auto header = Ui::CreateChild
(this); + header->show(); + _header->geometryValue( + ) | rpl::start_with_next([=](const QRect &g) { + header->setGeometry(g); + }, header->lifetime()); + header->setRightInfo(_chartData.getDayString(xIndex)); + + const auto zoomOutButton = Ui::CreateChild( + header, + tr::lng_stats_zoom_out(), + st::statisticsHeaderButton); + zoomOutButton->show(); + zoomOutButton->setTextTransform( + Ui::RoundButton::TextTransform::NoTransform); + zoomOutButton->setClickedCallback([=] { + auto lifetime = std::make_shared(); + const auto animation = lifetime->make_state(); + const auto currentXPercentage = _footer->xPercentageLimits(); + animation->start([=](float64 value) { + _chartView->maybeLocalZoom({ + _chartData, + Type::SkipCalculation, + value, + }); + const auto p = value; + _footer->setXPercentageLimits({ + anim::interpolateF(wasZoom.min, currentXPercentage.min, p), + anim::interpolateF(wasZoom.max, currentXPercentage.max, p), + }); + if (value == 0.) { + if (lifetime) { + lifetime->destroy(); + } + } + }, 1., 0., kFooterZoomDuration, anim::easeOutCirc); + + Ui::Animations::HideWidgets({ header }); + }); + + Ui::Animations::ShowWidgets({ header }); + + zoomOutButton->moveToLeft(0, 0); + + const auto finish = [=](const Limits &zoomLimit) { + _footer->xPercentageLimitsChange( + ) | rpl::start_with_next([=](const Limits &l) { + const auto result = FindNearestElements( + _chartData.xPercentage, + Limits{ + anim::interpolateF(zoomLimit.min, zoomLimit.max, l.min), + anim::interpolateF(zoomLimit.min, zoomLimit.max, l.max), + }); + header->setRightInfo(HeaderRightInfo(_chartData, result)); + header->update(); + }, header->lifetime()); + }; + + { + auto lifetime = std::make_shared(); + const auto animation = lifetime->make_state(); + _chartView->maybeLocalZoom({ _chartData, Type::Prepare }); + animation->start([=](float64 value) { + const auto zoom = _chartView->maybeLocalZoom({ + _chartData, + Type::Process, + value, + xIndex, + }); + const auto result = Limits{ + anim::interpolateF(wasZoom.min, zoom.range.min, value), + anim::interpolateF(wasZoom.max, zoom.range.max, value), + }; + _footer->setXPercentageLimits(result); + if (value == 1.) { + if (lifetime) { + lifetime->destroy(); + } + finish(zoom.limit); + } + }, 0., 1., kFooterZoomDuration, anim::easeOutCirc); + } +} + void ChartWidget::setupFilterButtons() { if (!_chartData || (_chartData.lines.size() <= 1)) { _filterButtons = nullptr; diff --git a/Telegram/SourceFiles/statistics/chart_widget.h b/Telegram/SourceFiles/statistics/chart_widget.h index 6aa5d91b4..132301071 100644 --- a/Telegram/SourceFiles/statistics/chart_widget.h +++ b/Telegram/SourceFiles/statistics/chart_widget.h @@ -134,6 +134,9 @@ private: void updateChartFullWidth(int w); + [[nodiscard]] bool hasLocalZoom() const; + void processLocalZoom(int xIndex); + const base::unique_qptr _chartArea; const std::unique_ptr
_header; const std::unique_ptr