Added ability to use BarChartView as non-stack as well.

This commit is contained in:
23rd 2023-11-17 03:28:33 +03:00 committed by John Preston
parent b7346c203a
commit 44f6280d0a
6 changed files with 129 additions and 16 deletions

View file

@ -189,11 +189,11 @@ void FillStatistic(
addChart(
stats.channel.viewCountBySourceGraph,
tr::lng_chart_title_view_count_by_source(),
Type::Stack);
Type::StackBar);
addChart(
stats.channel.joinBySourceGraph,
tr::lng_chart_title_join_by_source(),
Type::Stack);
Type::StackBar);
addChart(
stats.channel.languageGraph,
tr::lng_chart_title_language(),
@ -218,7 +218,7 @@ void FillStatistic(
addChart(
stats.supergroup.joinBySourceGraph,
tr::lng_chart_title_group_join_by_source(),
Type::Stack);
Type::StackBar);
addChart(
stats.supergroup.languageGraph,
tr::lng_chart_title_group_language(),
@ -226,7 +226,7 @@ void FillStatistic(
addChart(
stats.supergroup.messageContentGraph,
tr::lng_chart_title_group_message_content(),
Type::Stack);
Type::StackBar);
addChart(
stats.supergroup.actionGraph,
tr::lng_chart_title_group_action(),

View file

@ -1462,7 +1462,7 @@ void ChartWidget::setChartData(
_chartView = CreateChartView(type);
_chartView->setLinesFilterController(_linesFilterController);
_rulersView.setChartData(_chartData, type, _linesFilterController);
_areRulersAbove = (type == ChartViewType::Stack);
_areRulersAbove = (type == ChartViewType::StackBar);
if (_chartData.isFooterHidden) {
_footer->hide();

View file

@ -18,7 +18,8 @@ struct Limits final {
enum class ChartViewType {
Linear,
Stack,
Bar,
StackBar,
DoubleLinear,
StackLinear,
};

View file

@ -12,10 +12,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "statistics/view/stack_chart_common.h"
#include "ui/effects/animation_value_f.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "styles/style_statistics.h"
namespace Statistic {
BarChartView::BarChartView() = default;
BarChartView::BarChartView(bool isStack)
: _isStack(isStack)
, _cachedLineRatios(false) {
}
BarChartView::~BarChartView() = default;
void BarChartView::paint(QPainter &p, const PaintContext &c) {
@ -40,6 +46,8 @@ void BarChartView::paintChartAndSelected(
c.rect,
localStart);
p.setClipRect(0, 0, c.rect.width() * 2, rect::bottom(c.rect));
const auto opacity = p.opacity();
auto hq = PainterHighQualityEnabler(p);
@ -47,7 +55,9 @@ void BarChartView::paintChartAndSelected(
localEnd - localStart + 1,
-c.rect.y());
auto selectedBottoms = std::vector<float64>();
const auto hasSelectedXIndex = !c.footer && (_lastSelectedXIndex >= 0);
const auto hasSelectedXIndex = _isStack
&& !c.footer
&& (_lastSelectedXIndex >= 0);
if (hasSelectedXIndex) {
selectedBottoms = std::vector<float64>(c.chartData.lines.size(), 0);
constexpr auto kSelectedAlpha = 0.5;
@ -77,10 +87,27 @@ void BarChartView::paintChartAndSelected(
if (hasSelectedXIndex && (x == _lastSelectedXIndex)) {
selectedBottoms[i] = column.y();
}
path.addRect(column);
bottoms[bottomIndex] += yPoint;
if (_isStack) {
path.addRect(column);
bottoms[bottomIndex] += yPoint;
} else {
if (path.isEmpty()) {
path.moveTo(column.topLeft());
} else {
path.lineTo(column.topLeft());
}
if (x == localEnd) {
path.lineTo(c.rect.width(), column.y());
} else {
path.lineTo(rect::right(column), column.y());
}
}
}
if (_isStack) {
p.fillPath(path, line.color);
} else {
p.strokePath(path, line.color);
}
p.fillPath(path, line.color);
}
for (auto i = 0; i < selectedBottoms.size(); i++) {
@ -103,6 +130,8 @@ void BarChartView::paintChartAndSelected(
yPoint);
p.fillRect(column, line.color);
}
p.setClipping(false);
}
void BarChartView::paintSelectedXIndex(
@ -113,8 +142,73 @@ void BarChartView::paintSelectedXIndex(
const auto was = _lastSelectedXIndex;
_lastSelectedXIndex = selectedXIndex;
_lastSelectedXProgress = progress;
if ((_lastSelectedXIndex >= 0) || (was >= 0)) {
BarChartView::paintChartAndSelected(p, c);
if (_isStack) {
if ((_lastSelectedXIndex >= 0) || (was >= 0)) {
BarChartView::paintChartAndSelected(p, c);
}
} else {
const auto linesFilter = linesFilterController();
auto hq = PainterHighQualityEnabler(p);
auto o = ScopedPainterOpacity(p, progress);
p.setBrush(st::boxBg);
const auto r = st::statisticsDetailsDotRadius;
const auto i = selectedXIndex;
const auto isSameToken = _selectedPoints.isSame(selectedXIndex, c);
auto linePainted = false;
const auto &[localStart, localEnd] = _lastPaintedXIndices;
const auto &[leftStart, w] = ComputeLeftStartAndStep(
c.chartData,
c.xPercentageLimits,
c.rect,
localStart);
for (auto i = 0; i < c.chartData.lines.size(); i++) {
const auto &line = c.chartData.lines[i];
const auto lineAlpha = linesFilter->alpha(line.id);
const auto useCache = isSameToken
|| (lineAlpha < 1. && !linesFilter->isEnabled(line.id));
if (!useCache) {
// Calculate.
const auto x = _lastSelectedXIndex;
const auto yPercentage = (line.y[x] - c.heightLimits.min)
/ float64(c.heightLimits.max - c.heightLimits.min);
const auto yPoint = (1. - yPercentage) * c.rect.height();
const auto bottomIndex = x - localStart;
const auto column = QRectF(
leftStart + (x - localStart) * w,
c.rect.height() - 0 - yPoint,
w,
yPoint);
const auto xPoint = column.left() + column.width() / 2.;
_selectedPoints.points[line.id] = QPointF(xPoint, yPoint)
+ c.rect.topLeft();
}
if (!linePainted && lineAlpha) {
[[maybe_unused]] const auto o = ScopedPainterOpacity(
p,
p.opacity() * progress * kRulerLineAlpha);
const auto lineRect = QRectF(
begin(_selectedPoints.points)->second.x()
- (st::lineWidth / 2.),
c.rect.y(),
st::lineWidth,
c.rect.height());
p.fillRect(lineRect, st::boxTextFg);
linePainted = true;
}
// Paint.
auto o = ScopedPainterOpacity(p, lineAlpha * p.opacity());
p.setPen(QPen(line.color, st::statisticsChartLineWidth));
p.drawEllipse(_selectedPoints.points[line.id], r, r);
}
_selectedPoints.lastXIndex = selectedXIndex;
_selectedPoints.lastHeightLimits = c.heightLimits;
_selectedPoints.lastXLimits = c.xPercentageLimits;
}
}
@ -147,6 +241,17 @@ int BarChartView::findXIndexByPosition(
AbstractChartView::HeightLimits BarChartView::heightLimits(
Data::StatisticalChart &chartData,
Limits xIndices) {
if (!_isStack) {
if (!_cachedLineRatios) {
_cachedLineRatios.init(chartData);
}
return DefaultHeightLimits(
_cachedLineRatios,
linesFilterController(),
chartData,
xIndices);
}
_cachedHeightLimits = {};
if (_cachedHeightLimits.ySum.empty()) {
_cachedHeightLimits.ySum.reserve(chartData.x.size());

View file

@ -22,7 +22,7 @@ struct Limits;
class BarChartView final : public AbstractChartView {
public:
BarChartView();
BarChartView(bool isStack);
~BarChartView() override final;
void paint(QPainter &p, const PaintContext &c) override;
@ -52,10 +52,14 @@ private:
SegmentTree ySumSegmentTree;
} _cachedHeightLimits;
const bool _isStack;
DoubleLineRatios _cachedLineRatios; // Non-stack.
Limits _lastPaintedXIndices;
int _lastSelectedXIndex = -1;
float64 _lastSelectedXProgress = 0;
CachedSelectedPoints _selectedPoints; // Non-stack.
};
} // namespace Statistic

View file

@ -19,8 +19,11 @@ std::unique_ptr<AbstractChartView> CreateChartView(ChartViewType type) {
case ChartViewType::Linear: {
return std::make_unique<LinearChartView>(false);
} break;
case ChartViewType::Stack: {
return std::make_unique<BarChartView>();
case ChartViewType::Bar: {
return std::make_unique<BarChartView>(false);
} break;
case ChartViewType::StackBar: {
return std::make_unique<BarChartView>(true);
} break;
case ChartViewType::DoubleLinear: {
return std::make_unique<LinearChartView>(true);