2023-05-24 15:30:30 +00:00
|
|
|
/*
|
|
|
|
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
|
|
|
|
*/
|
2023-08-27 20:47:04 +00:00
|
|
|
#include "statistics/view/linear_chart_view.h"
|
2023-05-24 15:30:30 +00:00
|
|
|
|
|
|
|
#include "data/data_statistics.h"
|
2023-05-26 13:04:33 +00:00
|
|
|
#include "statistics/statistics_common.h"
|
2023-05-26 13:04:49 +00:00
|
|
|
#include "ui/effects/animation_value_f.h"
|
2023-07-07 08:28:50 +00:00
|
|
|
#include "ui/painter.h"
|
2023-05-24 15:30:30 +00:00
|
|
|
#include "styles/style_boxes.h"
|
2023-07-07 08:28:50 +00:00
|
|
|
#include "styles/style_statistics.h"
|
2023-05-24 15:30:30 +00:00
|
|
|
|
|
|
|
namespace Statistic {
|
2023-07-26 22:03:52 +00:00
|
|
|
namespace {
|
|
|
|
|
2023-07-26 22:23:57 +00:00
|
|
|
constexpr auto kAlphaDuration = float64(350);
|
|
|
|
|
2023-07-26 22:03:52 +00:00
|
|
|
void PaintChartLine(
|
|
|
|
QPainter &p,
|
|
|
|
int lineIndex,
|
|
|
|
const Data::StatisticalChart &chartData,
|
|
|
|
const Limits &xIndices,
|
|
|
|
const Limits &xPercentageLimits,
|
|
|
|
const Limits &heightLimits,
|
|
|
|
const QSize &size) {
|
|
|
|
const auto &line = chartData.lines[lineIndex];
|
|
|
|
|
|
|
|
auto chartPoints = QPolygonF();
|
|
|
|
|
|
|
|
const auto localStart = std::max(0, int(xIndices.min));
|
|
|
|
const auto localEnd = std::min(
|
|
|
|
int(chartData.xPercentage.size() - 1),
|
|
|
|
int(xIndices.max));
|
|
|
|
|
|
|
|
for (auto i = localStart; i <= localEnd; i++) {
|
|
|
|
if (line.y[i] < 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const auto xPoint = size.width()
|
|
|
|
* ((chartData.xPercentage[i] - xPercentageLimits.min)
|
|
|
|
/ (xPercentageLimits.max - xPercentageLimits.min));
|
|
|
|
const auto yPercentage = (line.y[i] - heightLimits.min)
|
|
|
|
/ float64(heightLimits.max - heightLimits.min);
|
|
|
|
const auto yPoint = (1. - yPercentage) * size.height();
|
|
|
|
chartPoints << QPointF(xPoint, yPoint);
|
|
|
|
}
|
|
|
|
p.setPen(QPen(line.color, st::statisticsChartLineWidth));
|
|
|
|
p.setBrush(Qt::NoBrush);
|
|
|
|
p.drawPolyline(chartPoints);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
2023-05-24 15:30:30 +00:00
|
|
|
|
2023-07-26 22:10:24 +00:00
|
|
|
LinearChartView::LinearChartView() = default;
|
2023-07-26 22:03:52 +00:00
|
|
|
|
2023-07-26 22:10:24 +00:00
|
|
|
void LinearChartView::paint(
|
2023-07-26 22:03:52 +00:00
|
|
|
QPainter &p,
|
|
|
|
const Data::StatisticalChart &chartData,
|
|
|
|
const Limits &xIndices,
|
|
|
|
const Limits &xPercentageLimits,
|
|
|
|
const Limits &heightLimits,
|
|
|
|
const QRect &rect,
|
2023-07-26 22:49:53 +00:00
|
|
|
bool footer) {
|
2023-07-26 22:03:52 +00:00
|
|
|
|
2023-07-26 22:10:24 +00:00
|
|
|
const auto cacheToken = LinearChartView::CacheToken(
|
2023-07-26 22:03:52 +00:00
|
|
|
xIndices,
|
|
|
|
xPercentageLimits,
|
|
|
|
heightLimits,
|
|
|
|
rect.size());
|
|
|
|
|
2023-07-26 22:49:53 +00:00
|
|
|
const auto imageSize = rect.size() * style::DevicePixelRatio();
|
|
|
|
const auto cacheScale = 1. / style::DevicePixelRatio();
|
|
|
|
auto &caches = (footer ? _footerCaches : _mainCaches);
|
|
|
|
|
2023-07-26 22:03:52 +00:00
|
|
|
for (auto i = 0; i < chartData.lines.size(); i++) {
|
|
|
|
const auto &line = chartData.lines[i];
|
2023-07-26 22:23:57 +00:00
|
|
|
p.setOpacity(alpha(line.id));
|
2023-07-26 22:03:52 +00:00
|
|
|
if (!p.opacity()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-07-26 22:49:53 +00:00
|
|
|
auto &cache = caches[line.id];
|
2023-07-26 22:03:52 +00:00
|
|
|
|
|
|
|
const auto isSameToken = (cache.lastToken == cacheToken);
|
2023-07-26 22:49:53 +00:00
|
|
|
if ((isSameToken && cache.hq)
|
|
|
|
|| (p.opacity() < 1. && !isEnabled(line.id))) {
|
2023-07-26 22:03:52 +00:00
|
|
|
p.drawImage(rect.topLeft(), cache.image);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
cache.hq = isSameToken;
|
|
|
|
auto image = QImage();
|
|
|
|
image = QImage(
|
2023-07-26 22:49:53 +00:00
|
|
|
imageSize * (isSameToken ? 1. : cacheScale),
|
2023-07-26 22:03:52 +00:00
|
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
|
|
image.setDevicePixelRatio(style::DevicePixelRatio());
|
|
|
|
image.fill(Qt::transparent);
|
|
|
|
{
|
|
|
|
auto imagePainter = QPainter(&image);
|
2023-07-26 22:49:53 +00:00
|
|
|
auto hq = PainterHighQualityEnabler(imagePainter);
|
|
|
|
if (!isSameToken) {
|
|
|
|
imagePainter.scale(cacheScale, cacheScale);
|
2023-07-26 22:03:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
PaintChartLine(
|
|
|
|
imagePainter,
|
|
|
|
i,
|
|
|
|
chartData,
|
|
|
|
xIndices,
|
|
|
|
xPercentageLimits,
|
|
|
|
heightLimits,
|
|
|
|
rect.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isSameToken) {
|
2023-07-26 22:49:53 +00:00
|
|
|
image = image.scaled(
|
|
|
|
imageSize,
|
|
|
|
Qt::IgnoreAspectRatio,
|
|
|
|
Qt::FastTransformation);
|
2023-07-26 22:03:52 +00:00
|
|
|
}
|
|
|
|
p.drawImage(rect.topLeft(), image);
|
|
|
|
cache.lastToken = cacheToken;
|
|
|
|
cache.image = std::move(image);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-27 00:13:32 +00:00
|
|
|
void LinearChartView::paintSelectedXIndex(
|
|
|
|
QPainter &p,
|
|
|
|
const Data::StatisticalChart &chartData,
|
|
|
|
const Limits &xPercentageLimits,
|
|
|
|
const Limits &heightLimits,
|
|
|
|
const QRect &rect,
|
|
|
|
int selectedXIndex) {
|
|
|
|
if (selectedXIndex < 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
p.setBrush(st::boxBg);
|
|
|
|
const auto r = st::statisticsDetailsDotRadius;
|
|
|
|
const auto i = selectedXIndex;
|
|
|
|
const auto isSameToken = (_selectedPoints.lastXIndex == selectedXIndex)
|
|
|
|
&& (_selectedPoints.lastHeightLimits.min == heightLimits.min)
|
|
|
|
&& (_selectedPoints.lastHeightLimits.max == heightLimits.max);
|
|
|
|
for (const auto &line : chartData.lines) {
|
|
|
|
const auto lineAlpha = alpha(line.id);
|
|
|
|
const auto useCache = isSameToken
|
|
|
|
|| (lineAlpha < 1. && !isEnabled(line.id));
|
|
|
|
if (!useCache) {
|
|
|
|
// Calculate.
|
|
|
|
const auto xPoint = rect.width()
|
|
|
|
* ((chartData.xPercentage[i] - xPercentageLimits.min)
|
|
|
|
/ (xPercentageLimits.max - xPercentageLimits.min));
|
|
|
|
const auto yPercentage = (line.y[i] - heightLimits.min)
|
|
|
|
/ float64(heightLimits.max - heightLimits.min);
|
|
|
|
const auto yPoint = (1. - yPercentage) * rect.height();
|
|
|
|
_selectedPoints.points[line.id] = QPointF(xPoint, yPoint)
|
|
|
|
+ rect.topLeft();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 = heightLimits;
|
|
|
|
}
|
|
|
|
|
2023-07-26 22:23:57 +00:00
|
|
|
void LinearChartView::setEnabled(int id, bool enabled, crl::time now) {
|
|
|
|
const auto it = _entries.find(id);
|
|
|
|
if (it == end(_entries)) {
|
|
|
|
_entries[id] = Entry{ .enabled = enabled, .startedAt = now };
|
|
|
|
} else if (it->second.enabled != enabled) {
|
|
|
|
auto &entry = it->second;
|
|
|
|
entry.enabled = enabled;
|
|
|
|
entry.startedAt = now
|
|
|
|
- kAlphaDuration * (enabled ? entry.alpha : (1. - entry.alpha));
|
|
|
|
}
|
|
|
|
_isFinished = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LinearChartView::isFinished() const {
|
|
|
|
return _isFinished;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LinearChartView::isEnabled(int id) const {
|
|
|
|
const auto it = _entries.find(id);
|
|
|
|
return (it == end(_entries)) ? true : it->second.enabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
float64 LinearChartView::alpha(int id) const {
|
|
|
|
const auto it = _entries.find(id);
|
|
|
|
return (it == end(_entries)) ? 1. : it->second.alpha;
|
|
|
|
}
|
|
|
|
|
|
|
|
void LinearChartView::tick(crl::time now) {
|
|
|
|
auto finishedCount = 0;
|
|
|
|
auto idsToRemove = std::vector<int>();
|
|
|
|
for (auto &[id, entry] : _entries) {
|
|
|
|
if (!entry.startedAt) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const auto progress = (now - entry.startedAt) / kAlphaDuration;
|
|
|
|
entry.alpha = std::clamp(
|
|
|
|
entry.enabled ? progress : (1. - progress),
|
|
|
|
0.,
|
|
|
|
1.);
|
|
|
|
if (entry.alpha == 1.) {
|
|
|
|
idsToRemove.push_back(id);
|
|
|
|
}
|
|
|
|
if (progress >= 1.) {
|
|
|
|
finishedCount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_isFinished = (finishedCount == _entries.size());
|
|
|
|
for (const auto &id : idsToRemove) {
|
|
|
|
_entries.remove(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-24 15:30:30 +00:00
|
|
|
} // namespace Statistic
|