Added animation to pie chart while changing its parts.
This commit is contained in:
parent
e9496fb612
commit
4d269f6e97
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue