Added animation to pie chart while changing its parts.

This commit is contained in:
23rd 2023-10-11 18:12:11 +03:00 committed by John Preston
parent e9496fb612
commit 4d269f6e97
5 changed files with 134 additions and 30 deletions

View File

@ -279,7 +279,7 @@ void PointDetailsWidget::setXIndex(int xIndex) {
_lines.reserve(_chartData.lines.size()); _lines.reserve(_chartData.lines.size());
auto hasPositiveValues = false; auto hasPositiveValues = false;
const auto parts = _maxPercentageWidth const auto parts = _maxPercentageWidth
? PiePartsPercentage( ? PiePartsPercentageByIndices(
_chartData, _chartData,
nullptr, nullptr,
{ float64(xIndex), float64(xIndex) }).parts { float64(xIndex), float64(xIndex) }).parts

View File

@ -14,25 +14,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Statistic { namespace Statistic {
PiePartData PiePartsPercentage( PiePartData PiePartsPercentage(
const Data::StatisticalChart &chartData, const std::vector<float64> &sums,
const std::shared_ptr<LinesFilterController> &linesFilter, float64 totalSum,
const Limits &xIndices) { bool round) {
auto result = PiePartData(); auto result = PiePartData();
result.parts.reserve(chartData.lines.size()); result.parts.reserve(sums.size());
auto sums = std::vector<float64>();
sums.reserve(chartData.lines.size());
auto totalSum = 0.;
for (const auto &line : chartData.lines) {
auto sum = 0;
for (auto i = xIndices.min; i <= xIndices.max; i++) {
sum += line.y[i];
}
if (linesFilter) {
sum *= linesFilter->alpha(line.id);
}
totalSum += sum;
sums.push_back(sum);
}
auto stackedPercentage = 0.; auto stackedPercentage = 0.;
auto sumPercDiffs = 0.; auto sumPercDiffs = 0.;
@ -46,7 +32,9 @@ PiePartData PiePartsPercentage(
constexpr auto kPerChar = '%'; constexpr auto kPerChar = '%';
for (auto k = 0; k < sums.size(); k++) { for (auto k = 0; k < sums.size(); k++) {
const auto rawPercentage = totalSum ? (sums[k] / totalSum) : 0.; const auto rawPercentage = totalSum ? (sums[k] / totalSum) : 0.;
const auto rounded = 0.01 * std::round(rawPercentage * 100.); const auto rounded = round
? (0.01 * std::round(rawPercentage * 100.))
: rawPercentage;
roundedPercentagesSum += rounded; roundedPercentagesSum += rounded;
const auto diff = rawPercentage - rounded; const auto diff = rawPercentage - rounded;
sumPercDiffs += diff; sumPercDiffs += diff;
@ -68,7 +56,7 @@ PiePartData PiePartsPercentage(
}); });
result.pieHasSinglePart |= (rounded == 1.); result.pieHasSinglePart |= (rounded == 1.);
} }
{ if (round) {
const auto index = (roundedPercentagesSum > 1.) const auto index = (roundedPercentagesSum > 1.)
? maxPercDiffIndex ? maxPercDiffIndex
: minPercDiffIndex; : minPercDiffIndex;
@ -85,4 +73,25 @@ PiePartData PiePartsPercentage(
return result; return result;
} }
PiePartData PiePartsPercentageByIndices(
const Data::StatisticalChart &chartData,
const std::shared_ptr<LinesFilterController> &linesFilter,
const Limits &xIndices) {
auto sums = std::vector<float64>();
sums.reserve(chartData.lines.size());
auto totalSum = 0.;
for (const auto &line : chartData.lines) {
auto sum = 0;
for (auto i = xIndices.min; i <= xIndices.max; i++) {
sum += line.y[i];
}
if (linesFilter) {
sum *= linesFilter->alpha(line.id);
}
totalSum += sum;
sums.push_back(sum);
}
return PiePartsPercentage(sums, totalSum, true);
}
} // namespace Statistic } // namespace Statistic

View File

@ -27,6 +27,11 @@ struct PiePartData final {
}; };
[[nodiscard]] PiePartData PiePartsPercentage( [[nodiscard]] PiePartData PiePartsPercentage(
const std::vector<float64> &sums,
float64 totalSum,
bool round);
[[nodiscard]] PiePartData PiePartsPercentageByIndices(
const Data::StatisticalChart &chartData, const Data::StatisticalChart &chartData,
const std::shared_ptr<LinesFilterController> &linesFilter, const std::shared_ptr<LinesFilterController> &linesFilter,
const Limits &xIndices); const Limits &xIndices);

View File

@ -87,6 +87,57 @@ inline float64 InterpolationRatio(float64 from, float64 to, float64 result) {
} // namespace } // namespace
void StackLinearChartView::ChangingPiePartController::setParts(
const std::vector<PiePartData::Part> &was,
const std::vector<PiePartData::Part> &now) {
if (_animValues.size() != was.size()) {
_animValues = std::vector<anim::value>(was.size(), anim::value());
for (auto i = 0; i < was.size(); i++) {
_animValues[i] = anim::value(
was[i].roundedPercentage,
now[i].roundedPercentage);
}
} else {
for (auto i = 0; i < was.size(); i++) {
_animValues[i] = anim::value(
_animValues[i].current(),
now[i].roundedPercentage);
}
}
_startedAt = crl::now();
_isFinished = false;
}
void StackLinearChartView::ChangingPiePartController::update() {
const auto progress = std::clamp(
(crl::now() - _startedAt) / float64(st::slideWrapDuration),
0.,
1.);
auto totalSum = 0.;
auto finished = true;
auto result = std::vector<float64>();
result.reserve(_animValues.size());
for (auto &anim : _animValues) {
anim.update(progress, anim::easeOutCubic);
if (finished && (anim.current() != anim.to())) {
finished = false;
}
const auto value = anim.current();
result.push_back(value);
totalSum += value;
}
_isFinished = finished;
_current = PiePartsPercentage(result, totalSum, false);
}
PiePartData StackLinearChartView::ChangingPiePartController::current() const {
return _current;
}
bool StackLinearChartView::ChangingPiePartController::isFinished() const {
return _isFinished;
}
StackLinearChartView::StackLinearChartView() { StackLinearChartView::StackLinearChartView() {
_piePartAnimation.init([=] { AbstractChartView::update(); }); _piePartAnimation.init([=] { AbstractChartView::update(); });
} }
@ -185,7 +236,7 @@ void StackLinearChartView::saveZoomRange(const PaintContext &c) {
} }
void StackLinearChartView::savePieTextParts(const PaintContext &c) { void StackLinearChartView::savePieTextParts(const PaintContext &c) {
auto data = PiePartsPercentage( auto data = PiePartsPercentageByIndices(
c.chartData, c.chartData,
linesFilterController(), linesFilterController(),
_transition.zoomedInRangeXIndices); _transition.zoomedInRangeXIndices);
@ -504,14 +555,33 @@ void StackLinearChartView::paintZoomed(QPainter &p, const PaintContext &c) {
return; return;
} }
const auto wasZoomedInRangeXIndices = _transition.zoomedInRangeXIndices;
saveZoomRange(c); saveZoomRange(c);
const auto partsData = PiePartsPercentage( const auto &[zoomedStart, zoomedEnd] = _transition.zoomedInRangeXIndices;
const auto partsData = PiePartsPercentageByIndices(
c.chartData, c.chartData,
linesFilterController(), linesFilterController(),
_transition.zoomedInRangeXIndices); _transition.zoomedInRangeXIndices);
const auto xIndicesChanged = (wasZoomedInRangeXIndices.min != zoomedStart)
|| (wasZoomedInRangeXIndices.max != zoomedEnd);
if (xIndicesChanged) {
const auto wasParts = PiePartsPercentageByIndices(
c.chartData,
linesFilterController(),
wasZoomedInRangeXIndices);
_changingPieController.setParts(wasParts.parts, partsData.parts);
if (!_piePartAnimation.animating()) {
_piePartAnimation.start();
}
}
if (!_changingPieController.isFinished()) {
_changingPieController.update();
}
_pieHasSinglePart = partsData.pieHasSinglePart; _pieHasSinglePart = partsData.pieHasSinglePart;
const auto &parts = partsData.parts; applyParts(partsData.parts);
applyParts(parts); const auto &parts = _changingPieController.isFinished()
? partsData.parts
: _changingPieController.current().parts;
p.fillRect(c.rect + QMargins(0, 0, 0, st::lineWidth), st::boxBg); p.fillRect(c.rect + QMargins(0, 0, 0, st::lineWidth), st::boxBg);
const auto center = QPointF(c.rect.center()); const auto center = QPointF(c.rect.center());
@ -547,14 +617,12 @@ void StackLinearChartView::paintZoomed(QPainter &p, const PaintContext &c) {
selectedLineIndex = k; selectedLineIndex = k;
} }
} }
if (_piePartController.isFinished()) { if (_piePartController.isFinished() && _changingPieController.isFinished()) {
_piePartAnimation.stop(); _piePartAnimation.stop();
} }
paintPieText(p, c); paintPieText(p, c);
if (selectedLineIndex >= 0) { if (selectedLineIndex >= 0) {
const auto &[zoomedStart, zoomedEnd] =
_transition.zoomedInRangeXIndices;
const auto &line = c.chartData.lines[selectedLineIndex]; const auto &line = c.chartData.lines[selectedLineIndex];
auto sum = 0; auto sum = 0;
for (auto i = zoomedStart; i <= zoomedEnd; i++) { for (auto i = zoomedStart; i <= zoomedEnd; i++) {
@ -633,11 +701,13 @@ void StackLinearChartView::paintZoomedFooter(
} }
void StackLinearChartView::paintPieText(QPainter &p, const PaintContext &c) { void StackLinearChartView::paintPieText(QPainter &p, const PaintContext &c) {
constexpr auto kMinPercentage = 0.03; constexpr auto kMinPercentage = 0.039;
if (_transition.progress == 1.) { if (_transition.progress == 1.) {
savePieTextParts(c); savePieTextParts(c);
} }
const auto &parts = _transition.textParts; const auto &parts = _changingPieController.isFinished()
? _transition.textParts
: _changingPieController.current().parts;
const auto center = QPointF(c.rect.center()); const auto center = QPointF(c.rect.center());
const auto side = (c.rect.width() / 2.) * kCircleSizeRatio; const auto side = (c.rect.width() / 2.) * kCircleSizeRatio;

View File

@ -121,8 +121,28 @@ private:
LineId _selected = -1; LineId _selected = -1;
}; };
class ChangingPiePartController final {
public:
void setParts(
const std::vector<PiePartData::Part> &was,
const std::vector<PiePartData::Part> &now);
void update();
[[nodiscard]] PiePartData current() const;
[[nodiscard]] bool isFinished() const;
private:
crl::time _startedAt = 0;;
std::vector<anim::value> _animValues;
PiePartData _current;
bool _isFinished = true;
};
PiePartController _piePartController; PiePartController _piePartController;
ChangingPiePartController _changingPieController;
Ui::Animations::Basic _piePartAnimation; Ui::Animations::Basic _piePartAnimation;
bool _pieHasSinglePart = false; bool _pieHasSinglePart = false;
}; };