Initially refactored statistics module to simplify value types changing.

This commit is contained in:
23rd 2024-04-11 17:37:54 +03:00 committed by John Preston
parent 7ffa9844e2
commit a37cbd7d05
14 changed files with 97 additions and 71 deletions

View File

@ -30,14 +30,15 @@ struct StatisticalChart {
[[nodiscard]] int findIndex(int left, int right, float64 v) const; [[nodiscard]] int findIndex(int left, int right, float64 v) const;
struct Line final { struct Line final {
std::vector<int> y; std::vector<Statistic::ChartValue> y;
Statistic::SegmentTree segmentTree; Statistic::SegmentTree segmentTree;
int id = 0; int id = 0;
QString idString; QString idString;
QString name; QString name;
int maxValue = 0; Statistic::ChartValue maxValue = 0;
int minValue = std::numeric_limits<int>::max(); Statistic::ChartValue minValue =
std::numeric_limits<Statistic::ChartValue>::max();
QString colorKey; QString colorKey;
QColor color; QColor color;
QColor colorDark; QColor colorDark;
@ -55,8 +56,9 @@ struct StatisticalChart {
float64 max = 0.; float64 max = 0.;
} defaultZoomXIndex; } defaultZoomXIndex;
int maxValue = 0; Statistic::ChartValue maxValue = 0;
int minValue = std::numeric_limits<int>::max(); Statistic::ChartValue minValue =
std::numeric_limits<Statistic::ChartValue>::max();
float64 oneDayPercentage = 0.; float64 oneDayPercentage = 0.;

View File

@ -12,17 +12,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Statistic { namespace Statistic {
namespace { namespace {
constexpr auto kMinLines = int(2); constexpr auto kMinLines = ChartValue(2);
constexpr auto kMaxLines = int(6); constexpr auto kMaxLines = ChartValue(6);
constexpr auto kStep = 5.; constexpr auto kStep = 5.;
[[nodiscard]] int Round(int maxValue) { [[nodiscard]] ChartValue Round(ChartValue maxValue) {
const auto k = int(maxValue / kStep); const auto k = ChartValue(maxValue / kStep);
return (k % 10 == 0) ? maxValue : ((maxValue / 10 + 1) * 10); return (k % 10 == 0) ? maxValue : ((maxValue / 10 + 1) * 10);
} }
[[nodiscard]] QString Format(int absoluteValue) { [[nodiscard]] QString Format(ChartValue absoluteValue) {
constexpr auto kTooMuch = int(10'000); constexpr auto kTooMuch = ChartValue(10'000);
return (absoluteValue >= kTooMuch) return (absoluteValue >= kTooMuch)
? Lang::FormatCountToShort(absoluteValue).string ? Lang::FormatCountToShort(absoluteValue).string
: QString::number(absoluteValue); : QString::number(absoluteValue);
@ -31,8 +31,8 @@ constexpr auto kStep = 5.;
} // namespace } // namespace
ChartRulersData::ChartRulersData( ChartRulersData::ChartRulersData(
int newMaxHeight, ChartValue newMaxHeight,
int newMinHeight, ChartValue newMinHeight,
bool useMinHeight, bool useMinHeight,
float64 rightRatio, float64 rightRatio,
Fn<QString(float64)> leftCustomCaption, Fn<QString(float64)> leftCustomCaption,
@ -42,11 +42,13 @@ ChartRulersData::ChartRulersData(
? Round(newMaxHeight) ? Round(newMaxHeight)
: newMaxHeight; : newMaxHeight;
const auto step = std::max(1, int(std::ceil(v / kStep))); const auto step = std::max(
ChartValue(1),
ChartValue(std::ceil(v / kStep)));
auto n = kMaxLines; auto n = kMaxLines;
if (v < kMaxLines) { if (v < kMaxLines) {
n = std::max(2, v + 1); n = std::max(2, int(v + 1));
} else if (v / 2 < kMaxLines) { } else if (v / 2 < kMaxLines) {
n = v / 2 + 1; n = v / 2 + 1;
if (v % 2 != 0) { if (v % 2 != 0) {
@ -87,11 +89,11 @@ ChartRulersData::ChartRulersData(
} }
lines.resize(n); lines.resize(n);
const auto diffAbsoluteValue = int((n - 1) * step); const auto diffAbsoluteValue = ChartValue((n - 1) * step);
const auto skipFloatValues = (step / rightRatio) < 1; const auto skipFloatValues = (step / rightRatio) < 1;
for (auto i = 0; i < n; i++) { for (auto i = 0; i < n; i++) {
auto &line = lines[i]; auto &line = lines[i];
const auto value = int(i * step); const auto value = ChartValue(i * step);
line.absoluteValue = newMinHeight + value; line.absoluteValue = newMinHeight + value;
line.relativeValue = 1. - value / float64(diffAbsoluteValue); line.relativeValue = 1. - value / float64(diffAbsoluteValue);
line.caption = leftCustomCaption line.caption = leftCustomCaption
@ -103,7 +105,7 @@ ChartRulersData::ChartRulersData(
? rightCustomCaption(line.absoluteValue) ? rightCustomCaption(line.absoluteValue)
: (!skipFloatValues) : (!skipFloatValues)
? Format(v) ? Format(v)
: ((v - int(v)) < 0.01) : ((v - ChartValue(v)) < 0.01)
? Format(v) ? Format(v)
: QString(); : QString();
} }
@ -112,8 +114,8 @@ ChartRulersData::ChartRulersData(
} }
void ChartRulersData::computeRelative( void ChartRulersData::computeRelative(
int newMaxHeight, ChartValue newMaxHeight,
int newMinHeight) { ChartValue newMinHeight) {
for (auto &line : lines) { for (auto &line : lines) {
line.relativeValue = 1. line.relativeValue = 1.
- ((line.absoluteValue - newMinHeight) - ((line.absoluteValue - newMinHeight)
@ -121,10 +123,10 @@ void ChartRulersData::computeRelative(
} }
} }
int ChartRulersData::LookupHeight(int maxValue) { ChartValue ChartRulersData::LookupHeight(ChartValue maxValue) {
const auto v = (maxValue > 100) ? Round(maxValue) : maxValue; const auto v = (maxValue > 100) ? Round(maxValue) : maxValue;
const auto step = int(std::ceil(v / kStep)); const auto step = ChartValue(std::ceil(v / kStep));
return step * kStep; return step * kStep;
} }

View File

@ -7,23 +7,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "statistics/statistics_types.h"
namespace Statistic { namespace Statistic {
struct ChartRulersData final { struct ChartRulersData final {
public: public:
ChartRulersData( ChartRulersData(
int newMaxHeight, ChartValue newMaxHeight,
int newMinHeight, ChartValue newMinHeight,
bool useMinHeight, bool useMinHeight,
float64 rightRatio, float64 rightRatio,
Fn<QString(float64)> leftCustomCaption = nullptr, Fn<QString(float64)> leftCustomCaption = nullptr,
Fn<QString(float64)> rightCustomCaption = nullptr); Fn<QString(float64)> rightCustomCaption = nullptr);
void computeRelative( void computeRelative(
int newMaxHeight, ChartValue newMaxHeight,
int newMinHeight); ChartValue newMinHeight);
[[nodiscard]] static int LookupHeight(int maxValue); [[nodiscard]] static ChartValue LookupHeight(ChartValue maxValue);
struct Line final { struct Line final {
float64 absoluteValue = 0.; float64 absoluteValue = 0.;

View File

@ -1157,7 +1157,7 @@ void ChartWidget::setupDetails() {
return; return;
} }
const auto maxAbsoluteValue = [&] { const auto maxAbsoluteValue = [&] {
auto maxValue = 0; auto maxValue = ChartValue(0);
for (const auto &l : _chartData.lines) { for (const auto &l : _chartData.lines) {
maxValue = std::max(l.maxValue, maxValue); maxValue = std::max(l.maxValue, maxValue);
} }

View File

@ -14,7 +14,7 @@ constexpr auto kMinArraySize = size_t(30);
} // namespace } // namespace
SegmentTree::SegmentTree(std::vector<int> array) SegmentTree::SegmentTree(std::vector<ChartValue> array)
: _array(std::move(array)) { : _array(std::move(array)) {
if (_array.size() < kMinArraySize) { if (_array.size() < kMinArraySize) {
return; return;
@ -28,7 +28,7 @@ SegmentTree::SegmentTree(std::vector<int> array)
build(1, 0, _array.size()); build(1, 0, _array.size());
} }
void SegmentTree::build(int v, int from, int size) { void SegmentTree::build(ChartValue v, int from, int size) {
_heap[v].from = from; _heap[v].from = from;
_heap[v].to = (from + size - 1); _heap[v].to = (from + size - 1);
@ -48,9 +48,9 @@ void SegmentTree::build(int v, int from, int size) {
} }
} }
int SegmentTree::rMaxQ(int from, int to) { ChartValue SegmentTree::rMaxQ(int from, int to) {
if (_array.size() < kMinArraySize) { if (_array.size() < kMinArraySize) {
auto max = 0; auto max = ChartValue(0);
from = std::max(from, 0); from = std::max(from, 0);
to = std::min(to, int(_array.size() - 1)); to = std::min(to, int(_array.size() - 1));
for (auto i = from; i <= to; i++) { for (auto i = from; i <= to; i++) {
@ -61,7 +61,7 @@ int SegmentTree::rMaxQ(int from, int to) {
return rMaxQ(1, from, to); return rMaxQ(1, from, to);
} }
int SegmentTree::rMaxQ(int v, int from, int to) { ChartValue SegmentTree::rMaxQ(ChartValue v, int from, int to) {
const auto &n = _heap[v]; const auto &n = _heap[v];
// If you did a range update that contained this node, // If you did a range update that contained this node,
// you can infer the Min value without going down the tree. // you can infer the Min value without going down the tree.
@ -84,9 +84,9 @@ int SegmentTree::rMaxQ(int v, int from, int to) {
return 0; return 0;
} }
int SegmentTree::rMinQ(int from, int to) { ChartValue SegmentTree::rMinQ(int from, int to) {
if (_array.size() < kMinArraySize) { if (_array.size() < kMinArraySize) {
auto min = std::numeric_limits<int>::max(); auto min = std::numeric_limits<ChartValue>::max();
from = std::max(from, 0); from = std::max(from, 0);
to = std::min(to, int(_array.size() - 1)); to = std::min(to, int(_array.size() - 1));
for (auto i = from; i <= to; i++) { for (auto i = from; i <= to; i++) {
@ -97,7 +97,7 @@ int SegmentTree::rMinQ(int from, int to) {
return rMinQ(1, from, to); return rMinQ(1, from, to);
} }
int SegmentTree::rMinQ(int v, int from, int to) { ChartValue SegmentTree::rMinQ(ChartValue v, int from, int to) {
const auto &n = _heap[v]; const auto &n = _heap[v];
// If you did a range update that contained this node, // If you did a range update that contained this node,
// you can infer the Min value without going down the tree. // you can infer the Min value without going down the tree.
@ -117,10 +117,10 @@ int SegmentTree::rMinQ(int v, int from, int to) {
return std::min(leftMin, rightMin); return std::min(leftMin, rightMin);
} }
return std::numeric_limits<int>::max(); return std::numeric_limits<ChartValue>::max();
} }
void SegmentTree::propagate(int v) { void SegmentTree::propagate(ChartValue v) {
auto &n = _heap[v]; auto &n = _heap[v];
if (n.pendingVal) { if (n.pendingVal) {
@ -131,7 +131,7 @@ void SegmentTree::propagate(int v) {
} }
} }
void SegmentTree::change(SegmentTree::Node &n, int value) { void SegmentTree::change(SegmentTree::Node &n, ChartValue value) {
n.pendingVal = { value, true }; n.pendingVal = { value, true };
n.sum = n.size() * value; n.sum = n.size() * value;
n.max = value; n.max = value;

View File

@ -7,12 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "statistics/statistics_types.h"
namespace Statistic { namespace Statistic {
class SegmentTree final { class SegmentTree final {
public: public:
SegmentTree() = default; SegmentTree() = default;
SegmentTree(std::vector<int> array); SegmentTree(std::vector<ChartValue> array);
[[nodiscard]] bool empty() const { [[nodiscard]] bool empty() const {
return _array.empty(); return _array.empty();
@ -21,20 +23,20 @@ public:
return !empty(); return !empty();
} }
[[nodiscard]] int rMaxQ(int from, int to); [[nodiscard]] ChartValue rMaxQ(int from, int to);
[[nodiscard]] int rMinQ(int from, int to); [[nodiscard]] ChartValue rMinQ(int from, int to);
private: private:
struct Node final { struct Node final {
int sum = 0; ChartValue sum = 0;
int max = 0; ChartValue max = 0;
int min = 0; ChartValue min = 0;
struct PendingVal { struct PendingVal {
[[nodiscard]] explicit operator bool() const { [[nodiscard]] explicit operator bool() const {
return available; return available;
} }
int value = 0; ChartValue value = 0;
bool available = false; bool available = false;
}; };
PendingVal pendingVal; PendingVal pendingVal;
@ -47,12 +49,12 @@ private:
} }
}; };
void build(int v, int from, int size); void build(ChartValue v, int from, int size);
void propagate(int v); void propagate(ChartValue v);
void change(Node &n, int value); void change(Node &n, ChartValue value);
[[nodiscard]] int rMaxQ(int v, int from, int to); [[nodiscard]] ChartValue rMaxQ(ChartValue v, int from, int to);
[[nodiscard]] int rMinQ(int v, int from, int to); [[nodiscard]] ChartValue rMinQ(ChartValue v, int from, int to);
[[nodiscard]] bool contains(int from1, int to1, int from2, int to2) const; [[nodiscard]] bool contains(int from1, int to1, int from2, int to2) const;
[[nodiscard]] bool intersects( [[nodiscard]] bool intersects(
@ -61,7 +63,7 @@ private:
int from2, int from2,
int to2) const; int to2) const;
std::vector<int> _array; std::vector<ChartValue> _array;
std::vector<Node> _heap; std::vector<Node> _heap;
}; };

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/debug_log.h" #include "base/debug_log.h"
#include "data/data_statistics_chart.h" #include "data/data_statistics_chart.h"
#include "statistics/statistics_types.h"
#include <QtCore/QJsonArray> #include <QtCore/QJsonArray>
#include <QtCore/QJsonDocument> #include <QtCore/QJsonDocument>
@ -61,7 +62,7 @@ Data::StatisticalChart StatisticalChartFromJSON(const QByteArray &json) {
line.isHiddenOnStart = ranges::contains(hiddenLines, columnId); line.isHiddenOnStart = ranges::contains(hiddenLines, columnId);
line.y.resize(length); line.y.resize(length);
for (auto i = 0; i < length; i++) { for (auto i = 0; i < length; i++) {
const auto value = int(base::SafeRound( const auto value = ChartValue(base::SafeRound(
array.at(i + 1).toDouble())); array.at(i + 1).toDouble()));
line.y[i] = value; line.y[i] = value;
if (value > line.maxValue) { if (value > line.maxValue) {

View File

@ -0,0 +1,14 @@
/*
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 Statistic {
using ChartValue = int64;
} // namespace Statistic

View File

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_statistics_chart.h" #include "data/data_statistics_chart.h"
#include "statistics/chart_lines_filter_controller.h" #include "statistics/chart_lines_filter_controller.h"
#include "statistics/statistics_types.h"
namespace Statistic { namespace Statistic {
@ -71,11 +72,11 @@ AbstractChartView::HeightLimits DefaultHeightLimits(
const std::shared_ptr<LinesFilterController> &linesFilter, const std::shared_ptr<LinesFilterController> &linesFilter,
Data::StatisticalChart &chartData, Data::StatisticalChart &chartData,
Limits xIndices) { Limits xIndices) {
auto minValue = std::numeric_limits<int>::max(); auto minValue = std::numeric_limits<ChartValue>::max();
auto maxValue = 0; auto maxValue = ChartValue(0);
auto minValueFull = std::numeric_limits<int>::max(); auto minValueFull = std::numeric_limits<ChartValue>::max();
auto maxValueFull = 0; auto maxValueFull = ChartValue(0);
for (auto &l : chartData.lines) { for (auto &l : chartData.lines) {
if (!linesFilter->isEnabled(l.id)) { if (!linesFilter->isEnabled(l.id)) {
continue; continue;
@ -83,11 +84,11 @@ AbstractChartView::HeightLimits DefaultHeightLimits(
const auto r = ratios.ratio(l.id); const auto r = ratios.ratio(l.id);
const auto lineMax = l.segmentTree.rMaxQ(xIndices.min, xIndices.max); const auto lineMax = l.segmentTree.rMaxQ(xIndices.min, xIndices.max);
const auto lineMin = l.segmentTree.rMinQ(xIndices.min, xIndices.max); const auto lineMin = l.segmentTree.rMinQ(xIndices.min, xIndices.max);
maxValue = std::max(int(lineMax * r), maxValue); maxValue = std::max(ChartValue(lineMax * r), maxValue);
minValue = std::min(int(lineMin * r), minValue); minValue = std::min(ChartValue(lineMin * r), minValue);
maxValueFull = std::max(int(l.maxValue * r), maxValueFull); maxValueFull = std::max(ChartValue(l.maxValue * r), maxValueFull);
minValueFull = std::min(int(l.minValue * r), minValueFull); minValueFull = std::min(ChartValue(l.minValue * r), minValueFull);
} }
if (maxValue == minValue) { if (maxValue == minValue) {
maxValue = chartData.maxValue; maxValue = chartData.maxValue;

View File

@ -256,9 +256,9 @@ AbstractChartView::HeightLimits BarChartView::heightLimits(
if (_cachedHeightLimits.ySum.empty()) { if (_cachedHeightLimits.ySum.empty()) {
_cachedHeightLimits.ySum.reserve(chartData.x.size()); _cachedHeightLimits.ySum.reserve(chartData.x.size());
auto maxValueFull = 0; auto maxValueFull = ChartValue(0);
for (auto i = 0; i < chartData.x.size(); i++) { for (auto i = 0; i < chartData.x.size(); i++) {
auto sum = 0; auto sum = ChartValue(0);
for (const auto &line : chartData.lines) { for (const auto &line : chartData.lines) {
if (linesFilterController()->isEnabled(line.id)) { if (linesFilterController()->isEnabled(line.id)) {
sum += line.y[i]; sum += line.y[i];
@ -276,7 +276,7 @@ AbstractChartView::HeightLimits BarChartView::heightLimits(
_cachedHeightLimits.ySumSegmentTree.rMaxQ( _cachedHeightLimits.ySumSegmentTree.rMaxQ(
xIndices.min, xIndices.min,
xIndices.max), xIndices.max),
1); ChartValue(1));
return { return {
.full = _cachedHeightLimits.full, .full = _cachedHeightLimits.full,
.ranged = { 0., float64(max) }, .ranged = { 0., float64(max) },

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "statistics/segment_tree.h" #include "statistics/segment_tree.h"
#include "statistics/statistics_common.h" #include "statistics/statistics_common.h"
#include "statistics/statistics_types.h"
#include "statistics/view/abstract_chart_view.h" #include "statistics/view/abstract_chart_view.h"
#include "ui/effects/animation_value.h" #include "ui/effects/animation_value.h"
@ -48,7 +49,7 @@ private:
struct { struct {
Limits full; Limits full;
std::vector<int> ySum; std::vector<ChartValue> ySum;
SegmentTree ySumSegmentTree; SegmentTree ySumSegmentTree;
} _cachedHeightLimits; } _cachedHeightLimits;

View File

@ -81,7 +81,7 @@ PiePartData PiePartsPercentageByIndices(
sums.reserve(chartData.lines.size()); sums.reserve(chartData.lines.size());
auto totalSum = 0.; auto totalSum = 0.;
for (const auto &line : chartData.lines) { for (const auto &line : chartData.lines) {
auto sum = 0; auto sum = ChartValue(0);
for (auto i = xIndices.min; i <= xIndices.max; i++) { for (auto i = xIndices.min; i <= xIndices.max; i++) {
sum += line.y[i]; sum += line.y[i];
} }

View File

@ -165,8 +165,8 @@ void StackLinearChartView::prepareZoom(
_transition.zoomedOutXIndices = c.xIndices; _transition.zoomedOutXIndices = c.xIndices;
_transition.zoomedOutXPercentage = c.xPercentageLimits; _transition.zoomedOutXPercentage = c.xPercentageLimits;
} else if (step == TransitionStep::PrepareToZoomIn) { } else if (step == TransitionStep::PrepareToZoomIn) {
const auto &[zoomedStart, zoomedEnd] = const auto &[zoomedStart, zoomedEnd]
_transition.zoomedOutXIndices; = _transition.zoomedOutXIndices;
_transition.lines = std::vector<Transition::TransitionLine>( _transition.lines = std::vector<Transition::TransitionLine>(
c.chartData.lines.size(), c.chartData.lines.size(),
Transition::TransitionLine()); Transition::TransitionLine());
@ -624,7 +624,7 @@ void StackLinearChartView::paintZoomed(QPainter &p, const PaintContext &c) {
if (selectedLineIndex >= 0) { if (selectedLineIndex >= 0) {
const auto &line = c.chartData.lines[selectedLineIndex]; const auto &line = c.chartData.lines[selectedLineIndex];
auto sum = 0; auto sum = ChartValue(0);
for (auto i = zoomedStart; i <= zoomedEnd; i++) { for (auto i = zoomedStart; i <= zoomedEnd; i++) {
sum += line.y[i]; sum += line.y[i];
} }
@ -669,8 +669,8 @@ void StackLinearChartView::paintZoomedFooter(
0); 0);
const auto next = std::clamp(i + offset, zoomedStart, zoomedEnd); const auto next = std::clamp(i + offset, zoomedStart, zoomedEnd);
const auto xPointPercentage = const auto xPointPercentage
(xPercentage[next] - xPercentage[zoomedStart]) = (xPercentage[next] - xPercentage[zoomedStart])
/ (xPercentage[zoomedEnd] - xPercentage[zoomedStart]); / (xPercentage[zoomedEnd] - xPercentage[zoomedStart]);
const auto xPoint = leftStart + width * xPointPercentage; const auto xPoint = leftStart + width * xPointPercentage;

View File

@ -205,6 +205,7 @@ PRIVATE
statistics/statistics_data_deserialize.h statistics/statistics_data_deserialize.h
statistics/statistics_format_values.cpp statistics/statistics_format_values.cpp
statistics/statistics_format_values.h statistics/statistics_format_values.h
statistics/statistics_types.h
statistics/view/abstract_chart_view.cpp statistics/view/abstract_chart_view.cpp
statistics/view/abstract_chart_view.h statistics/view/abstract_chart_view.h
statistics/view/bar_chart_view.cpp statistics/view/bar_chart_view.cpp