Added initial widget with full zoom static linear chart.
This commit is contained in:
parent
06948ad15e
commit
c9eb9a3ee0
|
@ -1284,6 +1284,10 @@ PRIVATE
|
||||||
settings/settings_websites.h
|
settings/settings_websites.h
|
||||||
statistics/chart_horizontal_lines_data.cpp
|
statistics/chart_horizontal_lines_data.cpp
|
||||||
statistics/chart_horizontal_lines_data.h
|
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.cpp
|
||||||
statistics/segment_tree.h
|
statistics/segment_tree.h
|
||||||
statistics/statistics_box.cpp
|
statistics/statistics_box.cpp
|
||||||
|
|
213
Telegram/SourceFiles/statistics/chart_widget.cpp
Normal file
213
Telegram/SourceFiles/statistics/chart_widget.cpp
Normal 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
|
55
Telegram/SourceFiles/statistics/chart_widget.h
Normal file
55
Telegram/SourceFiles/statistics/chart_widget.h
Normal 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
|
65
Telegram/SourceFiles/statistics/linear_chart_view.cpp
Normal file
65
Telegram/SourceFiles/statistics/linear_chart_view.cpp
Normal 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
|
21
Telegram/SourceFiles/statistics/linear_chart_view.h
Normal file
21
Telegram/SourceFiles/statistics/linear_chart_view.h
Normal 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
|
|
@ -7,13 +7,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "statistics/statistics_box.h"
|
#include "statistics/statistics_box.h"
|
||||||
|
|
||||||
|
#include "api/api_statistics.h"
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "statistics/chart_widget.h"
|
||||||
#include "ui/layers/generic_box.h"
|
#include "ui/layers/generic_box.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void StatisticsBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
|
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());
|
box->setTitle(tr::lng_stats_title());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user