bar_chart_view.cpp 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "statistics/view/bar_chart_view.h"
  8. #include "data/data_statistics_chart.h"
  9. #include "statistics/chart_lines_filter_controller.h"
  10. #include "statistics/view/stack_chart_common.h"
  11. #include "ui/effects/animation_value_f.h"
  12. #include "ui/painter.h"
  13. #include "ui/rect.h"
  14. #include "styles/style_statistics.h"
  15. namespace Statistic {
  16. BarChartView::BarChartView(bool isStack)
  17. : _isStack(isStack)
  18. , _cachedLineRatios(false) {
  19. }
  20. BarChartView::~BarChartView() = default;
  21. void BarChartView::paint(QPainter &p, const PaintContext &c) {
  22. constexpr auto kOffset = float64(2);
  23. _lastPaintedXIndices = {
  24. float64(std::max(0., c.xIndices.min - kOffset)),
  25. float64(std::min(
  26. float64(c.chartData.xPercentage.size() - 1),
  27. c.xIndices.max + kOffset)),
  28. };
  29. BarChartView::paintChartAndSelected(p, c);
  30. }
  31. void BarChartView::paintChartAndSelected(
  32. QPainter &p,
  33. const PaintContext &c) {
  34. const auto &[localStart, localEnd] = _lastPaintedXIndices;
  35. const auto &[leftStart, w] = ComputeLeftStartAndStep(
  36. c.chartData,
  37. c.xPercentageLimits,
  38. c.rect,
  39. localStart);
  40. p.setClipRect(0, 0, c.rect.width() * 2, rect::bottom(c.rect));
  41. const auto opacity = p.opacity();
  42. auto hq = PainterHighQualityEnabler(p);
  43. auto bottoms = std::vector<float64>(
  44. localEnd - localStart + 1,
  45. -c.rect.y());
  46. auto selectedBottoms = std::vector<float64>();
  47. const auto hasSelectedXIndex = _isStack
  48. && !c.footer
  49. && (_lastSelectedXIndex >= 0);
  50. if (hasSelectedXIndex) {
  51. selectedBottoms = std::vector<float64>(c.chartData.lines.size(), 0);
  52. constexpr auto kSelectedAlpha = 0.5;
  53. p.setOpacity(
  54. anim::interpolateF(1.0, kSelectedAlpha, _lastSelectedXProgress));
  55. }
  56. for (auto i = 0; i < c.chartData.lines.size(); i++) {
  57. const auto &line = c.chartData.lines[i];
  58. auto path = QPainterPath();
  59. for (auto x = localStart; x <= localEnd; x++) {
  60. if (line.y[x] <= 0 && _isStack) {
  61. continue;
  62. }
  63. const auto yPercentage = (line.y[x] - c.heightLimits.min)
  64. / float64(c.heightLimits.max - c.heightLimits.min);
  65. const auto yPoint = yPercentage
  66. * c.rect.height()
  67. * linesFilterController()->alpha(line.id);
  68. const auto bottomIndex = x - localStart;
  69. const auto column = QRectF(
  70. leftStart + (x - localStart) * w,
  71. c.rect.height() - bottoms[bottomIndex] - yPoint,
  72. w,
  73. yPoint);
  74. if (hasSelectedXIndex && (x == _lastSelectedXIndex)) {
  75. selectedBottoms[i] = column.y();
  76. }
  77. if (_isStack) {
  78. path.addRect(column);
  79. bottoms[bottomIndex] += yPoint;
  80. } else {
  81. if (path.isEmpty()) {
  82. path.moveTo(column.topLeft());
  83. } else {
  84. path.lineTo(column.topLeft());
  85. }
  86. if (x == localEnd) {
  87. path.lineTo(c.rect.width(), column.y());
  88. } else {
  89. path.lineTo(rect::right(column), column.y());
  90. }
  91. }
  92. }
  93. if (_isStack) {
  94. p.fillPath(path, line.color);
  95. } else {
  96. p.strokePath(path, line.color);
  97. }
  98. }
  99. for (auto i = 0; i < selectedBottoms.size(); i++) {
  100. p.setOpacity(opacity);
  101. if (selectedBottoms[i] <= 0) {
  102. continue;
  103. }
  104. const auto &line = c.chartData.lines[i];
  105. const auto yPercentage = 0.
  106. + (line.y[_lastSelectedXIndex] - c.heightLimits.min)
  107. / float64(c.heightLimits.max - c.heightLimits.min);
  108. const auto yPoint = yPercentage
  109. * c.rect.height()
  110. * linesFilterController()->alpha(line.id);
  111. const auto column = QRectF(
  112. leftStart + (_lastSelectedXIndex - localStart) * w,
  113. selectedBottoms[i],
  114. w,
  115. yPoint);
  116. p.fillRect(column, line.color);
  117. }
  118. p.setClipping(false);
  119. }
  120. void BarChartView::paintSelectedXIndex(
  121. QPainter &p,
  122. const PaintContext &c,
  123. int selectedXIndex,
  124. float64 progress) {
  125. const auto was = _lastSelectedXIndex;
  126. _lastSelectedXIndex = selectedXIndex;
  127. _lastSelectedXProgress = progress;
  128. if ((_lastSelectedXIndex < 0) && (was < 0)) {
  129. return;
  130. }
  131. if (_isStack) {
  132. BarChartView::paintChartAndSelected(p, c);
  133. } else if (selectedXIndex >= 0) {
  134. const auto linesFilter = linesFilterController();
  135. auto hq = PainterHighQualityEnabler(p);
  136. auto o = ScopedPainterOpacity(p, progress);
  137. p.setBrush(st::boxBg);
  138. const auto r = st::statisticsDetailsDotRadius;
  139. const auto isSameToken = _selectedPoints.isSame(selectedXIndex, c);
  140. auto linePainted = false;
  141. const auto &[localStart, localEnd] = _lastPaintedXIndices;
  142. const auto &[leftStart, w] = ComputeLeftStartAndStep(
  143. c.chartData,
  144. c.xPercentageLimits,
  145. c.rect,
  146. localStart);
  147. for (auto i = 0; i < c.chartData.lines.size(); i++) {
  148. const auto &line = c.chartData.lines[i];
  149. const auto lineAlpha = linesFilter->alpha(line.id);
  150. const auto useCache = isSameToken
  151. || (lineAlpha < 1. && !linesFilter->isEnabled(line.id));
  152. if (!useCache) {
  153. // Calculate.
  154. const auto x = _lastSelectedXIndex;
  155. const auto yPercentage = (line.y[x] - c.heightLimits.min)
  156. / float64(c.heightLimits.max - c.heightLimits.min);
  157. const auto yPoint = (1. - yPercentage) * c.rect.height();
  158. const auto column = QRectF(
  159. leftStart + (x - localStart) * w,
  160. c.rect.height() - 0 - yPoint,
  161. w,
  162. yPoint);
  163. const auto xPoint = column.left() + column.width() / 2.;
  164. _selectedPoints.points[line.id] = QPointF(xPoint, yPoint)
  165. + c.rect.topLeft();
  166. }
  167. if (!linePainted && lineAlpha) {
  168. [[maybe_unused]] const auto o = ScopedPainterOpacity(
  169. p,
  170. p.opacity() * progress * kRulerLineAlpha);
  171. const auto lineRect = QRectF(
  172. begin(_selectedPoints.points)->second.x()
  173. - (st::lineWidth / 2.),
  174. c.rect.y(),
  175. st::lineWidth,
  176. c.rect.height());
  177. p.fillRect(lineRect, st::boxTextFg);
  178. linePainted = true;
  179. }
  180. // Paint.
  181. auto o = ScopedPainterOpacity(p, lineAlpha * p.opacity());
  182. p.setPen(QPen(line.color, st::statisticsChartLineWidth));
  183. p.drawEllipse(_selectedPoints.points[line.id], r, r);
  184. }
  185. _selectedPoints.lastXIndex = selectedXIndex;
  186. _selectedPoints.lastHeightLimits = c.heightLimits;
  187. _selectedPoints.lastXLimits = c.xPercentageLimits;
  188. }
  189. }
  190. int BarChartView::findXIndexByPosition(
  191. const Data::StatisticalChart &chartData,
  192. const Limits &xPercentageLimits,
  193. const QRect &rect,
  194. float64 xPos) {
  195. if ((xPos < rect.x()) || (xPos > (rect.x() + rect.width()))) {
  196. return _lastSelectedXIndex = -1;
  197. }
  198. const auto &[localStart, localEnd] = _lastPaintedXIndices;
  199. const auto &[leftStart, w] = ComputeLeftStartAndStep(
  200. chartData,
  201. xPercentageLimits,
  202. rect,
  203. localStart);
  204. for (auto i = 0; i < chartData.lines.size(); i++) {
  205. for (auto x = localStart; x <= localEnd; x++) {
  206. const auto left = leftStart + (x - localStart) * w;
  207. if ((xPos >= left) && (xPos < (left + w))) {
  208. return _lastSelectedXIndex = x;
  209. }
  210. }
  211. }
  212. return _lastSelectedXIndex = -1;
  213. }
  214. AbstractChartView::HeightLimits BarChartView::heightLimits(
  215. Data::StatisticalChart &chartData,
  216. Limits xIndices) {
  217. if (!_isStack) {
  218. if (!_cachedLineRatios) {
  219. _cachedLineRatios.init(chartData);
  220. }
  221. return DefaultHeightLimits(
  222. _cachedLineRatios,
  223. linesFilterController(),
  224. chartData,
  225. xIndices);
  226. }
  227. _cachedHeightLimits = {};
  228. if (_cachedHeightLimits.ySum.empty()) {
  229. _cachedHeightLimits.ySum.reserve(chartData.x.size());
  230. auto maxValueFull = ChartValue(0);
  231. for (auto i = 0; i < chartData.x.size(); i++) {
  232. auto sum = ChartValue(0);
  233. for (const auto &line : chartData.lines) {
  234. if (linesFilterController()->isEnabled(line.id)) {
  235. sum += line.y[i];
  236. }
  237. }
  238. _cachedHeightLimits.ySum.push_back(sum);
  239. maxValueFull = std::max(sum, maxValueFull);
  240. }
  241. _cachedHeightLimits.ySumSegmentTree = SegmentTree(
  242. _cachedHeightLimits.ySum);
  243. _cachedHeightLimits.full = { 0., float64(maxValueFull) };
  244. }
  245. const auto max = std::max(
  246. _cachedHeightLimits.ySumSegmentTree.rMaxQ(
  247. xIndices.min,
  248. xIndices.max),
  249. ChartValue(1));
  250. return {
  251. .full = _cachedHeightLimits.full,
  252. .ranged = { 0., float64(max) },
  253. };
  254. }
  255. } // namespace Statistic