| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558 |
- /*
- 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/chart_widget.h"
- #include "base/qt/qt_key_modifiers.h"
- #include "lang/lang_keys.h"
- #include "statistics/chart_lines_filter_controller.h"
- #include "statistics/statistics_format_values.h"
- #include "statistics/view/abstract_chart_view.h"
- #include "statistics/view/chart_view_factory.h"
- #include "statistics/view/stack_chart_common.h"
- #include "statistics/widgets/chart_header_widget.h"
- #include "statistics/widgets/chart_lines_filter_widget.h"
- #include "statistics/widgets/point_details_widget.h"
- #include "ui/abstract_button.h"
- #include "ui/effects/animation_value_f.h"
- #include "ui/effects/ripple_animation.h"
- #include "ui/effects/show_animation.h"
- #include "ui/image/image_prepare.h"
- #include "ui/painter.h"
- #include "ui/rect.h"
- #include "ui/widgets/buttons.h"
- #include "styles/style_layers.h"
- #include "styles/style_statistics.h"
- namespace Statistic {
- namespace {
- constexpr auto kHeightLimitsUpdateTimeout = crl::time(320);
- inline float64 InterpolationRatio(float64 from, float64 to, float64 result) {
- return (result - from) / (to - from);
- };
- void FillLineColorsByKey(Data::StatisticalChart &chartData) {
- for (auto &line : chartData.lines) {
- if (line.colorKey == u"BLUE"_q) {
- line.color = st::statisticsChartLineBlue->c;
- } else if (line.colorKey == u"GREEN"_q) {
- line.color = st::statisticsChartLineGreen->c;
- } else if (line.colorKey == u"RED"_q) {
- line.color = st::statisticsChartLineRed->c;
- } else if (line.colorKey == u"GOLDEN"_q) {
- line.color = st::statisticsChartLineGolden->c;
- } else if (line.colorKey == u"LIGHTBLUE"_q) {
- line.color = st::statisticsChartLineLightblue->c;
- } else if (line.colorKey == u"LIGHTGREEN"_q) {
- line.color = st::statisticsChartLineLightgreen->c;
- } else if (line.colorKey == u"ORANGE"_q) {
- line.color = st::statisticsChartLineOrange->c;
- } else if (line.colorKey == u"INDIGO"_q) {
- line.color = st::statisticsChartLineIndigo->c;
- } else if (line.colorKey == u"PURPLE"_q) {
- line.color = st::statisticsChartLinePurple->c;
- } else if (line.colorKey == u"CYAN"_q) {
- line.color = st::statisticsChartLineCyan->c;
- }
- }
- }
- [[nodiscard]] QString HeaderSubTitle(
- const Data::StatisticalChart &chartData,
- int xIndexMin,
- int xIndexMax) {
- constexpr auto kOneDay = 3600 * 24 * 1000;
- const auto leftTimestamp = chartData.x[xIndexMin];
- if (leftTimestamp < kOneDay) {
- return {};
- }
- const auto leftText = LangDayMonthYear(leftTimestamp / 1000);
- if ((xIndexMin == xIndexMax) && !chartData.weekFormat) {
- return leftText;
- } else {
- constexpr auto kSevenDays = 3600 * 24 * 7;
- const auto rightTimestamp = 0
- + (chartData.x[xIndexMax] / 1000)
- + (chartData.weekFormat ? kSevenDays : 0);
- return leftText
- + ' '
- + QChar(8212)
- + ' '
- + LangDayMonthYear(rightTimestamp);
- }
- }
- void PaintBottomLine(
- QPainter &p,
- const std::vector<ChartWidget::BottomCaptionLineData> &dates,
- Data::StatisticalChart &chartData,
- const Limits &xPercentageLimits,
- int fullWidth,
- int chartWidth,
- int y,
- int captionIndicesOffset) {
- p.setFont(st::statisticsDetailsBottomCaptionStyle.font);
- const auto opacity = p.opacity();
- const auto startXIndex = chartData.findStartIndex(
- xPercentageLimits.min);
- const auto endXIndex = chartData.findEndIndex(
- startXIndex,
- xPercentageLimits.max);
- const auto captionMaxWidth = chartData.dayStringMaxWidth;
- const auto edgeAlphaSize = captionMaxWidth / 4.;
- for (auto k = 0; k < dates.size(); k++) {
- const auto &date = dates[k];
- const auto isLast = (k == dates.size() - 1);
- const auto resultAlpha = date.alpha;
- const auto step = std::max(date.step, 1);
- auto start = startXIndex - captionIndicesOffset;
- while (start % step != 0) {
- start--;
- }
- auto end = endXIndex - captionIndicesOffset;
- while ((end % step != 0) || end < (chartData.x.size() - 1)) {
- end++;
- }
- start += captionIndicesOffset;
- end += captionIndicesOffset;
- const auto offset = fullWidth * xPercentageLimits.min;
- // 30 ms / 200 ms = 0.15.
- constexpr auto kFastAlphaSpeed = 0.85;
- const auto hasFastAlpha = (date.stepRaw < dates.back().stepMinFast);
- const auto fastAlpha = isLast
- ? 1.
- : std::max(resultAlpha - kFastAlphaSpeed, 0.);
- for (auto i = start; i < end; i += step) {
- if ((i < 0) || (i >= (chartData.x.size() - 1))) {
- continue;
- }
- const auto xPercentage = (chartData.x[i] - chartData.x.front())
- / float64(chartData.x.back() - chartData.x.front());
- const auto xPoint = xPercentage * fullWidth - offset;
- const auto r = QRectF(
- xPoint - captionMaxWidth / 2.,
- y,
- captionMaxWidth,
- st::statisticsChartBottomCaptionHeight);
- const auto edgeAlpha = (r.x() < 0)
- ? std::max(
- 0.,
- 1. + (r.x() / edgeAlphaSize))
- : (rect::right(r) > chartWidth)
- ? std::max(
- 0.,
- 1. + ((chartWidth - rect::right(r)) / edgeAlphaSize))
- : 1.;
- p.setOpacity(opacity
- * edgeAlpha
- * (hasFastAlpha ? fastAlpha : resultAlpha));
- p.drawText(r, chartData.getDayString(i), style::al_center);
- }
- }
- }
- } // namespace
- class RpMouseWidget : public Ui::AbstractButton {
- public:
- using Ui::AbstractButton::AbstractButton;
- struct State {
- QPoint point;
- QEvent::Type mouseState;
- };
- [[nodiscard]] const QPoint &start() const;
- [[nodiscard]] rpl::producer<State> mouseStateChanged() const;
- protected:
- void mousePressEvent(QMouseEvent *e) override;
- void mouseMoveEvent(QMouseEvent *e) override;
- void mouseReleaseEvent(QMouseEvent *e) override;
- private:
- QPoint _start = QPoint(-1, -1);
- rpl::event_stream<State> _mouseStateChanged;
- };
- const QPoint &RpMouseWidget::start() const {
- return _start;
- }
- rpl::producer<RpMouseWidget::State> RpMouseWidget::mouseStateChanged() const {
- return _mouseStateChanged.events();
- }
- void RpMouseWidget::mousePressEvent(QMouseEvent *e) {
- _start = e->pos();
- _mouseStateChanged.fire({ e->pos(), QEvent::MouseButtonPress });
- }
- void RpMouseWidget::mouseMoveEvent(QMouseEvent *e) {
- if (_start.x() >= 0 || _start.y() >= 0) {
- _mouseStateChanged.fire({ e->pos(), QEvent::MouseMove });
- }
- }
- void RpMouseWidget::mouseReleaseEvent(QMouseEvent *e) {
- _start = { -1, -1 };
- _mouseStateChanged.fire({ e->pos(), QEvent::MouseButtonRelease });
- }
- class ChartWidget::Footer final : public RpMouseWidget {
- public:
- using PaintCallback = Fn<void(QPainter &, const QRect &)>;
- explicit Footer(not_null<Ui::RpWidget*> parent);
- void setXPercentageLimits(const Limits &xLimits);
- [[nodiscard]] Limits xPercentageLimits() const;
- [[nodiscard]] rpl::producer<Limits> xPercentageLimitsChange() const;
- void setPaintChartCallback(PaintCallback paintChartCallback);
- protected:
- void paintEvent(QPaintEvent *e) override;
- int resizeGetHeight(int newWidth) override;
- private:
- void moveSide(bool left, float64 x);
- void moveCenter(
- bool isDirectionToLeft,
- float64 x,
- float64 diffBetweenStartAndLeft);
- void fire() const;
- enum class DragArea {
- None,
- Middle,
- Left,
- Right,
- };
- DragArea _dragArea = DragArea::None;
- float64 _diffBetweenStartAndSide = 0;
- Ui::Animations::Simple _moveCenterAnimation;
- bool _draggedAfterPress = false;
- const QPen _sidePen;
- float64 _width = 0.;
- float64 _widthBetweenSides = 0.;
- PaintCallback _paintChartCallback;
- QImage _frame;
- QImage _mask;
- Limits _leftSide;
- Limits _rightSide;
- rpl::event_stream<Limits> _xPercentageLimitsChange;
- };
- ChartWidget::Footer::Footer(not_null<Ui::RpWidget*> parent)
- : RpMouseWidget(parent)
- , _sidePen(
- st::premiumButtonFg,
- st::statisticsChartLineWidth,
- Qt::SolidLine,
- Qt::RoundCap) {
- sizeValue(
- ) | rpl::take(2) | rpl::start_with_next([=](const QSize &s) {
- const auto current = xPercentageLimits();
- if (current.min == current.max) {
- setXPercentageLimits({ 0., 1. });
- }
- }, lifetime());
- mouseStateChanged(
- ) | rpl::start_with_next([=](const RpMouseWidget::State &state) {
- if (_moveCenterAnimation.animating()) {
- return;
- }
- const auto posX = state.point.x();
- const auto isLeftSide = (posX >= _leftSide.min)
- && (posX <= _leftSide.max);
- const auto isRightSide = !isLeftSide
- && (posX >= _rightSide.min)
- && (posX <= _rightSide.max);
- switch (state.mouseState) {
- case QEvent::MouseMove: {
- _draggedAfterPress = true;
- if (_dragArea == DragArea::None) {
- return;
- }
- const auto resultX = posX - _diffBetweenStartAndSide;
- if (_dragArea == DragArea::Right) {
- moveSide(false, resultX);
- } else if (_dragArea == DragArea::Left) {
- moveSide(true, resultX);
- } else if (_dragArea == DragArea::Middle) {
- const auto toLeft = (posX
- - _diffBetweenStartAndSide
- - _leftSide.min) <= 0;
- moveCenter(toLeft, posX, _diffBetweenStartAndSide);
- }
- fire();
- } break;
- case QEvent::MouseButtonPress: {
- _draggedAfterPress = false;
- _dragArea = isLeftSide
- ? DragArea::Left
- : isRightSide
- ? DragArea::Right
- : ((posX < _leftSide.min) || (posX > _rightSide.max))
- ? DragArea::None
- : DragArea::Middle;
- _diffBetweenStartAndSide = isRightSide
- ? (start().x() - _rightSide.min)
- : (start().x() - _leftSide.min);
- } break;
- case QEvent::MouseButtonRelease: {
- const auto finish = [=] {
- _dragArea = DragArea::None;
- fire();
- };
- if ((_dragArea == DragArea::None) && !_draggedAfterPress) {
- const auto startX = _leftSide.min
- + (_rightSide.max - _leftSide.min) / 2;
- const auto finishX = posX;
- const auto toLeft = (finishX <= startX);
- const auto diffBetweenStartAndLeft = startX - _leftSide.min;
- _moveCenterAnimation.stop();
- _moveCenterAnimation.start([=](float64 value) {
- moveCenter(toLeft, value, diffBetweenStartAndLeft);
- fire();
- update();
- if (value == finishX) {
- finish();
- }
- },
- startX,
- finishX,
- st::slideWrapDuration,
- anim::sineInOut);
- } else {
- finish();
- }
- } break;
- }
- update();
- }, lifetime());
- }
- int ChartWidget::Footer::resizeGetHeight(int newWidth) {
- const auto h = st::statisticsChartFooterHeight;
- if (!newWidth) {
- return h;
- }
- const auto was = xPercentageLimits();
- const auto w = float64(st::statisticsChartFooterSideWidth);
- _width = newWidth - w;
- _widthBetweenSides = newWidth - w * 2.;
- _mask = Ui::RippleAnimation::RoundRectMask(
- QSize(newWidth, h - st::lineWidth * 2),
- st::boxRadius);
- _frame = _mask;
- if (_widthBetweenSides && was.max) {
- setXPercentageLimits(was);
- }
- return h;
- }
- Limits ChartWidget::Footer::xPercentageLimits() const {
- return {
- .min = _widthBetweenSides ? _leftSide.min / _widthBetweenSides : 0.,
- .max = _widthBetweenSides
- ? (_rightSide.min - st::statisticsChartFooterSideWidth)
- / _widthBetweenSides
- : 0.,
- };
- }
- void ChartWidget::Footer::fire() const {
- _xPercentageLimitsChange.fire(xPercentageLimits());
- }
- void ChartWidget::Footer::moveCenter(
- bool isDirectionToLeft,
- float64 x,
- float64 diffBetweenStartAndLeft) {
- const auto resultX = x - diffBetweenStartAndLeft;
- const auto diffBetweenSides = std::max(
- _rightSide.min - _leftSide.min,
- float64(st::statisticsChartFooterBetweenSide));
- if (isDirectionToLeft) {
- moveSide(true, resultX);
- moveSide(false, _leftSide.min + diffBetweenSides);
- } else {
- moveSide(false, resultX + diffBetweenSides);
- moveSide(true, _rightSide.min - diffBetweenSides);
- }
- }
- void ChartWidget::Footer::moveSide(bool left, float64 x) {
- const auto w = float64(st::statisticsChartFooterSideWidth);
- const auto mid = float64(st::statisticsChartFooterBetweenSide);
- if (_width < (2 * w + mid)) {
- return;
- } else if (left) {
- const auto rightLimit = _rightSide.min - w - mid;
- const auto min = std::clamp(
- x,
- 0.,
- (rightLimit <= 0) ? _widthBetweenSides : rightLimit);
- _leftSide = Limits{ .min = min, .max = min + w };
- } else if (!left) {
- const auto min = std::clamp(x, _leftSide.max + mid, _width);
- _rightSide = Limits{ .min = min, .max = min + w };
- }
- }
- void ChartWidget::Footer::setPaintChartCallback(
- PaintCallback paintChartCallback) {
- _paintChartCallback = std::move(paintChartCallback);
- }
- void ChartWidget::Footer::paintEvent(QPaintEvent *e) {
- auto p = QPainter(this);
- auto hq = PainterHighQualityEnabler(p);
- const auto lineWidth = st::lineWidth;
- const auto innerMargins = QMargins{ 0, lineWidth, 0, lineWidth };
- const auto r = rect();
- const auto innerRect = r - innerMargins;
- const auto &inactiveColor = st::statisticsChartInactive;
- _frame.fill(Qt::transparent);
- if (_paintChartCallback) {
- auto q = QPainter(&_frame);
- {
- const auto opacity = q.opacity();
- _paintChartCallback(q, Rect(innerRect.size()));
- q.setOpacity(opacity);
- }
- q.setCompositionMode(QPainter::CompositionMode_DestinationIn);
- q.drawImage(0, 0, _mask);
- }
- p.drawImage(0, lineWidth, _frame);
- auto inactivePath = QPainterPath();
- inactivePath.addRoundedRect(
- innerRect,
- st::statisticsChartFooterSideRadius,
- st::statisticsChartFooterSideRadius);
- auto sidesPath = QPainterPath();
- sidesPath.addRoundedRect(
- _leftSide.min,
- 0,
- _rightSide.max - _leftSide.min,
- r.height(),
- st::statisticsChartFooterSideRadius,
- st::statisticsChartFooterSideRadius);
- inactivePath = inactivePath.subtracted(sidesPath);
- sidesPath.addRect(
- _leftSide.max,
- lineWidth,
- _rightSide.min - _leftSide.max,
- r.height() - lineWidth * 2);
- p.setBrush(st::statisticsChartActive);
- p.setPen(Qt::NoPen);
- p.drawPath(sidesPath);
- p.setBrush(inactiveColor);
- p.drawPath(inactivePath);
- {
- p.setPen(_sidePen);
- const auto halfWidth = st::statisticsChartLineWidth / 2.;
- const auto left = _leftSide.min
- + (_leftSide.max - _leftSide.min) / 2.
- + halfWidth;
- const auto right = _rightSide.min
- + (_rightSide.max - _rightSide.min) / 2.;
- const auto halfHeight = st::statisticsChartFooterArrowHeight / 2.
- - halfWidth;
- const auto center = r.height() / 2.;
- const auto top = center - halfHeight;
- const auto bottom = center + halfHeight;
- p.drawLine(left, top, left, bottom);
- p.drawLine(right, top, right, bottom);
- }
- }
- void ChartWidget::Footer::setXPercentageLimits(const Limits &xLimits) {
- const auto left = xLimits.min * _widthBetweenSides;
- const auto right = xLimits.max * _widthBetweenSides
- + st::statisticsChartFooterSideWidth;
- moveSide(true, left);
- moveSide(false, right);
- fire();
- update();
- }
- rpl::producer<Limits> ChartWidget::Footer::xPercentageLimitsChange() const {
- return _xPercentageLimitsChange.events();
- }
- ChartWidget::ChartAnimationController::ChartAnimationController(
- Fn<void()> &&updateCallback)
- : _animation(std::move(updateCallback)) {
- }
- void ChartWidget::ChartAnimationController::setXPercentageLimits(
- Data::StatisticalChart &chartData,
- Limits xPercentageLimits,
- const std::unique_ptr<AbstractChartView> &chartView,
- const std::shared_ptr<LinesFilterController> &linesFilter,
- crl::time now) {
- if ((_animationValueXMin.to() == xPercentageLimits.min)
- && (_animationValueXMax.to() == xPercentageLimits.max)
- && linesFilter->isFinished()) {
- return;
- }
- start();
- _animationValueXMin.start(xPercentageLimits.min);
- _animationValueXMax.start(xPercentageLimits.max);
- _lastUserInteracted = now;
- const auto startXIndex = chartData.findStartIndex(
- _animationValueXMin.to());
- const auto endXIndex = chartData.findEndIndex(
- startXIndex,
- _animationValueXMax.to());
- _currentXIndices = { float64(startXIndex), float64(endXIndex) };
- {
- const auto heightLimits = chartView->heightLimits(
- chartData,
- _currentXIndices);
- if (heightLimits.ranged.min == heightLimits.ranged.max) {
- return;
- }
- _previousFullHeightLimits = _finalHeightLimits;
- _finalHeightLimits = heightLimits.ranged;
- if (!_previousFullHeightLimits.max) {
- _previousFullHeightLimits = _finalHeightLimits;
- }
- if (!linesFilter->isFinished()) {
- _animationValueFooterHeightMin = anim::value(
- _animationValueFooterHeightMin.current(),
- heightLimits.full.min);
- _animationValueFooterHeightMax = anim::value(
- _animationValueFooterHeightMax.current(),
- heightLimits.full.max);
- } else if (!_animationValueFooterHeightMax.to()) {
- // Will be finished in setChartData.
- _animationValueFooterHeightMin = anim::value(
- 0,
- heightLimits.full.min);
- _animationValueFooterHeightMax = anim::value(
- 0,
- heightLimits.full.max);
- }
- }
- _animationValueHeightMin = anim::value(
- _animationValueHeightMin.current(),
- _finalHeightLimits.min);
- _animationValueHeightMax = anim::value(
- _animationValueHeightMax.current(),
- _finalHeightLimits.max);
- {
- const auto previousDelta = _previousFullHeightLimits.max
- - _previousFullHeightLimits.min;
- auto k = previousDelta
- / float64(_finalHeightLimits.max - _finalHeightLimits.min);
- if (k > 1.) {
- k = 1. / k;
- }
- constexpr auto kDtHeightSpeed1 = 0.03 * 2;
- constexpr auto kDtHeightSpeed2 = 0.03 * 2;
- constexpr auto kDtHeightSpeed3 = 0.045 * 2;
- constexpr auto kDtHeightSpeedFilter = kDtHeightSpeed1 / 1.2;
- constexpr auto kDtHeightSpeedThreshold1 = 0.7;
- constexpr auto kDtHeightSpeedThreshold2 = 0.1;
- constexpr auto kDtHeightInstantThreshold = 0.97;
- if (k < 1.) {
- auto &alpha = _animationValueHeightAlpha;
- alpha = anim::value(
- (alpha.current() == alpha.to()) ? 0. : alpha.current(),
- 1.);
- _dtHeight.currentAlpha = 0.;
- _addRulerRequests.fire({});
- }
- _dtHeight.speed = (!linesFilter->isFinished())
- ? kDtHeightSpeedFilter
- : (k > kDtHeightSpeedThreshold1)
- ? kDtHeightSpeed1
- : (k < kDtHeightSpeedThreshold2)
- ? kDtHeightSpeed2
- : kDtHeightSpeed3;
- if (k < kDtHeightInstantThreshold) {
- _dtHeight.current = { 0., 0. };
- }
- }
- }
- auto ChartWidget::ChartAnimationController::addRulerRequests() const
- -> rpl::producer<> {
- return _addRulerRequests.events();
- }
- void ChartWidget::ChartAnimationController::start() {
- if (!_animation.animating()) {
- _animation.start();
- }
- }
- void ChartWidget::ChartAnimationController::finish() {
- _animation.stop();
- _animationValueXMin.finish();
- _animationValueXMax.finish();
- _animationValueHeightMin.finish();
- _animationValueHeightMax.finish();
- _animationValueFooterHeightMin.finish();
- _animationValueFooterHeightMax.finish();
- _animationValueHeightAlpha.finish();
- _benchmark = {};
- }
- void ChartWidget::ChartAnimationController::restartBottomLineAlpha() {
- _bottomLineAlphaAnimationStartedAt = crl::now();
- _animValueBottomLineAlpha = anim::value(0., 1.);
- start();
- }
- void ChartWidget::ChartAnimationController::tick(
- crl::time now,
- ChartRulersView &rulersView,
- std::vector<BottomCaptionLineData> &dateLines,
- const std::unique_ptr<AbstractChartView> &chartView,
- const std::shared_ptr<LinesFilterController> &linesFilter) {
- if (!_animation.animating()) {
- return;
- }
- constexpr auto kXExpandingDuration = 200.;
- constexpr auto kAlphaExpandingDuration = 200.;
- {
- constexpr auto kIdealFPS = float64(60);
- const auto currentFPS = _benchmark.lastTickedAt
- ? (1000. / (now - _benchmark.lastTickedAt))
- : kIdealFPS;
- if (!_benchmark.lastFPSSlow) {
- constexpr auto kAcceptableFPS = int(30);
- _benchmark.lastFPSSlow = (currentFPS < kAcceptableFPS);
- }
- _benchmark.lastTickedAt = now;
- const auto k = (kIdealFPS / currentFPS)
- // Speed up to reduce ugly frames count.
- * (_benchmark.lastFPSSlow ? 2. : 1.);
- const auto speed = _dtHeight.speed * k;
- linesFilter->tick(speed);
- _dtHeight.current.min = std::min(_dtHeight.current.min + speed, 1.);
- _dtHeight.current.max = std::min(_dtHeight.current.max + speed, 1.);
- _dtHeight.currentAlpha = std::min(_dtHeight.currentAlpha + speed, 1.);
- }
- const auto dtX = std::min(
- (now - _animation.started()) / kXExpandingDuration,
- 1.);
- const auto dtBottomLineAlpha = std::min(
- (now - _bottomLineAlphaAnimationStartedAt) / kAlphaExpandingDuration,
- 1.);
- const auto isFinished = [](const anim::value &anim) {
- return anim.current() == anim.to();
- };
- const auto xFinished = isFinished(_animationValueXMin)
- && isFinished(_animationValueXMax);
- const auto yFinished = isFinished(_animationValueHeightMin)
- && isFinished(_animationValueHeightMax);
- const auto alphaFinished = isFinished(_animationValueHeightAlpha)
- && isFinished(_animationValueHeightMax);
- const auto bottomLineAlphaFinished = isFinished(
- _animValueBottomLineAlpha);
- const auto footerMinFinished = isFinished(_animationValueFooterHeightMin);
- const auto footerMaxFinished = isFinished(_animationValueFooterHeightMax);
- if (xFinished
- && yFinished
- && alphaFinished
- && bottomLineAlphaFinished
- && footerMinFinished
- && footerMaxFinished
- && linesFilter->isFinished()) {
- if ((_finalHeightLimits.min == _animationValueHeightMin.to())
- && _finalHeightLimits.max == _animationValueHeightMax.to()) {
- _animation.stop();
- _benchmark = {};
- }
- }
- if (xFinished) {
- _animationValueXMin.finish();
- _animationValueXMax.finish();
- } else {
- _animationValueXMin.update(dtX, anim::linear);
- _animationValueXMax.update(dtX, anim::linear);
- }
- if (bottomLineAlphaFinished) {
- _animValueBottomLineAlpha.finish();
- _bottomLineAlphaAnimationStartedAt = 0;
- } else {
- _animValueBottomLineAlpha.update(
- dtBottomLineAlpha,
- anim::easeInCubic);
- }
- if (!yFinished) {
- _animationValueHeightMin.update(
- _dtHeight.current.min,
- anim::easeInCubic);
- _animationValueHeightMax.update(
- _dtHeight.current.max,
- anim::easeInCubic);
- rulersView.computeRelative(
- _animationValueHeightMax.current(),
- _animationValueHeightMin.current());
- }
- if (!footerMinFinished) {
- _animationValueFooterHeightMin.update(
- _dtHeight.current.min,
- anim::easeInCubic);
- }
- if (!footerMaxFinished) {
- _animationValueFooterHeightMax.update(
- _dtHeight.current.max,
- anim::easeInCubic);
- }
- if (!alphaFinished) {
- _animationValueHeightAlpha.update(
- _dtHeight.currentAlpha,
- anim::easeInCubic);
- rulersView.setAlpha(_animationValueHeightAlpha.current());
- }
- if (!bottomLineAlphaFinished) {
- const auto value = _animValueBottomLineAlpha.current();
- for (auto &date : dateLines) {
- date.alpha = (1. - value) * date.fixedAlpha;
- }
- dateLines.back().alpha = value;
- } else {
- if (dateLines.size() > 1) {
- const auto data = dateLines.back();
- dateLines.clear();
- dateLines.push_back(data);
- }
- }
- }
- Limits ChartWidget::ChartAnimationController::currentXLimits() const {
- return { _animationValueXMin.current(), _animationValueXMax.current() };
- }
- Limits ChartWidget::ChartAnimationController::currentXIndices() const {
- return _currentXIndices;
- }
- Limits ChartWidget::ChartAnimationController::finalXLimits() const {
- return { _animationValueXMin.to(), _animationValueXMax.to() };
- }
- Limits ChartWidget::ChartAnimationController::currentHeightLimits() const {
- return {
- _animationValueHeightMin.current(),
- _animationValueHeightMax.current(),
- };
- }
- auto ChartWidget::ChartAnimationController::currentFooterHeightLimits() const
- -> Limits {
- return {
- _animationValueFooterHeightMin.current(),
- _animationValueFooterHeightMax.current(),
- };
- }
- Limits ChartWidget::ChartAnimationController::finalHeightLimits() const {
- return _finalHeightLimits;
- }
- bool ChartWidget::ChartAnimationController::animating() const {
- return _animation.animating();
- }
- bool ChartWidget::ChartAnimationController::footerAnimating() const {
- return (_animationValueFooterHeightMin.current()
- != _animationValueFooterHeightMin.to())
- || (_animationValueFooterHeightMax.current()
- != _animationValueFooterHeightMax.to());
- }
- ChartWidget::ChartWidget(not_null<Ui::RpWidget*> parent)
- : Ui::RpWidget(parent)
- , _chartArea(base::make_unique_q<RpMouseWidget>(this))
- , _header(std::make_unique<Header>(this))
- , _footer(std::make_unique<Footer>(this))
- , _linesFilterController(std::make_shared<LinesFilterController>())
- , _animationController([=] {
- _chartArea->update();
- if (_animationController.footerAnimating()
- || !_linesFilterController->isFinished()) {
- _footer->update();
- }
- }) {
- style::PaletteChanged(
- ) | rpl::start_with_next([=] {
- if (_chartData) {
- FillLineColorsByKey(_chartData);
- }
- }, lifetime());
- setupChartArea();
- // We have to create the footer,
- // even if it has to be hidden, otherwise it breaks some things.
- setupFooter();
- }
- int ChartWidget::resizeGetHeight(int newWidth) {
- if (newWidth <= 0) {
- return 0;
- }
- if (_filterButtons) {
- _filterButtons->resizeToWidth(newWidth);
- }
- const auto filtersTopSkip = st::statisticsFilterButtonsPadding.top();
- const auto filtersHeight = _filterButtons
- ? (_filterButtons->height()
- + st::statisticsFilterButtonsPadding.bottom())
- : 0;
- const auto &headerPadding = st::statisticsChartHeaderPadding;
- {
- _header->moveToLeft(headerPadding.left(), headerPadding.top());
- _header->resizeToWidth(newWidth - rect::m::sum::h(headerPadding));
- }
- const auto headerArea = rect::m::sum::v(headerPadding)
- + _header->height();
- const auto footerArea = (!_footer->isHidden())
- ? (st::statisticsChartFooterHeight + st::statisticsChartFooterSkip)
- : 0;
- const auto resultHeight = headerArea
- + st::statisticsChartHeight
- + footerArea
- + filtersTopSkip
- + filtersHeight;
- {
- if (footerArea) {
- _footer->resizeToWidth(newWidth);
- _footer->moveToLeft(
- 0,
- resultHeight
- - st::statisticsChartFooterHeight
- - filtersTopSkip
- - filtersHeight);
- }
- if (_filterButtons) {
- _filterButtons->moveToLeft(0, resultHeight - filtersHeight);
- }
- _chartArea->setGeometry(
- 0,
- headerArea,
- newWidth,
- resultHeight
- - headerArea
- - footerArea
- - filtersTopSkip
- - filtersHeight);
- {
- updateChartFullWidth(newWidth);
- updateBottomDates();
- }
- }
- return resultHeight;
- }
- void ChartWidget::updateChartFullWidth(int w) {
- const auto finalXLimits = _animationController.finalXLimits();
- _bottomLine.chartFullWidth = (finalXLimits.max == finalXLimits.min)
- ? 0
- : (w / (finalXLimits.max - finalXLimits.min));
- }
- QRect ChartWidget::chartAreaRect() const {
- return _chartArea->rect()
- - QMargins(
- st::lineWidth,
- st::boxTextFont->height,
- st::lineWidth,
- st::lineWidth
- + st::statisticsChartBottomCaptionHeight
- + st::statisticsChartBottomCaptionSkip);
- }
- void ChartWidget::setupChartArea() {
- _chartArea->paintRequest(
- ) | rpl::start_with_next([=](const QRect &r) {
- auto p = QPainter(_chartArea.get());
- const auto now = crl::now();
- _animationController.tick(
- now,
- _rulersView,
- _bottomLine.dates,
- _chartView,
- _linesFilterController);
- const auto chartRect = chartAreaRect();
- p.fillRect(r, st::boxBg);
- if (!_chartData) {
- return;
- }
- if (!_areRulersAbove) {
- _rulersView.paintRulers(p, chartRect);
- }
- const auto context = PaintContext{
- _chartData,
- _animationController.currentXIndices(),
- _animationController.currentXLimits(),
- _animationController.currentHeightLimits(),
- chartRect,
- false,
- };
- {
- PainterHighQualityEnabler hp(p);
- _chartView->paint(p, context);
- }
- if (!_areRulersAbove) {
- _rulersView.paintCaptionsToRulers(p, chartRect);
- }
- {
- [[maybe_unused]] const auto o = ScopedPainterOpacity(
- p,
- p.opacity() * kRulerLineAlpha);
- const auto bottom = rect()
- - QMargins{ 0, rect::bottom(chartRect), 0, 0 };
- p.fillRect(bottom, st::boxBg);
- p.fillRect(
- QRect(bottom.x(), bottom.y(), bottom.width(), st::lineWidth),
- st::boxTextFg);
- }
- if (_details.widget) {
- const auto detailsAlpha = _details.widget->alpha();
- for (const auto &line : _chartData.lines) {
- _details.widget->setLineAlpha(
- line.id,
- _linesFilterController->alpha(line.id));
- }
- _chartView->paintSelectedXIndex(
- p,
- context,
- _details.widget->xIndex(),
- detailsAlpha);
- }
- if (_areRulersAbove) {
- _rulersView.paintRulers(p, chartRect);
- _rulersView.paintCaptionsToRulers(p, chartRect);
- }
- p.setPen(st::windowSubTextFg);
- PaintBottomLine(
- p,
- _bottomLine.dates,
- _chartData,
- _animationController.finalXLimits(),
- _bottomLine.chartFullWidth,
- _chartArea->width(),
- rect::bottom(chartRect) + st::statisticsChartBottomCaptionSkip,
- _bottomLine.captionIndicesOffset);
- }, _footer->lifetime());
- }
- void ChartWidget::updateBottomDates() {
- if (!_chartData || !_bottomLine.chartFullWidth) {
- return;
- }
- const auto d = _bottomLine.chartFullWidth * _chartData.oneDayPercentage;
- const auto k = _chartArea->width() / d;
- const auto stepRaw = int(k / 6);
- const auto by = int(_chartArea->width() / float64(_chartData.x.size()));
- _bottomLine.captionIndicesOffset = 0
- + _chartData.dayStringMaxWidth / std::max(by, 1);
- const auto isCurrentNull = (_bottomLine.current.stepMinFast == 0);
- if (!isCurrentNull
- && (stepRaw < _bottomLine.current.stepMax)
- && (stepRaw > _bottomLine.current.stepMin)) {
- return;
- }
- const auto highestOneBit = [](unsigned int v) {
- if (!v) {
- return 0;
- }
- auto r = unsigned(1);
- while (v >>= 1) {
- r *= 2;
- }
- return int(r);
- };
- const auto step = highestOneBit(stepRaw) << 1;
- if (!isCurrentNull && (_bottomLine.current.step == step)) {
- return;
- }
- constexpr auto kStepRatio = 0.1;
- constexpr auto kFastStepOffset = 4;
- const auto stepMax = int(step + step * kStepRatio);
- const auto stepMin = int(step - step * kStepRatio);
- const auto stepMinFast = stepMin - kFastStepOffset;
- auto data = BottomCaptionLineData{
- .step = step,
- .stepMax = stepMax,
- .stepMin = stepMin,
- .stepMinFast = stepMinFast,
- .stepRaw = stepRaw,
- .alpha = 1.,
- };
- if (isCurrentNull) {
- _bottomLine.current = data;
- _bottomLine.dates.push_back(data);
- return;
- }
- _bottomLine.current = data;
- for (auto &date : _bottomLine.dates) {
- date.fixedAlpha = date.alpha;
- }
- _bottomLine.dates.push_back(data);
- if (_bottomLine.dates.size() > 2) {
- _bottomLine.dates.erase(begin(_bottomLine.dates));
- }
- _animationController.restartBottomLineAlpha();
- }
- void ChartWidget::updateHeader() {
- if (!_chartData) {
- return;
- }
- const auto i = _animationController.currentXIndices();
- _header->setSubTitle(HeaderSubTitle(_chartData, i.min, i.max));
- _header->update();
- }
- void ChartWidget::setupFooter() {
- _footer->setPaintChartCallback([=, fullXLimits = Limits{ 0., 1. }](
- QPainter &p,
- const QRect &r) {
- if (_chartData) {
- p.fillRect(r, st::boxBg);
- auto hp = PainterHighQualityEnabler(p);
- _chartView->paint(
- p,
- PaintContext{
- _chartData,
- { 0., float64(_chartData.x.size() - 1) },
- fullXLimits,
- _animationController.currentFooterHeightLimits(),
- r,
- true,
- });
- }
- });
- _animationController.addRulerRequests(
- ) | rpl::start_with_next([=] {
- _rulersView.add(
- _animationController.finalHeightLimits(),
- true);
- _animationController.start();
- }, _footer->lifetime());
- _footer->xPercentageLimitsChange(
- ) | rpl::start_with_next([=](Limits xPercentageLimits) {
- if (!_chartView) {
- return;
- }
- const auto now = crl::now();
- if (_details.widget
- && (_details.widget->xIndex() >= 0)
- && !_details.animation.animating()) {
- _details.hideOnAnimationEnd = true;
- _details.animation.start();
- }
- _animationController.setXPercentageLimits(
- _chartData,
- xPercentageLimits,
- _chartView,
- _linesFilterController,
- now);
- updateChartFullWidth(_chartArea->width());
- updateBottomDates();
- updateHeader();
- if ((now - _lastHeightLimitsChanged) < kHeightLimitsUpdateTimeout) {
- return;
- }
- _lastHeightLimitsChanged = now;
- _rulersView.add(
- _animationController.finalHeightLimits(),
- true);
- }, _footer->lifetime());
- }
- void ChartWidget::setupDetails() {
- if (!_chartData) {
- _details.widget = nullptr;
- _chartArea->update();
- return;
- }
- if (hasLocalZoom()) {
- _zoomEnabled = true;
- }
- _details.widget = base::make_unique_q<PointDetailsWidget>(
- this,
- _chartData,
- _zoomEnabled);
- _details.widget->setClickedCallback([=] {
- const auto index = _details.widget->xIndex();
- if (index < 0) {
- return;
- }
- if (hasLocalZoom()) {
- processLocalZoom(index);
- } else {
- _zoomRequests.fire_copy(_chartData.x[index]);
- }
- });
- _details.widget->shownValue(
- ) | rpl::start_with_next([=](bool shown) {
- if (shown && _details.widget->xIndex() < 0) {
- _details.widget->hide();
- }
- }, _details.widget->lifetime());
- _chartArea->mouseStateChanged(
- ) | rpl::start_with_next([=](const RpMouseWidget::State &state) {
- if (_animationController.animating()) {
- return;
- }
- switch (state.mouseState) {
- case QEvent::MouseButtonPress:
- case QEvent::MouseMove: {
- const auto wasXIndex = _details.widget->xIndex();
- const auto chartRect = chartAreaRect();
- const auto currentXLimits = _animationController.finalXLimits();
- const auto nearestXIndex = _chartView->findXIndexByPosition(
- _chartData,
- currentXLimits,
- chartRect,
- state.point.x());
- if (nearestXIndex < 0) {
- _details.widget->setXIndex(nearestXIndex);
- _details.widget->hide();
- _chartArea->update();
- return;
- }
- const auto currentX = 0
- + chartRect.width() * InterpolationRatio(
- currentXLimits.min,
- currentXLimits.max,
- _chartData.xPercentage[nearestXIndex]);
- const auto widgetArea = _details.widget->width()
- + st::statisticsDetailsPopupPadding.left();
- const auto xLeft = currentX - widgetArea;
- const auto x = (xLeft >= 0)
- ? xLeft
- : ((currentX + widgetArea - _chartArea->width()) > 0)
- ? 0
- : currentX;
- _details.widget->moveToLeft(
- std::clamp(
- int(x),
- _chartArea->x(),
- rect::right(_chartArea) - widgetArea),
- _chartArea->y());
- _details.widget->setXIndex(nearestXIndex);
- if (_details.widget->isHidden()) {
- _details.hideOnAnimationEnd = false;
- _details.animation.start();
- } else if ((state.mouseState == QEvent::MouseButtonPress)
- && (wasXIndex == nearestXIndex)) {
- _details.hideOnAnimationEnd = true;
- _details.animation.start();
- }
- _details.widget->show();
- _chartArea->update();
- } break;
- case QEvent::MouseButtonRelease: {
- } break;
- }
- }, _details.widget->lifetime());
- _details.animation.init([=](crl::time now) {
- const auto value = std::clamp(
- (now - _details.animation.started()) / float64(200),
- 0.,
- 1.);
- const auto alpha = _details.hideOnAnimationEnd ? (1. - value) : value;
- if (_details.widget) {
- _details.widget->setAlpha(alpha);
- _details.widget->update();
- }
- if (value >= 1.) {
- if (_details.hideOnAnimationEnd && _details.widget) {
- _details.widget->hide();
- _details.widget->setXIndex(-1);
- }
- _details.animation.stop();
- }
- _chartArea->update();
- });
- }
- 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);
- if (_footer->isHidden()) {
- return;
- }
- const auto wasZoom = _footer->xPercentageLimits();
- const auto header = Ui::CreateChild<Header>(this);
- header->show();
- _header->geometryValue(
- ) | rpl::start_with_next([=](const QRect &g) {
- header->setGeometry(g);
- }, header->lifetime());
- header->setTitle(_header->title());
- header->setSubTitle(HeaderSubTitle(_chartData, xIndex, xIndex));
- const auto enableMouse = [=](bool value) {
- setAttribute(Qt::WA_TransparentForMouseEvents, !value);
- };
- const auto mouseTrackingLifetime = std::make_shared<rpl::lifetime>();
- _chartView->setUpdateCallback([=] { _chartArea->update(); });
- const auto createMouseTracking = [=] {
- _chartArea->setMouseTracking(true);
- *mouseTrackingLifetime = _chartArea->events(
- ) | rpl::filter([](not_null<QEvent*> event) {
- return (event->type() == QEvent::MouseMove)
- || (event->type() == QEvent::Leave);
- }) | rpl::start_with_next([=](not_null<QEvent*> event) {
- auto pos = QPoint();
- if (event->type() == QEvent::MouseMove) {
- const auto e = static_cast<QMouseEvent*>(event.get());
- pos = e->pos();
- }
- _chartView->handleMouseMove(_chartData, _chartArea->rect(), pos);
- });
- mouseTrackingLifetime->add(crl::guard(_chartArea.get(), [=] {
- _chartArea->setMouseTracking(false);
- }));
- };
- const auto zoomOutButton = Ui::CreateChild<Ui::RoundButton>(
- header,
- tr::lng_stats_zoom_out(),
- st::statisticsHeaderButton);
- zoomOutButton->moveToRight(
- 0,
- (header->height() - zoomOutButton->height()) / 2);
- zoomOutButton->show();
- zoomOutButton->setTextTransform(
- Ui::RoundButton::TextTransform::NoTransform);
- zoomOutButton->setClickedCallback([=] {
- auto lifetime = std::make_shared<rpl::lifetime>();
- const auto animation = lifetime->make_state<Ui::Animations::Simple>();
- 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();
- }
- mouseTrackingLifetime->destroy();
- enableMouse(true);
- }
- }, 1., 0., kFooterZoomDuration, anim::easeOutCirc);
- enableMouse(false);
- Ui::Animations::HideWidgets({ header });
- });
- Ui::Animations::ShowWidgets({ header });
- const auto finish = [=](const Limits &zoomLimitIndices) {
- createMouseTracking();
- _footer->xPercentageLimitsChange(
- ) | rpl::start_with_next([=](const Limits &l) {
- const auto r = FindStackXIndicesFromRawXPercentages(
- _chartData,
- l,
- zoomLimitIndices);
- header->setSubTitle(HeaderSubTitle(_chartData, r.min, r.max));
- header->update();
- }, header->lifetime());
- };
- {
- auto lifetime = std::make_shared<rpl::lifetime>();
- const auto animation = lifetime->make_state<Ui::Animations::Simple>();
- _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.limitIndices);
- enableMouse(true);
- }
- }, 0., 1., kFooterZoomDuration, anim::easeOutCirc);
- enableMouse(false);
- }
- }
- void ChartWidget::setupFilterButtons() {
- if (!_chartData || (_chartData.lines.size() <= 1)) {
- _filterButtons = nullptr;
- _chartArea->update();
- return;
- }
- _filterButtons = base::make_unique_q<ChartLinesFilterWidget>(this);
- _filterButtons->show();
- {
- auto data = std::vector<ChartLinesFilterWidget::ButtonData>();
- data.reserve(_chartData.lines.size());
- for (const auto &line : _chartData.lines) {
- data.push_back({
- line.name,
- line.color,
- line.id,
- line.isHiddenOnStart,
- });
- if (line.isHiddenOnStart) {
- _linesFilterController->setEnabled(line.id, false, 1);
- }
- }
- _filterButtons->fillButtons(data);
- _linesFilterController->tick(1.);
- }
- _filterButtons->buttonEnabledChanges(
- ) | rpl::start_with_next([=](const ChartLinesFilterWidget::Entry &e) {
- const auto now = crl::now();
- _linesFilterController->setEnabled(e.id, e.enabled, now);
- _animationController.setXPercentageLimits(
- _chartData,
- _animationController.currentXLimits(),
- _chartView,
- _linesFilterController,
- now);
- }, _filterButtons->lifetime());
- }
- void ChartWidget::setChartData(
- Data::StatisticalChart chartData,
- ChartViewType type) {
- if (width() < st::statisticsChartHeight) {
- sizeValue(
- ) | rpl::start_with_next([=](const QSize &s) {
- if (s.width() > st::statisticsChartHeight) {
- setChartData(chartData, type);
- _waitingSizeLifetime.destroy();
- }
- }, _waitingSizeLifetime);
- return;
- }
- if (_chartData) {
- // We don't really support a replacement of chart data in runtime.
- return;
- }
- _chartData = std::move(chartData);
- FillLineColorsByKey(_chartData);
- _chartView = CreateChartView(type);
- _chartView->setLinesFilterController(_linesFilterController);
- _rulersView.setChartData(_chartData, type, _linesFilterController);
- _areRulersAbove = (type == ChartViewType::StackBar);
- if (_chartData.isFooterHidden) {
- _footer->hide();
- }
- setupDetails();
- setupFilterButtons();
- const auto defaultZoom = Limits{
- _chartData.xPercentage[_chartData.defaultZoomXIndex.min],
- _chartData.xPercentage[_chartData.defaultZoomXIndex.max],
- };
- _footer->setXPercentageLimits(defaultZoom);
- _animationController.setXPercentageLimits(
- _chartData,
- defaultZoom,
- _chartView,
- _linesFilterController,
- 0);
- updateHeader();
- _animationController.finish();
- _rulersView.add(_animationController.finalHeightLimits(), false);
- _chartArea->update();
- _footer->update();
- RpWidget::resizeToWidth(width());
- }
- void ChartWidget::setTitle(rpl::producer<QString> &&title) {
- std::move(
- title
- ) | rpl::start_with_next([=](QString t) {
- _header->setTitle(std::move(t));
- _header->update();
- }, _header->lifetime());
- }
- void ChartWidget::setZoomedChartData(
- Data::StatisticalChart chartData,
- float64 x,
- ChartViewType type) {
- _zoomedChartWidget = base::make_unique_q<ChartWidget>(
- dynamic_cast<Ui::RpWidget*>(parentWidget()));
- geometryValue(
- ) | rpl::start_with_next([=](const QRect &geometry) {
- _zoomedChartWidget->moveToLeft(geometry.x(), geometry.y());
- }, _zoomedChartWidget->lifetime());
- _zoomedChartWidget->show();
- _zoomedChartWidget->resizeToWidth(width());
- _zoomedChartWidget->setChartData(std::move(chartData), type);
- const auto customHeader = Ui::CreateChild<Header>(
- _zoomedChartWidget.get());
- {
- const auto xIndex = std::distance(
- begin(_chartData.x),
- ranges::find(_chartData.x, x));
- customHeader->setTitle(_header->title());
- if ((xIndex >= 0) && (xIndex < _chartData.x.size())) {
- customHeader->setSubTitle(
- HeaderSubTitle(_chartData, xIndex, xIndex));
- }
- const auto &headerPadding = st::statisticsChartHeaderPadding;
- customHeader->moveToLeft(headerPadding.left(), headerPadding.top());
- customHeader->resizeToWidth(width() - rect::m::sum::h(headerPadding));
- }
- const auto zoomOutButton = Ui::CreateChild<Ui::RoundButton>(
- customHeader,
- tr::lng_stats_zoom_out(),
- st::statisticsHeaderButton);
- zoomOutButton->setTextTransform(
- Ui::RoundButton::TextTransform::NoTransform);
- zoomOutButton->moveToRight(
- 0,
- (customHeader->height() - zoomOutButton->height()) / 2);
- zoomOutButton->setClickedCallback([=] {
- shownValue(
- ) | rpl::start_with_next([=](bool shown) {
- if (shown) {
- _zoomedChartWidget = nullptr;
- }
- }, _zoomedChartWidget->lifetime());
- Ui::Animations::ShowWidgets({ this });
- Ui::Animations::HideWidgets({ _zoomedChartWidget.get() });
- });
- Ui::Animations::ShowWidgets({ _zoomedChartWidget.get(), customHeader });
- Ui::Animations::HideWidgets({ this });
- }
- rpl::producer<float64> ChartWidget::zoomRequests() {
- _zoomEnabled = true;
- setupDetails();
- return _zoomRequests.events();
- }
- } // namespace Statistic
|