mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-07 23:53:58 +02:00
Added point details widget to chart widget.
This commit is contained in:
parent
70713d5f62
commit
74aae29b64
8 changed files with 151 additions and 12 deletions
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "base/qt/qt_key_modifiers.h"
|
#include "base/qt/qt_key_modifiers.h"
|
||||||
#include "statistics/linear_chart_view.h"
|
#include "statistics/linear_chart_view.h"
|
||||||
|
#include "statistics/point_details_widget.h"
|
||||||
#include "ui/abstract_button.h"
|
#include "ui/abstract_button.h"
|
||||||
#include "ui/effects/animation_value_f.h"
|
#include "ui/effects/animation_value_f.h"
|
||||||
#include "ui/rect.h"
|
#include "ui/rect.h"
|
||||||
|
@ -20,6 +21,10 @@ namespace {
|
||||||
|
|
||||||
constexpr auto kHeightLimitsUpdateTimeout = crl::time(320);
|
constexpr auto kHeightLimitsUpdateTimeout = crl::time(320);
|
||||||
|
|
||||||
|
inline float64 InterpolationRatio(float64 from, float64 to, float64 result) {
|
||||||
|
return (result - from) / (to - from);
|
||||||
|
};
|
||||||
|
|
||||||
[[nodiscard]] int FindMaxValue(
|
[[nodiscard]] int FindMaxValue(
|
||||||
Data::StatisticalChart &chartData,
|
Data::StatisticalChart &chartData,
|
||||||
int startXIndex,
|
int startXIndex,
|
||||||
|
@ -424,6 +429,10 @@ Limits ChartWidget::ChartAnimationController::currentXLimits() const {
|
||||||
return { _animationValueXMin.current(), _animationValueXMax.current() };
|
return { _animationValueXMin.current(), _animationValueXMax.current() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Limits ChartWidget::ChartAnimationController::finalXLimits() const {
|
||||||
|
return { _animationValueXMin.to(), _animationValueXMax.to() };
|
||||||
|
}
|
||||||
|
|
||||||
Limits ChartWidget::ChartAnimationController::currentHeightLimits() const {
|
Limits ChartWidget::ChartAnimationController::currentHeightLimits() const {
|
||||||
return {
|
return {
|
||||||
_animationValueHeightMin.current(),
|
_animationValueHeightMin.current(),
|
||||||
|
@ -442,7 +451,7 @@ auto ChartWidget::ChartAnimationController::heightAnimationStarts() const
|
||||||
|
|
||||||
ChartWidget::ChartWidget(not_null<Ui::RpWidget*> parent)
|
ChartWidget::ChartWidget(not_null<Ui::RpWidget*> parent)
|
||||||
: Ui::RpWidget(parent)
|
: Ui::RpWidget(parent)
|
||||||
, _chartArea(base::make_unique_q<Ui::RpWidget>(this))
|
, _chartArea(base::make_unique_q<RpMouseWidget>(this))
|
||||||
, _footer(std::make_unique<Footer>(this))
|
, _footer(std::make_unique<Footer>(this))
|
||||||
, _animationController([=] { _chartArea->update(); }) {
|
, _animationController([=] { _chartArea->update(); }) {
|
||||||
sizeValue(
|
sizeValue(
|
||||||
|
@ -457,16 +466,31 @@ ChartWidget::ChartWidget(not_null<Ui::RpWidget*> parent)
|
||||||
0,
|
0,
|
||||||
s.width(),
|
s.width(),
|
||||||
s.height() - st::countryRowHeight * 2);
|
s.height() - st::countryRowHeight * 2);
|
||||||
}, _footer->lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
setupChartArea();
|
||||||
|
setupFooter();
|
||||||
|
|
||||||
|
resize(width(), st::confirmMaxHeight + st::countryRowHeight * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect ChartWidget::chartAreaRect() const {
|
||||||
|
return _chartArea->rect()
|
||||||
|
- QMargins(
|
||||||
|
st::lineWidth,
|
||||||
|
st::boxTextFont->height,
|
||||||
|
st::lineWidth,
|
||||||
|
st::lineWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChartWidget::setupChartArea() {
|
||||||
_chartArea->paintRequest(
|
_chartArea->paintRequest(
|
||||||
) | rpl::start_with_next([=](const QRect &r) {
|
) | rpl::start_with_next([=](const QRect &r) {
|
||||||
auto p = QPainter(_chartArea.get());
|
auto p = QPainter(_chartArea.get());
|
||||||
|
|
||||||
_animationController.tick(crl::now(), _horizontalLines);
|
_animationController.tick(crl::now(), _horizontalLines);
|
||||||
|
|
||||||
const auto chartRect = _chartArea->rect()
|
const auto chartRect = chartAreaRect();
|
||||||
- QMargins{ 0, st::boxTextFont->height, 0, st::lineWidth };
|
|
||||||
|
|
||||||
p.fillRect(r, st::boxBg);
|
p.fillRect(r, st::boxBg);
|
||||||
|
|
||||||
|
@ -474,20 +498,33 @@ ChartWidget::ChartWidget(not_null<Ui::RpWidget*> parent)
|
||||||
PaintHorizontalLines(p, horizontalLine, chartRect);
|
PaintHorizontalLines(p, horizontalLine, chartRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_details.currentX) {
|
||||||
|
const auto lineRect = QRectF(
|
||||||
|
_details.currentX - (st::lineWidth / 2.),
|
||||||
|
0,
|
||||||
|
st::lineWidth,
|
||||||
|
_chartArea->height());
|
||||||
|
p.setOpacity(1.);
|
||||||
|
p.fillRect(lineRect, st::windowSubTextFg);
|
||||||
|
}
|
||||||
|
|
||||||
if (_chartData) {
|
if (_chartData) {
|
||||||
Statistic::PaintLinearChartView(
|
Statistic::PaintLinearChartView(
|
||||||
p,
|
p,
|
||||||
_chartData,
|
_chartData,
|
||||||
_animationController.currentXLimits(),
|
_animationController.currentXLimits(),
|
||||||
_animationController.currentHeightLimits(),
|
_animationController.currentHeightLimits(),
|
||||||
chartRect);
|
chartRect,
|
||||||
|
{ _details.widget ? _details.widget->xIndex() : -1, 1. });
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &horizontalLine : _horizontalLines) {
|
for (auto &horizontalLine : _horizontalLines) {
|
||||||
PaintCaptionsToHorizontalLines(p, horizontalLine, chartRect);
|
PaintCaptionsToHorizontalLines(p, horizontalLine, chartRect);
|
||||||
}
|
}
|
||||||
}, _footer->lifetime());
|
}, _footer->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChartWidget::setupFooter() {
|
||||||
_footer->paintRequest(
|
_footer->paintRequest(
|
||||||
) | rpl::start_with_next([=, fullXLimits = Limits{ 0., 1. }] {
|
) | rpl::start_with_next([=, fullXLimits = Limits{ 0., 1. }] {
|
||||||
auto p = QPainter(_footer.get());
|
auto p = QPainter(_footer.get());
|
||||||
|
@ -499,7 +536,8 @@ ChartWidget::ChartWidget(not_null<Ui::RpWidget*> parent)
|
||||||
_chartData,
|
_chartData,
|
||||||
fullXLimits,
|
fullXLimits,
|
||||||
_footer->fullHeightLimits(),
|
_footer->fullHeightLimits(),
|
||||||
_footer->rect());
|
_footer->rect(),
|
||||||
|
{});
|
||||||
}
|
}
|
||||||
}, _footer->lifetime());
|
}, _footer->lifetime());
|
||||||
|
|
||||||
|
@ -526,12 +564,65 @@ ChartWidget::ChartWidget(not_null<Ui::RpWidget*> parent)
|
||||||
_animationController.resetAlpha();
|
_animationController.resetAlpha();
|
||||||
addHorizontalLine(_animationController.finalHeightLimits(), true);
|
addHorizontalLine(_animationController.finalHeightLimits(), true);
|
||||||
}, _footer->lifetime());
|
}, _footer->lifetime());
|
||||||
resize(width(), st::confirmMaxHeight + st::countryRowHeight * 2);
|
}
|
||||||
|
|
||||||
|
void ChartWidget::setupDetails() {
|
||||||
|
if (!_chartData) {
|
||||||
|
_details = {};
|
||||||
|
_chartArea->update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_details.widget = base::make_unique_q<PointDetailsWidget>(
|
||||||
|
this,
|
||||||
|
_chartData);
|
||||||
|
|
||||||
|
_chartArea->mouseStateChanged(
|
||||||
|
) | rpl::start_with_next([=](const RpMouseWidget::State &state) {
|
||||||
|
switch (state.mouseState) {
|
||||||
|
case QEvent::MouseButtonPress:
|
||||||
|
case QEvent::MouseMove: {
|
||||||
|
const auto chartRect = chartAreaRect();
|
||||||
|
const auto pointerRatio = std::clamp(
|
||||||
|
state.point.x() / float64(chartRect.width()),
|
||||||
|
0.,
|
||||||
|
1.);
|
||||||
|
const auto currentXLimits = _animationController.finalXLimits();
|
||||||
|
const auto rawXPercentage = anim::interpolateF(
|
||||||
|
currentXLimits.min,
|
||||||
|
currentXLimits.max,
|
||||||
|
pointerRatio);
|
||||||
|
const auto nearestXPercentageIt = ranges::lower_bound(
|
||||||
|
_chartData.xPercentage,
|
||||||
|
rawXPercentage);
|
||||||
|
const auto nearestXIndex = std::distance(
|
||||||
|
begin(_chartData.xPercentage),
|
||||||
|
nearestXPercentageIt);
|
||||||
|
_details.currentX = 0
|
||||||
|
+ chartRect.width() * InterpolationRatio(
|
||||||
|
currentXLimits.min,
|
||||||
|
currentXLimits.max,
|
||||||
|
*nearestXPercentageIt);
|
||||||
|
const auto xLeft = _details.currentX
|
||||||
|
- _details.widget->width();
|
||||||
|
const auto x = (xLeft < 0)
|
||||||
|
? (_details.currentX)
|
||||||
|
: xLeft;
|
||||||
|
_details.widget->moveToLeft(x, _chartArea->y());
|
||||||
|
_details.widget->setXIndex(nearestXIndex);
|
||||||
|
_details.widget->show();
|
||||||
|
_chartArea->update();
|
||||||
|
} break;
|
||||||
|
case QEvent::MouseButtonRelease: {
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}, _details.widget->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChartWidget::setChartData(Data::StatisticalChart chartData) {
|
void ChartWidget::setChartData(Data::StatisticalChart chartData) {
|
||||||
_chartData = std::move(chartData);
|
_chartData = std::move(chartData);
|
||||||
|
|
||||||
|
setupDetails();
|
||||||
|
|
||||||
_footer->setFullHeightLimits(FindHeightLimitsBetweenXLimits(
|
_footer->setFullHeightLimits(FindHeightLimitsBetweenXLimits(
|
||||||
_chartData,
|
_chartData,
|
||||||
{ _chartData.xPercentage.front(), _chartData.xPercentage.back() }));
|
{ _chartData.xPercentage.front(), _chartData.xPercentage.back() }));
|
||||||
|
@ -543,6 +634,7 @@ void ChartWidget::setChartData(Data::StatisticalChart chartData) {
|
||||||
_animationController.finish();
|
_animationController.finish();
|
||||||
addHorizontalLine(_animationController.finalHeightLimits(), false);
|
addHorizontalLine(_animationController.finalHeightLimits(), false);
|
||||||
_chartArea->update();
|
_chartArea->update();
|
||||||
|
_footer->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChartWidget::addHorizontalLine(Limits newHeight, bool animated) {
|
void ChartWidget::addHorizontalLine(Limits newHeight, bool animated) {
|
||||||
|
|
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Statistic {
|
namespace Statistic {
|
||||||
|
|
||||||
class RpMouseWidget;
|
class RpMouseWidget;
|
||||||
|
class PointDetailsWidget;
|
||||||
|
|
||||||
class ChartWidget : public Ui::RpWidget {
|
class ChartWidget : public Ui::RpWidget {
|
||||||
public:
|
public:
|
||||||
|
@ -44,6 +45,7 @@ private:
|
||||||
std::vector<ChartHorizontalLinesData> &horizontalLines);
|
std::vector<ChartHorizontalLinesData> &horizontalLines);
|
||||||
|
|
||||||
[[nodiscard]] Limits currentXLimits() const;
|
[[nodiscard]] Limits currentXLimits() const;
|
||||||
|
[[nodiscard]] Limits finalXLimits() const;
|
||||||
[[nodiscard]] Limits currentHeightLimits() const;
|
[[nodiscard]] Limits currentHeightLimits() const;
|
||||||
[[nodiscard]] Limits finalHeightLimits() const;
|
[[nodiscard]] Limits finalHeightLimits() const;
|
||||||
|
|
||||||
|
@ -72,10 +74,21 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const base::unique_qptr<Ui::RpWidget> _chartArea;
|
[[nodiscard]] QRect chartAreaRect() const;
|
||||||
std::unique_ptr<Footer> _footer;
|
|
||||||
|
void setupChartArea();
|
||||||
|
void setupFooter();
|
||||||
|
void setupDetails();
|
||||||
|
|
||||||
|
const base::unique_qptr<RpMouseWidget> _chartArea;
|
||||||
|
const std::unique_ptr<Footer> _footer;
|
||||||
Data::StatisticalChart _chartData;
|
Data::StatisticalChart _chartData;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
base::unique_qptr<PointDetailsWidget> widget;
|
||||||
|
float64 currentX = 0;
|
||||||
|
} _details;
|
||||||
|
|
||||||
bool _useMinHeight = false;
|
bool _useMinHeight = false;
|
||||||
|
|
||||||
ChartAnimationController _animationController;
|
ChartAnimationController _animationController;
|
||||||
|
|
|
@ -10,7 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_statistics.h"
|
#include "data/data_statistics.h"
|
||||||
#include "statistics/statistics_common.h"
|
#include "statistics/statistics_common.h"
|
||||||
#include "ui/effects/animation_value_f.h"
|
#include "ui/effects/animation_value_f.h"
|
||||||
|
#include "ui/painter.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
|
#include "styles/style_statistics.h"
|
||||||
|
|
||||||
namespace Statistic {
|
namespace Statistic {
|
||||||
|
|
||||||
|
@ -19,10 +21,13 @@ void PaintLinearChartView(
|
||||||
const Data::StatisticalChart &chartData,
|
const Data::StatisticalChart &chartData,
|
||||||
const Limits &xPercentageLimits,
|
const Limits &xPercentageLimits,
|
||||||
const Limits &heightLimits,
|
const Limits &heightLimits,
|
||||||
const QRect &rect) {
|
const QRect &rect,
|
||||||
|
const DetailsPaintContext &detailsPaintContext) {
|
||||||
const auto currentMinHeight = rect.y(); //
|
const auto currentMinHeight = rect.y(); //
|
||||||
const auto currentMaxHeight = rect.height() + rect.y(); //
|
const auto currentMaxHeight = rect.height() + rect.y(); //
|
||||||
|
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
|
||||||
for (const auto &line : chartData.lines) {
|
for (const auto &line : chartData.lines) {
|
||||||
const auto additionalP = (chartData.xPercentage.size() < 2)
|
const auto additionalP = (chartData.xPercentage.size() < 2)
|
||||||
? 0.
|
? 0.
|
||||||
|
@ -31,6 +36,7 @@ void PaintLinearChartView(
|
||||||
|
|
||||||
auto first = true;
|
auto first = true;
|
||||||
auto chartPath = QPainterPath();
|
auto chartPath = QPainterPath();
|
||||||
|
auto detailsDotPoint = QPointF();
|
||||||
|
|
||||||
const auto startXIndex = chartData.findStartIndex(
|
const auto startXIndex = chartData.findStartIndex(
|
||||||
xPercentageLimits.min);
|
xPercentageLimits.min);
|
||||||
|
@ -53,15 +59,25 @@ void PaintLinearChartView(
|
||||||
const auto yPercentage = (line.y[i] - heightLimits.min)
|
const auto yPercentage = (line.y[i] - heightLimits.min)
|
||||||
/ float64(heightLimits.max - heightLimits.min);
|
/ float64(heightLimits.max - heightLimits.min);
|
||||||
const auto yPoint = rect.y() + (1. - yPercentage) * rect.height();
|
const auto yPoint = rect.y() + (1. - yPercentage) * rect.height();
|
||||||
|
if ((i == detailsPaintContext.xIndex)
|
||||||
|
&& detailsPaintContext.progress > 0.) {
|
||||||
|
detailsDotPoint = QPointF(xPoint, yPoint);
|
||||||
|
}
|
||||||
if (first) {
|
if (first) {
|
||||||
first = false;
|
first = false;
|
||||||
chartPath.moveTo(xPoint, yPoint);
|
chartPath.moveTo(xPoint, yPoint);
|
||||||
}
|
}
|
||||||
chartPath.lineTo(xPoint, yPoint);
|
chartPath.lineTo(xPoint, yPoint);
|
||||||
}
|
}
|
||||||
p.setPen(line.color);
|
p.setPen(QPen(line.color, st::statisticsChartLineWidth));
|
||||||
p.setBrush(Qt::NoBrush);
|
p.setBrush(Qt::NoBrush);
|
||||||
p.drawPath(chartPath);
|
p.drawPath(chartPath);
|
||||||
|
|
||||||
|
if (!detailsDotPoint.isNull()) {
|
||||||
|
p.setBrush(st::boxBg);
|
||||||
|
const auto r = st::statisticsDetailsDotRadius;
|
||||||
|
p.drawEllipse(detailsDotPoint, r, r);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
p.setPen(st::boxTextFg);
|
p.setPen(st::boxTextFg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,14 @@ struct StatisticalChart;
|
||||||
namespace Statistic {
|
namespace Statistic {
|
||||||
|
|
||||||
struct Limits;
|
struct Limits;
|
||||||
|
struct DetailsPaintContext;
|
||||||
|
|
||||||
void PaintLinearChartView(
|
void PaintLinearChartView(
|
||||||
QPainter &p,
|
QPainter &p,
|
||||||
const Data::StatisticalChart &chartData,
|
const Data::StatisticalChart &chartData,
|
||||||
const Limits &xPercentageLimits,
|
const Limits &xPercentageLimits,
|
||||||
const Limits &heightLimits,
|
const Limits &heightLimits,
|
||||||
const QRect &rect);
|
const QRect &rect,
|
||||||
|
const DetailsPaintContext &detailsPaintContext);
|
||||||
|
|
||||||
} // namespace Statistic
|
} // namespace Statistic
|
||||||
|
|
|
@ -37,7 +37,12 @@ PointDetailsWidget::PointDetailsWidget(
|
||||||
+ st::statisticsDetailsPopupMargins.bottom());
|
+ st::statisticsDetailsPopupMargins.bottom());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int PointDetailsWidget::xIndex() const {
|
||||||
|
return _xIndex;
|
||||||
|
}
|
||||||
|
|
||||||
void PointDetailsWidget::setXIndex(int xIndex) {
|
void PointDetailsWidget::setXIndex(int xIndex) {
|
||||||
|
_xIndex = xIndex;
|
||||||
_header.setText(_headerStyle, _chartData.getDayString(xIndex));
|
_header.setText(_headerStyle, _chartData.getDayString(xIndex));
|
||||||
|
|
||||||
_lines.clear();
|
_lines.clear();
|
||||||
|
|
|
@ -18,6 +18,7 @@ public:
|
||||||
not_null<Ui::RpWidget*> parent,
|
not_null<Ui::RpWidget*> parent,
|
||||||
const Data::StatisticalChart &chartData);
|
const Data::StatisticalChart &chartData);
|
||||||
|
|
||||||
|
[[nodiscard]] int xIndex() const;
|
||||||
void setXIndex(int xIndex);
|
void setXIndex(int xIndex);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -40,6 +41,8 @@ private:
|
||||||
QRect _innerRect;
|
QRect _innerRect;
|
||||||
QRect _textRect;
|
QRect _textRect;
|
||||||
|
|
||||||
|
int _xIndex = -1;
|
||||||
|
|
||||||
std::vector<Line> _lines;
|
std::vector<Line> _lines;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,6 +14,8 @@ statisticsDetailsPopupWidth: 135px;
|
||||||
statisticsDetailsPopupMargins: margins(8px, 8px, 8px, 8px);
|
statisticsDetailsPopupMargins: margins(8px, 8px, 8px, 8px);
|
||||||
statisticsDetailsPopupPadding: margins(6px, 6px, 6px, 6px);
|
statisticsDetailsPopupPadding: margins(6px, 6px, 6px, 6px);
|
||||||
statisticsDetailsPopupMidLineSpace: 8px;
|
statisticsDetailsPopupMidLineSpace: 8px;
|
||||||
|
statisticsDetailsDotRadius: 4px;
|
||||||
|
statisticsChartLineWidth: 2px;
|
||||||
|
|
||||||
statisticsDetailsPopupStyle: TextStyle(defaultTextStyle) {
|
statisticsDetailsPopupStyle: TextStyle(defaultTextStyle) {
|
||||||
font: font(11px);
|
font: font(11px);
|
||||||
|
|
|
@ -14,4 +14,10 @@ struct Limits final {
|
||||||
float64 max = 0;
|
float64 max = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Dot on line charts.
|
||||||
|
struct DetailsPaintContext final {
|
||||||
|
int xIndex = -1;
|
||||||
|
float64 progress = 0.;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Statistic
|
} // namespace Statistic
|
||||||
|
|
Loading…
Add table
Reference in a new issue