Added initial widget with full zoom static linear chart.

This commit is contained in:
23rd 2023-05-24 18:30:30 +03:00 committed by John Preston
parent 06948ad15e
commit c9eb9a3ee0
6 changed files with 373 additions and 0 deletions

View File

@ -1284,6 +1284,10 @@ PRIVATE
settings/settings_websites.h
statistics/chart_horizontal_lines_data.cpp
statistics/chart_horizontal_lines_data.h
statistics/chart_widget.cpp
statistics/chart_widget.h
statistics/linear_chart_view.cpp
statistics/linear_chart_view.h
statistics/segment_tree.cpp
statistics/segment_tree.h
statistics/statistics_box.cpp

View File

@ -0,0 +1,213 @@
/*
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 "statistics/linear_chart_view.h"
#include "ui/rect.h"
#include "styles/style_boxes.h"
namespace Statistic {
namespace {
constexpr auto kHeightLimitsUpdateTimeout = crl::time(320);
[[nodiscard]] int FindMaxValue(
Data::StatisticalChart &chartData,
int startXIndex,
int endXIndex) {
auto maxValue = 0;
for (auto &l : chartData.lines) {
const auto lineMax = l.segmentTree.rMaxQ(startXIndex, endXIndex);
maxValue = std::max(lineMax, maxValue);
}
return maxValue;
}
[[nodiscard]] int FindMinValue(
Data::StatisticalChart &chartData,
int startXIndex,
int endXIndex) {
auto minValue = std::numeric_limits<int>::max();
for (auto &l : chartData.lines) {
const auto lineMin = l.segmentTree.rMinQ(startXIndex, endXIndex);
minValue = std::min(lineMin, minValue);
}
return minValue;
}
void PaintHorizontalLines(
QPainter &p,
const ChartHorizontalLinesData &horizontalLine,
const QRect &r) {
for (const auto &line : horizontalLine.lines) {
const auto lineRect = QRect(
0,
r.y() + r.height() * line.relativeValue,
r.x() + r.width(),
st::lineWidth);
p.fillRect(lineRect, st::boxTextFg);
}
}
void PaintCaptionsToHorizontalLines(
QPainter &p,
const ChartHorizontalLinesData &horizontalLine,
const QRect &r) {
p.setFont(st::boxTextFont->f);
p.setPen(st::boxTextFg);
for (const auto &line : horizontalLine.lines) {
p.drawText(10, r.y() + r.height() * line.relativeValue, line.caption);
}
}
} // namespace
ChartWidget::ChartWidget(not_null<Ui::RpWidget*> parent)
: Ui::RpWidget(parent) {
resize(width(), st::confirmMaxHeight);
}
void ChartWidget::setChartData(Data::StatisticalChart chartData) {
_chartData = chartData;
{
const auto startXIndex = _chartData.findStartIndex(
_chartData.xPercentage.front());
const auto endXIndex = _chartData.findEndIndex(
startXIndex,
_chartData.xPercentage.back());
setHeightLimits(
{
float64(FindMinValue(_chartData, startXIndex, endXIndex)),
float64(FindMaxValue(_chartData, startXIndex, endXIndex)),
},
true);
update();
}
}
void ChartWidget::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
const auto r = rect();
const auto captionRect = r;
const auto chartRect = r
- QMargins{ 0, st::boxTextFont->height, 0, st::lineWidth };
for (const auto &horizontalLine : _horizontalLines) {
PaintHorizontalLines(p, horizontalLine, chartRect);
}
if (_chartData) {
Statistic::PaintLinearChartView(
p,
_chartData,
chartRect);
}
for (const auto &horizontalLine : _horizontalLines) {
PaintCaptionsToHorizontalLines(p, horizontalLine, chartRect);
}
}
void ChartWidget::setHeightLimits(Limits newHeight, bool animated) {
{
const auto lineMaxHeight = ChartHorizontalLinesData::LookupHeight(
newHeight.max);
const auto diff = std::abs(lineMaxHeight - _animateToHeight.max);
const auto heightChanged = (!newHeight.max)
|| (diff < _thresholdHeight.max);
if (!heightChanged && (newHeight.max == _animateToHeight.min)) {
return;
}
}
const auto newLinesData = ChartHorizontalLinesData(
newHeight.max,
newHeight.min,
true);
newHeight = Limits{
.min = newLinesData.lines.front().absoluteValue,
.max = newLinesData.lines.back().absoluteValue,
};
{
auto k = (_currentHeight.max - _currentHeight.min)
/ float64(newHeight.max - newHeight.min);
if (k > 1.) {
k = 1. / k;
}
constexpr auto kUpdateStep1 = 0.1;
constexpr auto kUpdateStep2 = 0.03;
constexpr auto kUpdateStep3 = 0.045;
constexpr auto kUpdateStepThreshold1 = 0.7;
constexpr auto kUpdateStepThreshold2 = 0.1;
const auto s = (k > kUpdateStepThreshold1)
? kUpdateStep1
: (k < kUpdateStepThreshold2)
? kUpdateStep2
: kUpdateStep3;
const auto refresh = (newHeight.max != _animateToHeight.max)
|| (_useMinHeight && (newHeight.min != _animateToHeight.min));
if (refresh) {
_startFromH = _currentHeight;
_startFrom = {};
_minMaxUpdateStep = s;
}
}
_animateToHeight = newHeight;
measureHeightThreshold();
{
const auto now = crl::now();
if ((now - _lastHeightLimitsChanged) < kHeightLimitsUpdateTimeout) {
return;
}
_lastHeightLimitsChanged = now;
}
if (!animated) {
_currentHeight = newHeight;
_horizontalLines.clear();
_horizontalLines.push_back(newLinesData);
_horizontalLines.back().alpha = 1.;
return;
}
for (auto &horizontalLine : _horizontalLines) {
horizontalLine.fixedAlpha = horizontalLine.alpha;
}
_horizontalLines.push_back(newLinesData);
const auto callback = [=](float64 value) {
_horizontalLines.back().alpha = value;
const auto startIt = begin(_horizontalLines);
const auto endIt = end(_horizontalLines);
for (auto it = startIt; it != (endIt - 1); it++) {
it->alpha = it->fixedAlpha * (1. - (endIt - 1)->alpha);
}
update();
};
_heightLimitsAnimation.start(callback, 0., 1., st::slideDuration);
}
void ChartWidget::measureHeightThreshold() {
const auto chartHeight = height();
if (!_animateToHeight.max || !chartHeight) {
return;
}
_thresholdHeight.max = (_animateToHeight.max / float64(chartHeight))
* st::boxTextFont->height;
}
} // namespace Statistic

View File

@ -0,0 +1,55 @@
/*
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
*/
#pragma once
#include "data/data_statistics.h"
#include "statistics/chart_horizontal_lines_data.h"
#include "ui/effects/animations.h"
#include "ui/rp_widget.h"
namespace Statistic {
class ChartWidget : public Ui::RpWidget {
public:
ChartWidget(not_null<Ui::RpWidget*> parent);
struct Limits final {
float64 min = 0;
float64 max = 0;
};
void setChartData(Data::StatisticalChart chartData);
void setHeightLimits(Limits newHeight, bool animated);
protected:
void paintEvent(QPaintEvent *e) override;
private:
void measureHeightThreshold();
Data::StatisticalChart _chartData;
bool _useMinHeight = false;
Limits _currentHeight;
Limits _animateToHeight;
Limits _thresholdHeight = { -1, 0 };
Limits _startFrom;
Limits _startFromH;
float64 _minMaxUpdateStep = 0.;
crl::time _lastHeightLimitsChanged = 0;
std::vector<ChartHorizontalLinesData> _horizontalLines;
Ui::Animations::Simple _heightLimitsAnimation;
};
} // namespace Statistic

View File

@ -0,0 +1,65 @@
/*
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/linear_chart_view.h"
#include "data/data_statistics.h"
#include "styles/style_boxes.h"
namespace Statistic {
void PaintLinearChartView(
QPainter &p,
const Data::StatisticalChart &chartData,
const QRect &rect) {
const auto offset = 0;
const auto currentMinHeight = rect.y(); //
const auto currentMaxHeight = rect.height() + rect.y(); //
for (const auto &line : chartData.lines) {
const auto additionalP = (chartData.xPercentage.size() < 2)
? 0.
: (chartData.xPercentage.front() * rect.width());
const auto additionalPoints = 0;
auto first = true;
auto chartPath = QPainterPath();
const auto startXIndex = chartData.findStartIndex(
chartData.xPercentage.front());
const auto endXIndex = chartData.findEndIndex(
startXIndex,
chartData.xPercentage.back());
const auto localStart = std::max(0, startXIndex - additionalPoints);
const auto localEnd = std::min(
int(chartData.xPercentage.size() - 1),
endXIndex + additionalPoints);
for (auto i = localStart; i <= localEnd; i++) {
if (line.y[i] < 0) {
continue;
}
const auto xPoint = chartData.xPercentage[i] * rect.width()
- offset;
const auto yPercentage = (line.y[i] - line.minValue)
/ float64(line.maxValue - line.minValue);
const auto yPoint = rect.y() + (1. - yPercentage) * rect.height();
if (first) {
first = false;
chartPath.moveTo(xPoint, yPoint);
}
chartPath.lineTo(xPoint, yPoint);
}
p.setPen(line.color);
p.setBrush(Qt::NoBrush);
p.drawPath(chartPath);
}
p.setPen(st::boxTextFg);
}
} // namespace Statistic

View File

@ -0,0 +1,21 @@
/*
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
*/
#pragma once
namespace Data {
struct StatisticalChart;
} // namespace Data
namespace Statistic {
void PaintLinearChartView(
QPainter &p,
const Data::StatisticalChart &chartData,
const QRect &rect);
} // namespace Statistic

View File

@ -7,13 +7,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "statistics/statistics_box.h"
#include "api/api_statistics.h"
#include "data/data_peer.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "statistics/chart_widget.h"
#include "ui/layers/generic_box.h"
namespace {
} // namespace
void StatisticsBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
const auto chartWidget = box->addRow(
object_ptr<Statistic::ChartWidget>(box));
const auto api = chartWidget->lifetime().make_state<Api::Statistics>(
&peer->session().api());
api->request(
peer
) | rpl::start_with_done([=] {
if (const auto stats = api->channelStats()) {
chartWidget->setChartData(stats.memberCountGraph.chart);
}
}, chartWidget->lifetime());
box->setTitle(tr::lng_stats_title());
}