statistics_data_deserialize.cpp 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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/statistics_data_deserialize.h"
  8. #include "base/debug_log.h"
  9. #include "data/data_channel_earn.h" // kEarnMultiplier.
  10. #include "data/data_statistics_chart.h"
  11. #include "statistics/statistics_types.h"
  12. #include "ui/text/format_values.h" // kCreditsCurrency.
  13. #include <QtCore/QJsonArray>
  14. #include <QtCore/QJsonDocument>
  15. #include <QtCore/QJsonObject>
  16. #include <QtCore/QJsonValue>
  17. namespace Statistic {
  18. Data::StatisticalChart StatisticalChartFromJSON(const QByteArray &json) {
  19. auto error = QJsonParseError{ 0, QJsonParseError::NoError };
  20. const auto document = QJsonDocument::fromJson(json, &error);
  21. if (error.error != QJsonParseError::NoError || !document.isObject()) {
  22. LOG(("API Error: Bad stats graph json received."));
  23. return {};
  24. }
  25. const auto root = document.object();
  26. const auto columns = root.value(u"columns"_q).toArray();
  27. if (columns.empty()) {
  28. LOG(("API Error: Empty columns list from stats graph received."));
  29. return {};
  30. }
  31. const auto hiddenLinesRaw = root.value(u"hidden"_q).toArray();
  32. const auto hiddenLines = ranges::views::all(
  33. hiddenLinesRaw
  34. ) | ranges::views::transform([](const auto &q) {
  35. return q.toString();
  36. }) | ranges::to_vector;
  37. auto result = Data::StatisticalChart();
  38. {
  39. const auto tickFormatIt = root.constFind(u"yTickFormatter"_q);
  40. if (tickFormatIt != root.constEnd()) {
  41. const auto tickFormat = tickFormatIt->toString();
  42. if (tickFormat.contains(u"TON"_q)) {
  43. result.currency = Data::StatisticalCurrency::Ton;
  44. } else if (tickFormat.contains(Ui::kCreditsCurrency)) {
  45. result.currency = Data::StatisticalCurrency::Credits;
  46. }
  47. }
  48. }
  49. auto columnIdCount = 0;
  50. for (const auto &column : columns) {
  51. const auto array = column.toArray();
  52. if (array.empty()) {
  53. LOG(("API Error: Empty column from stats graph received."));
  54. return {};
  55. }
  56. const auto columnId = array.first().toString();
  57. if (columnId == u"x"_q) {
  58. const auto length = array.size() - 1;
  59. result.x.reserve(length);
  60. for (auto i = 0; i < length; i++) {
  61. result.x.push_back(array.at(i + 1).toDouble());
  62. }
  63. } else {
  64. auto line = Data::StatisticalChart::Line();
  65. const auto length = array.size() - 1;
  66. line.id = (++columnIdCount);
  67. line.idString = columnId;
  68. line.isHiddenOnStart = ranges::contains(hiddenLines, columnId);
  69. line.y.resize(length);
  70. for (auto i = 0; i < length; i++) {
  71. using Currency = Data::StatisticalCurrency;
  72. const auto multiplier = (result.currency == Currency::Credits)
  73. ? Data::kEarnMultiplier
  74. : 1;
  75. const auto value = ChartValue(
  76. base::SafeRound(array.at(i + 1).toDouble()))
  77. * multiplier;
  78. line.y[i] = value;
  79. if (value > line.maxValue) {
  80. line.maxValue = value;
  81. }
  82. if (value < line.minValue) {
  83. line.minValue = value;
  84. }
  85. }
  86. result.lines.push_back(std::move(line));
  87. }
  88. if (result.x.size() > 1) {
  89. result.timeStep = std::max(1., result.x[1] - result.x[0]);
  90. } else {
  91. constexpr auto kOneDay = 3600 * 24 * 1000;
  92. result.timeStep = kOneDay;
  93. }
  94. result.measure();
  95. }
  96. if (result.maxValue == result.minValue) {
  97. if (result.minValue) {
  98. result.minValue = 0;
  99. } else {
  100. result.maxValue = 1;
  101. }
  102. }
  103. {
  104. const auto subchart = root.value(u"subchart"_q).toObject();
  105. const auto subchartShowIt = subchart.constFind(u"show"_q);
  106. if (subchartShowIt != subchart.constEnd()) {
  107. if (subchartShowIt->isBool()) {
  108. result.isFooterHidden = !(subchartShowIt->toBool());
  109. }
  110. }
  111. const auto defaultZoomIt = subchart.constFind(u"defaultZoom"_q);
  112. auto min = int(0);
  113. auto max = int(result.x.size() - 1);
  114. if (defaultZoomIt != subchart.constEnd()) {
  115. if (const auto array = defaultZoomIt->toArray(); !array.empty()) {
  116. const auto minValue = array.first().toDouble();
  117. const auto maxValue = array.last().toDouble();
  118. for (auto i = 0; i < result.x.size(); i++) {
  119. if (result.x[i] == minValue) {
  120. min = i;
  121. }
  122. if (result.x[i] == maxValue) {
  123. max = i;
  124. }
  125. }
  126. }
  127. }
  128. result.defaultZoomXIndex.min = std::min(min, max);
  129. result.defaultZoomXIndex.max = std::max(min, max);
  130. }
  131. {
  132. const auto percentageShowIt = root.constFind(u"percentage"_q);
  133. if (percentageShowIt != root.constEnd()) {
  134. if (percentageShowIt->isBool()) {
  135. result.hasPercentages = (percentageShowIt->toBool());
  136. }
  137. }
  138. }
  139. {
  140. const auto tooltipFormatIt = root.constFind(u"xTooltipFormatter"_q);
  141. if (tooltipFormatIt != root.constEnd()) {
  142. const auto tooltipFormat = tooltipFormatIt->toString();
  143. result.weekFormat = tooltipFormat.contains(u"'week'"_q);
  144. }
  145. }
  146. const auto colors = root.value(u"colors"_q).toObject();
  147. const auto names = root.value(u"names"_q).toObject();
  148. for (auto &line : result.lines) {
  149. const auto colorIt = colors.constFind(line.idString);
  150. if (colorIt != colors.constEnd() && (*colorIt).isString()) {
  151. static const auto RegExp = QRegularExpression(u"(.*)(#.*)"_q);
  152. const auto match = RegExp.match(
  153. colorIt->toString());
  154. if (match.hasMatch()) {
  155. line.colorKey = match.captured(1);
  156. line.color = QColor(match.captured(2));
  157. }
  158. }
  159. const auto nameIt = names.constFind(line.idString);
  160. if (nameIt != names.constEnd() && (*nameIt).isString()) {
  161. line.name = nameIt->toString().replace('-', QChar(8212));
  162. }
  163. }
  164. return result;
  165. }
  166. } // namespace Statistic