| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- /*
- 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/bar_chart_view.h"
- #include "data/data_statistics_chart.h"
- #include "statistics/chart_lines_filter_controller.h"
- #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(bool isStack)
- : _isStack(isStack)
- , _cachedLineRatios(false) {
- }
- BarChartView::~BarChartView() = default;
- void BarChartView::paint(QPainter &p, const PaintContext &c) {
- constexpr auto kOffset = float64(2);
- _lastPaintedXIndices = {
- float64(std::max(0., c.xIndices.min - kOffset)),
- float64(std::min(
- float64(c.chartData.xPercentage.size() - 1),
- c.xIndices.max + kOffset)),
- };
- BarChartView::paintChartAndSelected(p, c);
- }
- void BarChartView::paintChartAndSelected(
- QPainter &p,
- const PaintContext &c) {
- const auto &[localStart, localEnd] = _lastPaintedXIndices;
- const auto &[leftStart, w] = ComputeLeftStartAndStep(
- c.chartData,
- c.xPercentageLimits,
- c.rect,
- localStart);
- p.setClipRect(0, 0, c.rect.width() * 2, rect::bottom(c.rect));
- const auto opacity = p.opacity();
- auto hq = PainterHighQualityEnabler(p);
- auto bottoms = std::vector<float64>(
- localEnd - localStart + 1,
- -c.rect.y());
- auto selectedBottoms = std::vector<float64>();
- const auto hasSelectedXIndex = _isStack
- && !c.footer
- && (_lastSelectedXIndex >= 0);
- if (hasSelectedXIndex) {
- selectedBottoms = std::vector<float64>(c.chartData.lines.size(), 0);
- constexpr auto kSelectedAlpha = 0.5;
- p.setOpacity(
- anim::interpolateF(1.0, kSelectedAlpha, _lastSelectedXProgress));
- }
- for (auto i = 0; i < c.chartData.lines.size(); i++) {
- const auto &line = c.chartData.lines[i];
- auto path = QPainterPath();
- for (auto x = localStart; x <= localEnd; x++) {
- if (line.y[x] <= 0 && _isStack) {
- continue;
- }
- const auto yPercentage = (line.y[x] - c.heightLimits.min)
- / float64(c.heightLimits.max - c.heightLimits.min);
- const auto yPoint = yPercentage
- * c.rect.height()
- * linesFilterController()->alpha(line.id);
- const auto bottomIndex = x - localStart;
- const auto column = QRectF(
- leftStart + (x - localStart) * w,
- c.rect.height() - bottoms[bottomIndex] - yPoint,
- w,
- yPoint);
- if (hasSelectedXIndex && (x == _lastSelectedXIndex)) {
- selectedBottoms[i] = column.y();
- }
- 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);
- }
- }
- for (auto i = 0; i < selectedBottoms.size(); i++) {
- p.setOpacity(opacity);
- if (selectedBottoms[i] <= 0) {
- continue;
- }
- const auto &line = c.chartData.lines[i];
- const auto yPercentage = 0.
- + (line.y[_lastSelectedXIndex] - c.heightLimits.min)
- / float64(c.heightLimits.max - c.heightLimits.min);
- const auto yPoint = yPercentage
- * c.rect.height()
- * linesFilterController()->alpha(line.id);
- const auto column = QRectF(
- leftStart + (_lastSelectedXIndex - localStart) * w,
- selectedBottoms[i],
- w,
- yPoint);
- p.fillRect(column, line.color);
- }
- p.setClipping(false);
- }
- void BarChartView::paintSelectedXIndex(
- QPainter &p,
- const PaintContext &c,
- int selectedXIndex,
- float64 progress) {
- const auto was = _lastSelectedXIndex;
- _lastSelectedXIndex = selectedXIndex;
- _lastSelectedXProgress = progress;
- if ((_lastSelectedXIndex < 0) && (was < 0)) {
- return;
- }
- if (_isStack) {
- BarChartView::paintChartAndSelected(p, c);
- } else if (selectedXIndex >= 0) {
- const auto linesFilter = linesFilterController();
- auto hq = PainterHighQualityEnabler(p);
- auto o = ScopedPainterOpacity(p, progress);
- p.setBrush(st::boxBg);
- const auto r = st::statisticsDetailsDotRadius;
- 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 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;
- }
- }
- int BarChartView::findXIndexByPosition(
- const Data::StatisticalChart &chartData,
- const Limits &xPercentageLimits,
- const QRect &rect,
- float64 xPos) {
- if ((xPos < rect.x()) || (xPos > (rect.x() + rect.width()))) {
- return _lastSelectedXIndex = -1;
- }
- const auto &[localStart, localEnd] = _lastPaintedXIndices;
- const auto &[leftStart, w] = ComputeLeftStartAndStep(
- chartData,
- xPercentageLimits,
- rect,
- localStart);
- for (auto i = 0; i < chartData.lines.size(); i++) {
- for (auto x = localStart; x <= localEnd; x++) {
- const auto left = leftStart + (x - localStart) * w;
- if ((xPos >= left) && (xPos < (left + w))) {
- return _lastSelectedXIndex = x;
- }
- }
- }
- return _lastSelectedXIndex = -1;
- }
- 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());
- auto maxValueFull = ChartValue(0);
- for (auto i = 0; i < chartData.x.size(); i++) {
- auto sum = ChartValue(0);
- for (const auto &line : chartData.lines) {
- if (linesFilterController()->isEnabled(line.id)) {
- sum += line.y[i];
- }
- }
- _cachedHeightLimits.ySum.push_back(sum);
- maxValueFull = std::max(sum, maxValueFull);
- }
- _cachedHeightLimits.ySumSegmentTree = SegmentTree(
- _cachedHeightLimits.ySum);
- _cachedHeightLimits.full = { 0., float64(maxValueFull) };
- }
- const auto max = std::max(
- _cachedHeightLimits.ySumSegmentTree.rMaxQ(
- xIndices.min,
- xIndices.max),
- ChartValue(1));
- return {
- .full = _cachedHeightLimits.full,
- .ranged = { 0., float64(max) },
- };
- }
- } // namespace Statistic
|