linear_chart_view.cpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  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/linear_chart_view.h"
  8. #include "data/data_statistics_chart.h"
  9. #include "statistics/chart_lines_filter_controller.h"
  10. #include "statistics/statistics_common.h"
  11. #include "ui/effects/animation_value_f.h"
  12. #include "ui/painter.h"
  13. #include "styles/style_boxes.h"
  14. #include "styles/style_statistics.h"
  15. namespace Statistic {
  16. namespace {
  17. void PaintChartLine(
  18. QPainter &p,
  19. int lineIndex,
  20. const PaintContext &c,
  21. const DoubleLineRatios &ratios) {
  22. const auto &line = c.chartData.lines[lineIndex];
  23. auto chartPoints = QPolygonF();
  24. constexpr auto kOffset = float64(2);
  25. const auto localStart = int(std::max(0., c.xIndices.min - kOffset));
  26. const auto localEnd = int(std::min(
  27. float64(c.chartData.xPercentage.size() - 1),
  28. c.xIndices.max + kOffset));
  29. const auto ratio = ratios.ratio(line.id);
  30. for (auto i = localStart; i <= localEnd; i++) {
  31. if (line.y[i] < 0) {
  32. continue;
  33. }
  34. const auto xPoint = c.rect.width()
  35. * ((c.chartData.xPercentage[i] - c.xPercentageLimits.min)
  36. / (c.xPercentageLimits.max - c.xPercentageLimits.min));
  37. const auto yPercentage = (line.y[i] * ratio - c.heightLimits.min)
  38. / float64(c.heightLimits.max - c.heightLimits.min);
  39. const auto yPoint = (1. - yPercentage) * c.rect.height();
  40. chartPoints << QPointF(xPoint, yPoint);
  41. }
  42. p.setPen(QPen(
  43. line.color,
  44. c.footer ? st::lineWidth : st::statisticsChartLineWidth));
  45. p.setBrush(Qt::NoBrush);
  46. p.drawPolyline(chartPoints);
  47. }
  48. } // namespace
  49. LinearChartView::LinearChartView(bool isDouble)
  50. : _cachedLineRatios(isDouble) {
  51. }
  52. LinearChartView::~LinearChartView() = default;
  53. void LinearChartView::paint(QPainter &p, const PaintContext &c) {
  54. const auto cacheToken = LinearChartView::CacheToken(
  55. c.xIndices,
  56. c.xPercentageLimits,
  57. c.heightLimits,
  58. c.rect.size());
  59. const auto opacity = p.opacity();
  60. const auto linesFilter = linesFilterController();
  61. const auto imageSize = c.rect.size() * style::DevicePixelRatio();
  62. const auto cacheScale = 1. / style::DevicePixelRatio();
  63. auto &caches = (c.footer ? _footerCaches : _mainCaches);
  64. for (auto i = 0; i < c.chartData.lines.size(); i++) {
  65. const auto &line = c.chartData.lines[i];
  66. p.setOpacity(linesFilter->alpha(line.id));
  67. if (!p.opacity()) {
  68. continue;
  69. }
  70. auto &cache = caches[line.id];
  71. const auto isSameToken = (cache.lastToken == cacheToken);
  72. if ((isSameToken && cache.hq)
  73. || (p.opacity() < 1. && !linesFilter->isEnabled(line.id))) {
  74. p.drawImage(c.rect.topLeft(), cache.image);
  75. continue;
  76. }
  77. cache.hq = isSameToken;
  78. auto image = QImage();
  79. image = QImage(
  80. imageSize * (isSameToken ? 1. : cacheScale),
  81. QImage::Format_ARGB32_Premultiplied);
  82. image.setDevicePixelRatio(style::DevicePixelRatio());
  83. image.fill(Qt::transparent);
  84. {
  85. auto imagePainter = QPainter(&image);
  86. auto hq = PainterHighQualityEnabler(imagePainter);
  87. if (!isSameToken) {
  88. imagePainter.scale(cacheScale, cacheScale);
  89. }
  90. PaintChartLine(imagePainter, i, c, _cachedLineRatios);
  91. }
  92. if (!isSameToken) {
  93. image = image.scaled(
  94. imageSize,
  95. Qt::IgnoreAspectRatio,
  96. Qt::FastTransformation);
  97. }
  98. p.drawImage(c.rect.topLeft(), image);
  99. cache.lastToken = cacheToken;
  100. cache.image = std::move(image);
  101. }
  102. p.setOpacity(opacity);
  103. }
  104. void LinearChartView::paintSelectedXIndex(
  105. QPainter &p,
  106. const PaintContext &c,
  107. int selectedXIndex,
  108. float64 progress) {
  109. if (selectedXIndex < 0) {
  110. return;
  111. }
  112. const auto linesFilter = linesFilterController();
  113. auto hq = PainterHighQualityEnabler(p);
  114. auto o = ScopedPainterOpacity(p, progress);
  115. p.setBrush(st::boxBg);
  116. const auto r = st::statisticsDetailsDotRadius;
  117. const auto i = selectedXIndex;
  118. const auto isSameToken = _selectedPoints.isSame(selectedXIndex, c);
  119. auto linePainted = false;
  120. for (const auto &line : c.chartData.lines) {
  121. const auto lineAlpha = linesFilter->alpha(line.id);
  122. const auto useCache = isSameToken
  123. || (lineAlpha < 1. && !linesFilter->isEnabled(line.id));
  124. if (!useCache) {
  125. // Calculate.
  126. const auto r = _cachedLineRatios.ratio(line.id);
  127. const auto xPoint = c.rect.width()
  128. * ((c.chartData.xPercentage[i] - c.xPercentageLimits.min)
  129. / (c.xPercentageLimits.max - c.xPercentageLimits.min));
  130. const auto yPercentage = (line.y[i] * r - c.heightLimits.min)
  131. / float64(c.heightLimits.max - c.heightLimits.min);
  132. const auto yPoint = (1. - yPercentage) * c.rect.height();
  133. _selectedPoints.points[line.id] = QPointF(xPoint, yPoint)
  134. + c.rect.topLeft();
  135. }
  136. if (!linePainted && lineAlpha) {
  137. [[maybe_unused]] const auto o = ScopedPainterOpacity(
  138. p,
  139. p.opacity() * progress * kRulerLineAlpha);
  140. const auto lineRect = QRectF(
  141. begin(_selectedPoints.points)->second.x()
  142. - (st::lineWidth / 2.),
  143. c.rect.y(),
  144. st::lineWidth,
  145. c.rect.height());
  146. p.fillRect(lineRect, st::boxTextFg);
  147. linePainted = true;
  148. }
  149. // Paint.
  150. auto o = ScopedPainterOpacity(p, lineAlpha * p.opacity());
  151. p.setPen(QPen(line.color, st::statisticsChartLineWidth));
  152. p.drawEllipse(_selectedPoints.points[line.id], r, r);
  153. }
  154. _selectedPoints.lastXIndex = selectedXIndex;
  155. _selectedPoints.lastHeightLimits = c.heightLimits;
  156. _selectedPoints.lastXLimits = c.xPercentageLimits;
  157. }
  158. int LinearChartView::findXIndexByPosition(
  159. const Data::StatisticalChart &chartData,
  160. const Limits &xPercentageLimits,
  161. const QRect &rect,
  162. float64 x) {
  163. if ((x < rect.x()) || (x > (rect.x() + rect.width()))) {
  164. return -1;
  165. }
  166. const auto pointerRatio = std::clamp(
  167. (x - rect.x()) / rect.width(),
  168. 0.,
  169. 1.);
  170. const auto rawXPercentage = anim::interpolateF(
  171. xPercentageLimits.min,
  172. xPercentageLimits.max,
  173. pointerRatio);
  174. const auto it = ranges::lower_bound(
  175. chartData.xPercentage,
  176. rawXPercentage);
  177. const auto left = rawXPercentage - (*(it - 1));
  178. const auto right = (*it) - rawXPercentage;
  179. const auto nearest = ((right) > (left)) ? (it - 1) : it;
  180. const auto resultXPercentageIt = ((*nearest) > xPercentageLimits.max)
  181. ? (nearest - 1)
  182. : ((*nearest) < xPercentageLimits.min)
  183. ? (nearest + 1)
  184. : nearest;
  185. return std::distance(begin(chartData.xPercentage), resultXPercentageIt);
  186. }
  187. AbstractChartView::HeightLimits LinearChartView::heightLimits(
  188. Data::StatisticalChart &chartData,
  189. Limits xIndices) {
  190. if (!_cachedLineRatios) {
  191. _cachedLineRatios.init(chartData);
  192. }
  193. return DefaultHeightLimits(
  194. _cachedLineRatios,
  195. linesFilterController(),
  196. chartData,
  197. xIndices);
  198. }
  199. } // namespace Statistic