mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-07 15:43:55 +02:00
Added initial animation and selection to stack chart view.
This commit is contained in:
parent
35ff45971f
commit
b606a7b21d
4 changed files with 240 additions and 60 deletions
|
@ -706,7 +706,17 @@ void ChartWidget::ChartAnimationController::tick(
|
||||||
const auto footerMinFinished = isFinished(_animationValueFooterHeightMin);
|
const auto footerMinFinished = isFinished(_animationValueFooterHeightMin);
|
||||||
const auto footerMaxFinished = isFinished(_animationValueFooterHeightMax);
|
const auto footerMaxFinished = isFinished(_animationValueFooterHeightMax);
|
||||||
|
|
||||||
chartView->tick(now);
|
// chartView->tick(now);
|
||||||
|
{
|
||||||
|
constexpr auto kDtHeightSpeed1 = 0.03 * 2;
|
||||||
|
constexpr auto kDtHeightSpeed2 = 0.03 * 2;
|
||||||
|
constexpr auto kDtHeightSpeed3 = 0.045 * 2;
|
||||||
|
if (_dtHeight.current.max > 0 && _dtHeight.current.max < 1) {
|
||||||
|
chartView->update(_dtHeight.current.max);
|
||||||
|
} else {
|
||||||
|
chartView->tick(now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (xFinished
|
if (xFinished
|
||||||
&& yFinished
|
&& yFinished
|
||||||
|
@ -1262,7 +1272,8 @@ void ChartWidget::setupFilterButtons() {
|
||||||
void ChartWidget::setChartData(Data::StatisticalChart chartData) {
|
void ChartWidget::setChartData(Data::StatisticalChart chartData) {
|
||||||
_chartData = std::move(chartData);
|
_chartData = std::move(chartData);
|
||||||
|
|
||||||
_chartView = CreateChartView(ChartViewType::Linear);
|
// _chartView = CreateChartView(ChartViewType::Linear);
|
||||||
|
_chartView = CreateChartView(ChartViewType::Stack);
|
||||||
|
|
||||||
setupDetails();
|
setupDetails();
|
||||||
setupFilterButtons();
|
setupFilterButtons();
|
||||||
|
|
|
@ -58,6 +58,8 @@ public:
|
||||||
Limits xIndices) = 0;
|
Limits xIndices) = 0;
|
||||||
|
|
||||||
virtual void tick(crl::time now) = 0;
|
virtual void tick(crl::time now) = 0;
|
||||||
|
virtual void update(float64 dt) {
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,38 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "statistics/view/stack_chart_view.h"
|
#include "statistics/view/stack_chart_view.h"
|
||||||
|
|
||||||
|
#include "ui/effects/animation_value_f.h"
|
||||||
#include "data/data_statistics.h"
|
#include "data/data_statistics.h"
|
||||||
|
#include "ui/painter.h"
|
||||||
|
|
||||||
namespace Statistic {
|
namespace Statistic {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kAlphaDuration = float64(200);
|
||||||
|
|
||||||
|
struct LeftStartAndStep final {
|
||||||
|
float64 start = 0.;
|
||||||
|
float64 step = 0.;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] LeftStartAndStep ComputeLeftStartAndStep(
|
||||||
|
const Data::StatisticalChart &chartData,
|
||||||
|
const Limits &xPercentageLimits,
|
||||||
|
const QRect &rect,
|
||||||
|
float64 xIndexStart) {
|
||||||
|
const auto fullWidth = rect.width()
|
||||||
|
/ (xPercentageLimits.max - xPercentageLimits.min);
|
||||||
|
const auto offset = fullWidth * xPercentageLimits.min;
|
||||||
|
const auto p = (chartData.xPercentage.size() < 2)
|
||||||
|
? 1.
|
||||||
|
: chartData.xPercentage[1] * fullWidth;
|
||||||
|
const auto w = chartData.xPercentage[1] * (fullWidth - p);
|
||||||
|
const auto leftStart = rect.x()
|
||||||
|
+ chartData.xPercentage[xIndexStart] * (fullWidth - p)
|
||||||
|
- offset;
|
||||||
|
return { leftStart, w };
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
StackChartView::StackChartView() = default;
|
StackChartView::StackChartView() = default;
|
||||||
|
@ -25,66 +53,96 @@ void StackChartView::paint(
|
||||||
const Limits &heightLimits,
|
const Limits &heightLimits,
|
||||||
const QRect &rect,
|
const QRect &rect,
|
||||||
bool footer) {
|
bool footer) {
|
||||||
const auto localStart = std::max(0, int(xIndices.min) - 2);
|
constexpr auto kOffset = float64(2);
|
||||||
const auto localEnd = std::min(
|
_lastPaintedXIndices = {
|
||||||
int(chartData.xPercentage.size() - 1),
|
float64(std::max(0., xIndices.min - kOffset)),
|
||||||
int(xIndices.max) + 2);
|
float64(std::min(
|
||||||
|
float64(chartData.xPercentage.size() - 1),
|
||||||
|
xIndices.max + kOffset)),
|
||||||
|
};
|
||||||
|
|
||||||
const auto fullWidth = rect.width() / (xPercentageLimits.max - xPercentageLimits.min);
|
StackChartView::paint(
|
||||||
const auto offset = fullWidth * xPercentageLimits.min;
|
p,
|
||||||
const auto pp = (chartData.xPercentage.size() < 2)
|
chartData,
|
||||||
? 1.
|
xPercentageLimits,
|
||||||
: chartData.xPercentage[1] * fullWidth;
|
heightLimits,
|
||||||
const auto w = chartData.xPercentage[1] * (fullWidth - pp);
|
rect,
|
||||||
// const auto w = rect.width() / float64(localEnd - localStart);
|
footer);
|
||||||
const auto r = w / 2.;
|
}
|
||||||
const auto leftStart = chartData.xPercentage[localStart] * (fullWidth - pp) - offset + rect.x();
|
|
||||||
|
|
||||||
auto paths = std::vector<QPainterPath>();
|
void StackChartView::paint(
|
||||||
paths.resize(chartData.lines.size());
|
QPainter &p,
|
||||||
|
const Data::StatisticalChart &chartData,
|
||||||
|
const Limits &xPercentageLimits,
|
||||||
|
const Limits &heightLimits,
|
||||||
|
const QRect &rect,
|
||||||
|
bool footer) {
|
||||||
|
const auto &[localStart, localEnd] = _lastPaintedXIndices;
|
||||||
|
const auto &[leftStart, w] = ComputeLeftStartAndStep(
|
||||||
|
chartData,
|
||||||
|
xPercentageLimits,
|
||||||
|
rect,
|
||||||
|
localStart);
|
||||||
|
|
||||||
for (auto i = localStart; i <= localEnd; i++) {
|
const auto opacity = p.opacity();
|
||||||
auto chartPoints = QPolygonF();
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
|
||||||
const auto xPoint = rect.width()
|
auto bottoms = std::vector<float64>(localEnd - localStart + 1, -rect.y());
|
||||||
* ((chartData.xPercentage[i] - xPercentageLimits.min)
|
auto selectedBottoms = std::vector<float64>();
|
||||||
/ (xPercentageLimits.max - xPercentageLimits.min));
|
const auto hasSelectedXIndex = !footer && (_lastSelectedXIndex >= 0);
|
||||||
auto bottom = float64(-rect.y());
|
if (hasSelectedXIndex) {
|
||||||
const auto left = leftStart + (i - localStart) * w;
|
selectedBottoms = std::vector<float64>(chartData.lines.size(), 0);
|
||||||
|
constexpr auto kSelectedAlpha = 0.5;
|
||||||
for (auto j = 0; j < chartData.lines.size(); j++) {
|
p.setOpacity(
|
||||||
const auto &line = chartData.lines[j];
|
anim::interpolateF(1.0, kSelectedAlpha, _lastSelectedXProgress));
|
||||||
if (line.y[i] <= 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto yPercentage = (line.y[i] - heightLimits.min)
|
|
||||||
/ float64(heightLimits.max - heightLimits.min);
|
|
||||||
const auto yPoint = yPercentage * rect.height() * alpha(line.id);
|
|
||||||
// const auto column = QRectF(
|
|
||||||
// xPoint - r,
|
|
||||||
// rect.height() - bottom - yPoint,
|
|
||||||
// w,
|
|
||||||
// yPoint);
|
|
||||||
const auto column = QRectF(
|
|
||||||
left,
|
|
||||||
rect.height() - bottom - yPoint,
|
|
||||||
w,
|
|
||||||
yPoint);
|
|
||||||
paths[j].addRect(column);
|
|
||||||
|
|
||||||
// p.setPen(Qt::NoPen);
|
|
||||||
// p.setBrush(line.color);
|
|
||||||
// p.setOpacity(0.3);
|
|
||||||
// // p.setOpacity(1.);
|
|
||||||
// p.drawRect(column);
|
|
||||||
// p.setOpacity(1.);
|
|
||||||
bottom += yPoint;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p.setPen(Qt::NoPen);
|
for (auto i = 0; i < chartData.lines.size(); i++) {
|
||||||
for (auto i = 0; i < paths.size(); i++) {
|
const auto &line = chartData.lines[i];
|
||||||
p.fillPath(paths[i], chartData.lines[i].color);
|
auto path = QPainterPath();
|
||||||
|
for (auto x = localStart; x <= localEnd; x++) {
|
||||||
|
if (line.y[x] <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto xPoint = rect.width()
|
||||||
|
* ((chartData.xPercentage[x] - xPercentageLimits.min)
|
||||||
|
/ (xPercentageLimits.max - xPercentageLimits.min));
|
||||||
|
const auto yPercentage = (line.y[x] - heightLimits.min)
|
||||||
|
/ float64(heightLimits.max - heightLimits.min);
|
||||||
|
const auto yPoint = yPercentage * rect.height() * alpha(line.id);
|
||||||
|
|
||||||
|
const auto bottomIndex = x - localStart;
|
||||||
|
const auto column = QRectF(
|
||||||
|
leftStart + (x - localStart) * w,
|
||||||
|
rect.height() - bottoms[bottomIndex] - yPoint,
|
||||||
|
w,
|
||||||
|
yPoint);
|
||||||
|
if (hasSelectedXIndex && (x == _lastSelectedXIndex)) {
|
||||||
|
selectedBottoms[i] = column.y();
|
||||||
|
} else {
|
||||||
|
path.addRect(column);
|
||||||
|
}
|
||||||
|
bottoms[bottomIndex] += yPoint;
|
||||||
|
}
|
||||||
|
p.fillPath(path, line.color);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i = 0; i < selectedBottoms.size(); i++) {
|
||||||
|
p.setOpacity(opacity);
|
||||||
|
if (selectedBottoms[i] <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto &line = chartData.lines[i];
|
||||||
|
const auto yPercentage = (line.y[_lastSelectedXIndex] - heightLimits.min)
|
||||||
|
/ float64(heightLimits.max - heightLimits.min);
|
||||||
|
const auto yPoint = yPercentage * rect.height() * alpha(line.id);
|
||||||
|
|
||||||
|
const auto column = QRectF(
|
||||||
|
leftStart + (_lastSelectedXIndex - localStart) * w,
|
||||||
|
selectedBottoms[i],
|
||||||
|
w,
|
||||||
|
yPoint);
|
||||||
|
p.fillRect(column, line.color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +154,16 @@ void StackChartView::paintSelectedXIndex(
|
||||||
const QRect &rect,
|
const QRect &rect,
|
||||||
int selectedXIndex,
|
int selectedXIndex,
|
||||||
float64 progress) {
|
float64 progress) {
|
||||||
|
_lastSelectedXIndex = selectedXIndex;
|
||||||
|
_lastSelectedXProgress = progress;
|
||||||
|
[[maybe_unused]] const auto o = ScopedPainterOpacity(p, progress);
|
||||||
|
StackChartView::paint(
|
||||||
|
p,
|
||||||
|
chartData,
|
||||||
|
xPercentageLimits,
|
||||||
|
heightLimits,
|
||||||
|
rect,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
int StackChartView::findXIndexByPosition(
|
int StackChartView::findXIndexByPosition(
|
||||||
|
@ -103,22 +171,60 @@ int StackChartView::findXIndexByPosition(
|
||||||
const Limits &xPercentageLimits,
|
const Limits &xPercentageLimits,
|
||||||
const QRect &rect,
|
const QRect &rect,
|
||||||
float64 xPos) {
|
float64 xPos) {
|
||||||
return 0;
|
if (xPos < rect.x()) {
|
||||||
|
return 0;
|
||||||
|
} else if (xPos > (rect.x() + rect.width())) {
|
||||||
|
return chartData.xPercentage.size() - 1;
|
||||||
|
}
|
||||||
|
const auto &[localStart, localEnd] = _lastPaintedXIndices;
|
||||||
|
const auto &[leftStart, w] = ComputeLeftStartAndStep(
|
||||||
|
chartData,
|
||||||
|
xPercentageLimits,
|
||||||
|
rect,
|
||||||
|
localStart);
|
||||||
|
|
||||||
|
for (auto i = 0; i < chartData.lines.size(); i++) {
|
||||||
|
const auto &line = chartData.lines[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 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StackChartView::setEnabled(int id, bool enabled, crl::time now) {
|
void StackChartView::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,
|
||||||
|
.anim = anim::value(enabled ? 0. : 1., enabled ? 1. : 0.),
|
||||||
|
};
|
||||||
|
} else if (it->second.enabled != enabled) {
|
||||||
|
auto &entry = it->second;
|
||||||
|
entry.enabled = enabled;
|
||||||
|
entry.startedAt = now;
|
||||||
|
entry.anim.start(enabled ? 1. : 0.);
|
||||||
|
}
|
||||||
|
_isFinished = false;
|
||||||
|
_cachedHeightLimits = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StackChartView::isFinished() const {
|
bool StackChartView::isFinished() const {
|
||||||
return true;
|
return _isFinished;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StackChartView::isEnabled(int id) const {
|
bool StackChartView::isEnabled(int id) const {
|
||||||
return true;
|
const auto it = _entries.find(id);
|
||||||
|
return (it == end(_entries)) ? true : it->second.enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
float64 StackChartView::alpha(int id) const {
|
float64 StackChartView::alpha(int id) const {
|
||||||
return 1.0;
|
const auto it = _entries.find(id);
|
||||||
|
return (it == end(_entries)) ? 1. : it->second.alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
AbstractChartView::HeightLimits StackChartView::heightLimits(
|
AbstractChartView::HeightLimits StackChartView::heightLimits(
|
||||||
|
@ -153,6 +259,42 @@ AbstractChartView::HeightLimits StackChartView::heightLimits(
|
||||||
}
|
}
|
||||||
|
|
||||||
void StackChartView::tick(crl::time now) {
|
void StackChartView::tick(crl::time now) {
|
||||||
|
for (auto &[id, entry] : _entries) {
|
||||||
|
const auto dt = std::min(
|
||||||
|
(now - entry.startedAt) / kAlphaDuration,
|
||||||
|
1.);
|
||||||
|
if (dt > 1.) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return update(dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StackChartView::update(float64 dt) {
|
||||||
|
auto finishedCount = 0;
|
||||||
|
auto idsToRemove = std::vector<int>();
|
||||||
|
for (auto &[id, entry] : _entries) {
|
||||||
|
if (!entry.startedAt) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
entry.anim.update(dt, anim::linear);
|
||||||
|
const auto progress = entry.anim.current();
|
||||||
|
entry.alpha = std::clamp(
|
||||||
|
progress,
|
||||||
|
0.,
|
||||||
|
1.);
|
||||||
|
if (entry.alpha == 1.) {
|
||||||
|
idsToRemove.push_back(id);
|
||||||
|
}
|
||||||
|
if (entry.anim.current() == entry.anim.to()) {
|
||||||
|
finishedCount++;
|
||||||
|
entry.anim.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_isFinished = (finishedCount == _entries.size());
|
||||||
|
for (const auto &id : idsToRemove) {
|
||||||
|
_entries.remove(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Statistic
|
} // namespace Statistic
|
||||||
|
|
|
@ -10,6 +10,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/view/abstract_chart_view.h"
|
#include "statistics/view/abstract_chart_view.h"
|
||||||
|
#include "ui/effects/animation_value.h"
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
struct StatisticalChart;
|
struct StatisticalChart;
|
||||||
|
@ -58,14 +59,38 @@ public:
|
||||||
Limits xIndices) override;
|
Limits xIndices) override;
|
||||||
|
|
||||||
void tick(crl::time now) override;
|
void tick(crl::time now) override;
|
||||||
|
void update(float64 dt) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void paint(
|
||||||
|
QPainter &p,
|
||||||
|
const Data::StatisticalChart &chartData,
|
||||||
|
const Limits &xPercentageLimits,
|
||||||
|
const Limits &heightLimits,
|
||||||
|
const QRect &rect,
|
||||||
|
bool footer);
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
Limits full;
|
Limits full;
|
||||||
std::vector<int> ySum;
|
std::vector<int> ySum;
|
||||||
SegmentTree ySumSegmentTree;
|
SegmentTree ySumSegmentTree;
|
||||||
} _cachedHeightLimits;
|
} _cachedHeightLimits;
|
||||||
|
|
||||||
|
Limits _lastPaintedXIndices;
|
||||||
|
int _lastSelectedXIndex = -1;
|
||||||
|
float64 _lastSelectedXProgress = 0;
|
||||||
|
|
||||||
|
struct Entry final {
|
||||||
|
bool enabled = false;
|
||||||
|
crl::time startedAt = 0;
|
||||||
|
float64 alpha = 1.;
|
||||||
|
anim::value anim;
|
||||||
|
bool disabled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
base::flat_map<int, Entry> _entries;
|
||||||
|
bool _isFinished = true;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Statistic
|
} // namespace Statistic
|
||||||
|
|
Loading…
Add table
Reference in a new issue