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());
auto hasPositiveValues = false;
const auto parts = _maxPercentageWidth
? PiePartsPercentage(
? PiePartsPercentageByIndices(
_chartData,
nullptr,
{ float64(xIndex), float64(xIndex) }).parts

View File

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

View File

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

View File

@ -87,6 +87,57 @@ inline float64 InterpolationRatio(float64 from, float64 to, float64 result) {
} // 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() {
_piePartAnimation.init([=] { AbstractChartView::update(); });
}
@ -185,7 +236,7 @@ void StackLinearChartView::saveZoomRange(const PaintContext &c) {
}
void StackLinearChartView::savePieTextParts(const PaintContext &c) {
auto data = PiePartsPercentage(
auto data = PiePartsPercentageByIndices(
c.chartData,
linesFilterController(),
_transition.zoomedInRangeXIndices);
@ -504,14 +555,33 @@ void StackLinearChartView::paintZoomed(QPainter &p, const PaintContext &c) {
return;
}
const auto wasZoomedInRangeXIndices = _transition.zoomedInRangeXIndices;
saveZoomRange(c);
const auto partsData = PiePartsPercentage(
const auto &[zoomedStart, zoomedEnd] = _transition.zoomedInRangeXIndices;
const auto partsData = PiePartsPercentageByIndices(
c.chartData,
linesFilterController(),
_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;
const auto &parts = partsData.parts;
applyParts(parts);
applyParts(partsData.parts);
const auto &parts = _changingPieController.isFinished()
? partsData.parts
: _changingPieController.current().parts;
p.fillRect(c.rect + QMargins(0, 0, 0, st::lineWidth), st::boxBg);
const auto center = QPointF(c.rect.center());
@ -547,14 +617,12 @@ void StackLinearChartView::paintZoomed(QPainter &p, const PaintContext &c) {
selectedLineIndex = k;
}
}
if (_piePartController.isFinished()) {
if (_piePartController.isFinished() && _changingPieController.isFinished()) {
_piePartAnimation.stop();
}
paintPieText(p, c);
if (selectedLineIndex >= 0) {
const auto &[zoomedStart, zoomedEnd] =
_transition.zoomedInRangeXIndices;
const auto &line = c.chartData.lines[selectedLineIndex];
auto sum = 0;
for (auto i = zoomedStart; i <= zoomedEnd; i++) {
@ -633,11 +701,13 @@ void StackLinearChartView::paintZoomedFooter(
}
void StackLinearChartView::paintPieText(QPainter &p, const PaintContext &c) {
constexpr auto kMinPercentage = 0.03;
constexpr auto kMinPercentage = 0.039;
if (_transition.progress == 1.) {
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 side = (c.rect.width() / 2.) * kCircleSizeRatio;

View File

@ -121,8 +121,28 @@ private:
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;
ChangingPiePartController _changingPieController;
Ui::Animations::Basic _piePartAnimation;
bool _pieHasSinglePart = false;
};