graph.cc 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/hud_display/graph.h"
  5. #include <algorithm>
  6. #include <limits>
  7. #include <sstream>
  8. #include "cc/paint/paint_flags.h"
  9. #include "third_party/skia/include/core/SkBlendMode.h"
  10. #include "third_party/skia/include/core/SkPaint.h"
  11. #include "third_party/skia/include/core/SkPath.h"
  12. #include "third_party/skia/include/core/SkPoint.h"
  13. #include "ui/gfx/canvas.h"
  14. #include "ui/gfx/geometry/rect_f.h"
  15. namespace ash {
  16. namespace hud_display {
  17. Graph::Graph(size_t max_data_points,
  18. Baseline baseline,
  19. Fill fill,
  20. Style style,
  21. SkColor color)
  22. : baseline_(baseline), fill_(fill), style_(style), color_(color) {
  23. DCHECK_LT(max_data_points, data_.BufferSize());
  24. max_data_points_ = std::min(max_data_points, data_.BufferSize() - 1);
  25. }
  26. Graph::~Graph() {}
  27. void Graph::AddValue(float value, float unscaled_value) {
  28. data_.SaveToBuffer(value);
  29. unscaled_data_.SaveToBuffer(unscaled_value);
  30. }
  31. void Graph::Layout(const gfx::Rect& graph_bounds, const Graph* base) {
  32. graph_bounds_ = graph_bounds;
  33. const float scale_x = graph_bounds.width() / (float)max_data_points_;
  34. // Assume data is already scaled to [0-1], which will map to full
  35. // |graph_bounds| height.
  36. const float scale_y = graph_bounds.height();
  37. // Let every graph to occupy at least given amount of pixels.
  38. const int pixel_adjust = (baseline_ == Baseline::BASELINE_BOTTOM) ? -1 : 1;
  39. // Bottom path is always base. So it's visually below the current line when
  40. // BASELINE_BOTTOM and above current graph when BASELINE_TOP.
  41. // top_path_ is similarly inverted.
  42. bottom_path_.resize(0);
  43. if (base) {
  44. bottom_path_.reserve(base->top_path().size());
  45. bottom_path_style_ = base->style_;
  46. for (const SkPoint& point : base->top_path())
  47. bottom_path_.push_back({point.x(), point.y() + pixel_adjust});
  48. }
  49. top_path_.resize(0);
  50. top_path_.reserve(max_data_points_);
  51. size_t i = 0;
  52. // base::RingBuffer<> does not conform to C++ containers specification, so
  53. // it's End() is actually Back().
  54. for (Graph::Data::Iterator it = data_.End(); it; --it) {
  55. const float value = **it;
  56. float x = graph_bounds.x() + (max_data_points_ - i) * scale_x;
  57. float y =
  58. (baseline_ == Baseline::BASELINE_BOTTOM ? -1 : 1) * value * scale_y;
  59. if (bottom_path_.size()) {
  60. CHECK_LT(i, bottom_path_.size());
  61. // Adjust to the single pixel line added above.
  62. y = bottom_path_[i].y() - pixel_adjust + y;
  63. } else {
  64. y = (baseline_ == Baseline::BASELINE_BOTTOM ? graph_bounds.bottom()
  65. : graph_bounds.y()) +
  66. y;
  67. }
  68. top_path_.push_back({x, y});
  69. ++i;
  70. if (i >= max_data_points_)
  71. break;
  72. }
  73. // This is the first layer from the start and it is filled and is non-empty.
  74. if (!base && fill_ != Graph::Fill::NONE && !top_path_.empty()) {
  75. gfx::RectF graph_bounds_f(graph_bounds);
  76. if (baseline_ == Baseline::BASELINE_BOTTOM) {
  77. bottom_path_.push_back({graph_bounds_f.right(), graph_bounds_f.bottom()});
  78. bottom_path_.push_back({top_path_.back().x(), graph_bounds_f.bottom()});
  79. } else {
  80. bottom_path_.push_back({graph_bounds_f.right(), graph_bounds_f.y()});
  81. bottom_path_.push_back({top_path_.back().x(), graph_bounds_f.y()});
  82. }
  83. }
  84. }
  85. void Graph::Draw(gfx::Canvas* canvas) const {
  86. if (top_path_.empty())
  87. return;
  88. SkPath path;
  89. path.moveTo(top_path_.front());
  90. const auto draw_top_line = [](const std::vector<SkPoint>& top_path,
  91. const auto& draw_point, SkPath& out_path) {
  92. SkPoint previous_point = top_path.front();
  93. for (std::vector<SkPoint>::const_iterator it = top_path.begin();
  94. it != top_path.end(); ++it) {
  95. // For the top line we are already here.
  96. if (it == top_path.begin())
  97. continue;
  98. // Depending on the line type, |draw_point| may use the previous point.
  99. draw_point(*it, previous_point, out_path);
  100. }
  101. };
  102. const auto draw_bottom_line = [](const std::vector<SkPoint>& bottom_path,
  103. const auto& draw_point, SkPath& result) {
  104. SkPoint previous_point = bottom_path.back();
  105. for (std::vector<SkPoint>::const_reverse_iterator it =
  106. bottom_path.crbegin();
  107. it != bottom_path.crend(); ++it) {
  108. // Bottom line needs line to the first point too.
  109. // Depending on the line type, |draw_point| may use the previous point.
  110. draw_point(*it, previous_point, result);
  111. }
  112. };
  113. // This is used to draw both top and bottom Style::LINES paths.
  114. const auto draw_lines_point =
  115. [](const SkPoint& point, const SkPoint& /*previous_point*/,
  116. SkPath& out_path) { out_path.lineTo(point); };
  117. // Top and bottom Style::SKYLINE drawing functions are symmetric.
  118. const auto draw_skyline_point =
  119. [](const SkPoint& point, SkPoint& previous_point, SkPath& out_path) {
  120. out_path.lineTo(SkPoint::Make(point.x(), previous_point.y()));
  121. out_path.lineTo(point);
  122. previous_point = point;
  123. };
  124. const auto draw_bottom_skyline_point =
  125. [](const SkPoint& point, SkPoint& previous_point, SkPath& out_path) {
  126. out_path.lineTo(SkPoint::Make(previous_point.x(), point.y()));
  127. out_path.lineTo(point);
  128. previous_point = point;
  129. };
  130. switch (style_) {
  131. case Style::LINES:
  132. draw_top_line(top_path_, draw_lines_point, path);
  133. break;
  134. case Style::SKYLINE:
  135. draw_top_line(top_path_, draw_skyline_point, path);
  136. break;
  137. }
  138. if (fill_ == Graph::Fill::SOLID) {
  139. switch (bottom_path_style_) {
  140. case Style::LINES:
  141. draw_bottom_line(bottom_path_, draw_lines_point, path);
  142. break;
  143. case Style::SKYLINE:
  144. draw_bottom_line(bottom_path_, draw_bottom_skyline_point, path);
  145. break;
  146. }
  147. }
  148. cc::PaintFlags flags;
  149. flags.setAntiAlias(true);
  150. flags.setBlendMode(SkBlendMode::kSrc);
  151. const cc::PaintFlags::Style style = (fill_ == Graph::Fill::NONE)
  152. ? cc::PaintFlags::kStroke_Style
  153. : cc::PaintFlags::kFill_Style;
  154. flags.setStyle(style);
  155. flags.setStrokeWidth(1);
  156. flags.setColor(color_);
  157. canvas->DrawPath(path, flags);
  158. }
  159. void Graph::UpdateLastValue(float value, float unscaled_value) {
  160. const size_t last_index = data_.BufferSize() - 1;
  161. if (!data_.IsFilledIndex(last_index))
  162. return;
  163. *data_.MutableReadBuffer(last_index) = value;
  164. *unscaled_data_.MutableReadBuffer(last_index) = unscaled_value;
  165. }
  166. float Graph::GetUnscaledValueAt(size_t index) const {
  167. if (index >= max_data_points_)
  168. return 0;
  169. // 0 - the oldest value
  170. // BufferSize() - 1 - the newest value.
  171. const size_t raw_index = index < unscaled_data_.BufferSize()
  172. ? unscaled_data_.BufferSize() - 1 - index
  173. : 0;
  174. // It will CHECK() if index is not populated.
  175. if (!unscaled_data_.IsFilledIndex(raw_index))
  176. return 0;
  177. return unscaled_data_.ReadBuffer(raw_index);
  178. }
  179. bool Graph::IsFilledIndex(size_t index) const {
  180. if (index >= max_data_points_)
  181. return false;
  182. // 0 - the oldest value
  183. // BufferSize() - 1 - the newest value.
  184. const size_t raw_index =
  185. index < data_.BufferSize() ? data_.BufferSize() - 1 - index : 0;
  186. return data_.IsFilledIndex(raw_index);
  187. }
  188. void Graph::Reset() {
  189. data_.Clear();
  190. unscaled_data_.Clear();
  191. }
  192. #if !defined(NDEBUG)
  193. std::string Graph::DebugDump(const std::string& name) const {
  194. std::ostringstream os;
  195. os << name << ": location BLxy [" << graph_bounds_.x() << ", "
  196. << graph_bounds_.bottom() << "] TRxy [" << graph_bounds_.right() << ", "
  197. << graph_bounds_.y() << "]";
  198. const int topsize = static_cast<int>(top_path_.size());
  199. for (int i = 0; i < topsize; ++i) {
  200. if ((i > 5) && (i < topsize - 5)) {
  201. // Skip the middle part.
  202. os << "\t" << name << ": ...";
  203. i = topsize - 5;
  204. }
  205. if (fill_ == Graph::Fill::SOLID) {
  206. // Print filled graph as a set of vertical lines.
  207. if (top_path_.size() == bottom_path_.size()) {
  208. // Each point on the top has matching point on the bottom.
  209. os << "\t" << name << ": " << i << ": [" << bottom_path_[i].x() << ", "
  210. << bottom_path_[i].y() << "] -> [" << top_path_[i].x() << ", "
  211. << top_path_[i].y() << "]";
  212. } else {
  213. // This is the first graph in stack. Use bottom_path_[0].y() as
  214. // reference.
  215. os << "\t" << name << ": " << i << ": ["
  216. << ((i == 0) ? bottom_path_[0].x() : top_path_[i].x()) << ", "
  217. << bottom_path_[0].y() << "] -> [" << top_path_[i].x() << ", "
  218. << top_path_[i].y() << "]";
  219. }
  220. } else {
  221. // Print lines graph as a list of dots.
  222. os << "\t" << name << ": " << i << ": -> [" << top_path_[i].x() << ", "
  223. << top_path_[i].y() << "]";
  224. }
  225. os << "\n";
  226. }
  227. return os.str();
  228. }
  229. #endif
  230. } // namespace hud_display
  231. } // namespace ash