diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp index a88725f0e..bbd05b98f 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp @@ -189,11 +189,11 @@ void FillStatistic( addChart( stats.channel.viewCountBySourceGraph, tr::lng_chart_title_view_count_by_source(), - Type::Stack); + Type::StackBar); addChart( stats.channel.joinBySourceGraph, tr::lng_chart_title_join_by_source(), - Type::Stack); + Type::StackBar); addChart( stats.channel.languageGraph, tr::lng_chart_title_language(), @@ -218,7 +218,7 @@ void FillStatistic( addChart( stats.supergroup.joinBySourceGraph, tr::lng_chart_title_group_join_by_source(), - Type::Stack); + Type::StackBar); addChart( stats.supergroup.languageGraph, tr::lng_chart_title_group_language(), @@ -226,7 +226,7 @@ void FillStatistic( addChart( stats.supergroup.messageContentGraph, tr::lng_chart_title_group_message_content(), - Type::Stack); + Type::StackBar); addChart( stats.supergroup.actionGraph, tr::lng_chart_title_group_action(), diff --git a/Telegram/SourceFiles/statistics/chart_widget.cpp b/Telegram/SourceFiles/statistics/chart_widget.cpp index cead2c80f..e3e57796c 100644 --- a/Telegram/SourceFiles/statistics/chart_widget.cpp +++ b/Telegram/SourceFiles/statistics/chart_widget.cpp @@ -1462,7 +1462,7 @@ void ChartWidget::setChartData( _chartView = CreateChartView(type); _chartView->setLinesFilterController(_linesFilterController); _rulersView.setChartData(_chartData, type, _linesFilterController); - _areRulersAbove = (type == ChartViewType::Stack); + _areRulersAbove = (type == ChartViewType::StackBar); if (_chartData.isFooterHidden) { _footer->hide(); diff --git a/Telegram/SourceFiles/statistics/statistics_common.h b/Telegram/SourceFiles/statistics/statistics_common.h index a37b0e873..68e50d8d8 100644 --- a/Telegram/SourceFiles/statistics/statistics_common.h +++ b/Telegram/SourceFiles/statistics/statistics_common.h @@ -18,7 +18,8 @@ struct Limits final { enum class ChartViewType { Linear, - Stack, + Bar, + StackBar, DoubleLinear, StackLinear, }; diff --git a/Telegram/SourceFiles/statistics/view/bar_chart_view.cpp b/Telegram/SourceFiles/statistics/view/bar_chart_view.cpp index 0b410c163..603d7efa1 100644 --- a/Telegram/SourceFiles/statistics/view/bar_chart_view.cpp +++ b/Telegram/SourceFiles/statistics/view/bar_chart_view.cpp @@ -12,10 +12,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "statistics/view/stack_chart_common.h" #include "ui/effects/animation_value_f.h" #include "ui/painter.h" +#include "ui/rect.h" +#include "styles/style_statistics.h" namespace Statistic { -BarChartView::BarChartView() = default; +BarChartView::BarChartView(bool isStack) +: _isStack(isStack) +, _cachedLineRatios(false) { +} + BarChartView::~BarChartView() = default; void BarChartView::paint(QPainter &p, const PaintContext &c) { @@ -40,6 +46,8 @@ void BarChartView::paintChartAndSelected( c.rect, localStart); + p.setClipRect(0, 0, c.rect.width() * 2, rect::bottom(c.rect)); + const auto opacity = p.opacity(); auto hq = PainterHighQualityEnabler(p); @@ -47,7 +55,9 @@ void BarChartView::paintChartAndSelected( localEnd - localStart + 1, -c.rect.y()); auto selectedBottoms = std::vector(); - const auto hasSelectedXIndex = !c.footer && (_lastSelectedXIndex >= 0); + const auto hasSelectedXIndex = _isStack + && !c.footer + && (_lastSelectedXIndex >= 0); if (hasSelectedXIndex) { selectedBottoms = std::vector(c.chartData.lines.size(), 0); constexpr auto kSelectedAlpha = 0.5; @@ -77,10 +87,27 @@ void BarChartView::paintChartAndSelected( if (hasSelectedXIndex && (x == _lastSelectedXIndex)) { selectedBottoms[i] = column.y(); } - path.addRect(column); - bottoms[bottomIndex] += yPoint; + if (_isStack) { + path.addRect(column); + bottoms[bottomIndex] += yPoint; + } else { + if (path.isEmpty()) { + path.moveTo(column.topLeft()); + } else { + path.lineTo(column.topLeft()); + } + if (x == localEnd) { + path.lineTo(c.rect.width(), column.y()); + } else { + path.lineTo(rect::right(column), column.y()); + } + } + } + if (_isStack) { + p.fillPath(path, line.color); + } else { + p.strokePath(path, line.color); } - p.fillPath(path, line.color); } for (auto i = 0; i < selectedBottoms.size(); i++) { @@ -103,6 +130,8 @@ void BarChartView::paintChartAndSelected( yPoint); p.fillRect(column, line.color); } + + p.setClipping(false); } void BarChartView::paintSelectedXIndex( @@ -113,8 +142,73 @@ void BarChartView::paintSelectedXIndex( const auto was = _lastSelectedXIndex; _lastSelectedXIndex = selectedXIndex; _lastSelectedXProgress = progress; - if ((_lastSelectedXIndex >= 0) || (was >= 0)) { - BarChartView::paintChartAndSelected(p, c); + + if (_isStack) { + if ((_lastSelectedXIndex >= 0) || (was >= 0)) { + BarChartView::paintChartAndSelected(p, c); + } + } else { + const auto linesFilter = linesFilterController(); + auto hq = PainterHighQualityEnabler(p); + auto o = ScopedPainterOpacity(p, progress); + p.setBrush(st::boxBg); + const auto r = st::statisticsDetailsDotRadius; + const auto i = selectedXIndex; + const auto isSameToken = _selectedPoints.isSame(selectedXIndex, c); + auto linePainted = false; + + const auto &[localStart, localEnd] = _lastPaintedXIndices; + const auto &[leftStart, w] = ComputeLeftStartAndStep( + c.chartData, + c.xPercentageLimits, + c.rect, + localStart); + + for (auto i = 0; i < c.chartData.lines.size(); i++) { + const auto &line = c.chartData.lines[i]; + const auto lineAlpha = linesFilter->alpha(line.id); + const auto useCache = isSameToken + || (lineAlpha < 1. && !linesFilter->isEnabled(line.id)); + if (!useCache) { + // Calculate. + const auto x = _lastSelectedXIndex; + const auto yPercentage = (line.y[x] - c.heightLimits.min) + / float64(c.heightLimits.max - c.heightLimits.min); + const auto yPoint = (1. - yPercentage) * c.rect.height(); + + const auto bottomIndex = x - localStart; + const auto column = QRectF( + leftStart + (x - localStart) * w, + c.rect.height() - 0 - yPoint, + w, + yPoint); + const auto xPoint = column.left() + column.width() / 2.; + _selectedPoints.points[line.id] = QPointF(xPoint, yPoint) + + c.rect.topLeft(); + } + + if (!linePainted && lineAlpha) { + [[maybe_unused]] const auto o = ScopedPainterOpacity( + p, + p.opacity() * progress * kRulerLineAlpha); + const auto lineRect = QRectF( + begin(_selectedPoints.points)->second.x() + - (st::lineWidth / 2.), + c.rect.y(), + st::lineWidth, + c.rect.height()); + p.fillRect(lineRect, st::boxTextFg); + linePainted = true; + } + + // Paint. + auto o = ScopedPainterOpacity(p, lineAlpha * p.opacity()); + p.setPen(QPen(line.color, st::statisticsChartLineWidth)); + p.drawEllipse(_selectedPoints.points[line.id], r, r); + } + _selectedPoints.lastXIndex = selectedXIndex; + _selectedPoints.lastHeightLimits = c.heightLimits; + _selectedPoints.lastXLimits = c.xPercentageLimits; } } @@ -147,6 +241,17 @@ int BarChartView::findXIndexByPosition( AbstractChartView::HeightLimits BarChartView::heightLimits( Data::StatisticalChart &chartData, Limits xIndices) { + if (!_isStack) { + if (!_cachedLineRatios) { + _cachedLineRatios.init(chartData); + } + + return DefaultHeightLimits( + _cachedLineRatios, + linesFilterController(), + chartData, + xIndices); + } _cachedHeightLimits = {}; if (_cachedHeightLimits.ySum.empty()) { _cachedHeightLimits.ySum.reserve(chartData.x.size()); diff --git a/Telegram/SourceFiles/statistics/view/bar_chart_view.h b/Telegram/SourceFiles/statistics/view/bar_chart_view.h index a274c2f75..44c1a7a6e 100644 --- a/Telegram/SourceFiles/statistics/view/bar_chart_view.h +++ b/Telegram/SourceFiles/statistics/view/bar_chart_view.h @@ -22,7 +22,7 @@ struct Limits; class BarChartView final : public AbstractChartView { public: - BarChartView(); + BarChartView(bool isStack); ~BarChartView() override final; void paint(QPainter &p, const PaintContext &c) override; @@ -52,10 +52,14 @@ private: SegmentTree ySumSegmentTree; } _cachedHeightLimits; + const bool _isStack; + DoubleLineRatios _cachedLineRatios; // Non-stack. Limits _lastPaintedXIndices; int _lastSelectedXIndex = -1; float64 _lastSelectedXProgress = 0; + CachedSelectedPoints _selectedPoints; // Non-stack. + }; } // namespace Statistic diff --git a/Telegram/SourceFiles/statistics/view/chart_view_factory.cpp b/Telegram/SourceFiles/statistics/view/chart_view_factory.cpp index 1a7566c00..789369d66 100644 --- a/Telegram/SourceFiles/statistics/view/chart_view_factory.cpp +++ b/Telegram/SourceFiles/statistics/view/chart_view_factory.cpp @@ -19,8 +19,11 @@ std::unique_ptr CreateChartView(ChartViewType type) { case ChartViewType::Linear: { return std::make_unique(false); } break; - case ChartViewType::Stack: { - return std::make_unique(); + case ChartViewType::Bar: { + return std::make_unique(false); + } break; + case ChartViewType::StackBar: { + return std::make_unique(true); } break; case ChartViewType::DoubleLinear: { return std::make_unique(true);