123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- // Copyright 2020 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "ash/hud_display/graph.h"
- #include <algorithm>
- #include <limits>
- #include <sstream>
- #include "cc/paint/paint_flags.h"
- #include "third_party/skia/include/core/SkBlendMode.h"
- #include "third_party/skia/include/core/SkPaint.h"
- #include "third_party/skia/include/core/SkPath.h"
- #include "third_party/skia/include/core/SkPoint.h"
- #include "ui/gfx/canvas.h"
- #include "ui/gfx/geometry/rect_f.h"
- namespace ash {
- namespace hud_display {
- Graph::Graph(size_t max_data_points,
- Baseline baseline,
- Fill fill,
- Style style,
- SkColor color)
- : baseline_(baseline), fill_(fill), style_(style), color_(color) {
- DCHECK_LT(max_data_points, data_.BufferSize());
- max_data_points_ = std::min(max_data_points, data_.BufferSize() - 1);
- }
- Graph::~Graph() {}
- void Graph::AddValue(float value, float unscaled_value) {
- data_.SaveToBuffer(value);
- unscaled_data_.SaveToBuffer(unscaled_value);
- }
- void Graph::Layout(const gfx::Rect& graph_bounds, const Graph* base) {
- graph_bounds_ = graph_bounds;
- const float scale_x = graph_bounds.width() / (float)max_data_points_;
- // Assume data is already scaled to [0-1], which will map to full
- // |graph_bounds| height.
- const float scale_y = graph_bounds.height();
- // Let every graph to occupy at least given amount of pixels.
- const int pixel_adjust = (baseline_ == Baseline::BASELINE_BOTTOM) ? -1 : 1;
- // Bottom path is always base. So it's visually below the current line when
- // BASELINE_BOTTOM and above current graph when BASELINE_TOP.
- // top_path_ is similarly inverted.
- bottom_path_.resize(0);
- if (base) {
- bottom_path_.reserve(base->top_path().size());
- bottom_path_style_ = base->style_;
- for (const SkPoint& point : base->top_path())
- bottom_path_.push_back({point.x(), point.y() + pixel_adjust});
- }
- top_path_.resize(0);
- top_path_.reserve(max_data_points_);
- size_t i = 0;
- // base::RingBuffer<> does not conform to C++ containers specification, so
- // it's End() is actually Back().
- for (Graph::Data::Iterator it = data_.End(); it; --it) {
- const float value = **it;
- float x = graph_bounds.x() + (max_data_points_ - i) * scale_x;
- float y =
- (baseline_ == Baseline::BASELINE_BOTTOM ? -1 : 1) * value * scale_y;
- if (bottom_path_.size()) {
- CHECK_LT(i, bottom_path_.size());
- // Adjust to the single pixel line added above.
- y = bottom_path_[i].y() - pixel_adjust + y;
- } else {
- y = (baseline_ == Baseline::BASELINE_BOTTOM ? graph_bounds.bottom()
- : graph_bounds.y()) +
- y;
- }
- top_path_.push_back({x, y});
- ++i;
- if (i >= max_data_points_)
- break;
- }
- // This is the first layer from the start and it is filled and is non-empty.
- if (!base && fill_ != Graph::Fill::NONE && !top_path_.empty()) {
- gfx::RectF graph_bounds_f(graph_bounds);
- if (baseline_ == Baseline::BASELINE_BOTTOM) {
- bottom_path_.push_back({graph_bounds_f.right(), graph_bounds_f.bottom()});
- bottom_path_.push_back({top_path_.back().x(), graph_bounds_f.bottom()});
- } else {
- bottom_path_.push_back({graph_bounds_f.right(), graph_bounds_f.y()});
- bottom_path_.push_back({top_path_.back().x(), graph_bounds_f.y()});
- }
- }
- }
- void Graph::Draw(gfx::Canvas* canvas) const {
- if (top_path_.empty())
- return;
- SkPath path;
- path.moveTo(top_path_.front());
- const auto draw_top_line = [](const std::vector<SkPoint>& top_path,
- const auto& draw_point, SkPath& out_path) {
- SkPoint previous_point = top_path.front();
- for (std::vector<SkPoint>::const_iterator it = top_path.begin();
- it != top_path.end(); ++it) {
- // For the top line we are already here.
- if (it == top_path.begin())
- continue;
- // Depending on the line type, |draw_point| may use the previous point.
- draw_point(*it, previous_point, out_path);
- }
- };
- const auto draw_bottom_line = [](const std::vector<SkPoint>& bottom_path,
- const auto& draw_point, SkPath& result) {
- SkPoint previous_point = bottom_path.back();
- for (std::vector<SkPoint>::const_reverse_iterator it =
- bottom_path.crbegin();
- it != bottom_path.crend(); ++it) {
- // Bottom line needs line to the first point too.
- // Depending on the line type, |draw_point| may use the previous point.
- draw_point(*it, previous_point, result);
- }
- };
- // This is used to draw both top and bottom Style::LINES paths.
- const auto draw_lines_point =
- [](const SkPoint& point, const SkPoint& /*previous_point*/,
- SkPath& out_path) { out_path.lineTo(point); };
- // Top and bottom Style::SKYLINE drawing functions are symmetric.
- const auto draw_skyline_point =
- [](const SkPoint& point, SkPoint& previous_point, SkPath& out_path) {
- out_path.lineTo(SkPoint::Make(point.x(), previous_point.y()));
- out_path.lineTo(point);
- previous_point = point;
- };
- const auto draw_bottom_skyline_point =
- [](const SkPoint& point, SkPoint& previous_point, SkPath& out_path) {
- out_path.lineTo(SkPoint::Make(previous_point.x(), point.y()));
- out_path.lineTo(point);
- previous_point = point;
- };
- switch (style_) {
- case Style::LINES:
- draw_top_line(top_path_, draw_lines_point, path);
- break;
- case Style::SKYLINE:
- draw_top_line(top_path_, draw_skyline_point, path);
- break;
- }
- if (fill_ == Graph::Fill::SOLID) {
- switch (bottom_path_style_) {
- case Style::LINES:
- draw_bottom_line(bottom_path_, draw_lines_point, path);
- break;
- case Style::SKYLINE:
- draw_bottom_line(bottom_path_, draw_bottom_skyline_point, path);
- break;
- }
- }
- cc::PaintFlags flags;
- flags.setAntiAlias(true);
- flags.setBlendMode(SkBlendMode::kSrc);
- const cc::PaintFlags::Style style = (fill_ == Graph::Fill::NONE)
- ? cc::PaintFlags::kStroke_Style
- : cc::PaintFlags::kFill_Style;
- flags.setStyle(style);
- flags.setStrokeWidth(1);
- flags.setColor(color_);
- canvas->DrawPath(path, flags);
- }
- void Graph::UpdateLastValue(float value, float unscaled_value) {
- const size_t last_index = data_.BufferSize() - 1;
- if (!data_.IsFilledIndex(last_index))
- return;
- *data_.MutableReadBuffer(last_index) = value;
- *unscaled_data_.MutableReadBuffer(last_index) = unscaled_value;
- }
- float Graph::GetUnscaledValueAt(size_t index) const {
- if (index >= max_data_points_)
- return 0;
- // 0 - the oldest value
- // BufferSize() - 1 - the newest value.
- const size_t raw_index = index < unscaled_data_.BufferSize()
- ? unscaled_data_.BufferSize() - 1 - index
- : 0;
- // It will CHECK() if index is not populated.
- if (!unscaled_data_.IsFilledIndex(raw_index))
- return 0;
- return unscaled_data_.ReadBuffer(raw_index);
- }
- bool Graph::IsFilledIndex(size_t index) const {
- if (index >= max_data_points_)
- return false;
- // 0 - the oldest value
- // BufferSize() - 1 - the newest value.
- const size_t raw_index =
- index < data_.BufferSize() ? data_.BufferSize() - 1 - index : 0;
- return data_.IsFilledIndex(raw_index);
- }
- void Graph::Reset() {
- data_.Clear();
- unscaled_data_.Clear();
- }
- #if !defined(NDEBUG)
- std::string Graph::DebugDump(const std::string& name) const {
- std::ostringstream os;
- os << name << ": location BLxy [" << graph_bounds_.x() << ", "
- << graph_bounds_.bottom() << "] TRxy [" << graph_bounds_.right() << ", "
- << graph_bounds_.y() << "]";
- const int topsize = static_cast<int>(top_path_.size());
- for (int i = 0; i < topsize; ++i) {
- if ((i > 5) && (i < topsize - 5)) {
- // Skip the middle part.
- os << "\t" << name << ": ...";
- i = topsize - 5;
- }
- if (fill_ == Graph::Fill::SOLID) {
- // Print filled graph as a set of vertical lines.
- if (top_path_.size() == bottom_path_.size()) {
- // Each point on the top has matching point on the bottom.
- os << "\t" << name << ": " << i << ": [" << bottom_path_[i].x() << ", "
- << bottom_path_[i].y() << "] -> [" << top_path_[i].x() << ", "
- << top_path_[i].y() << "]";
- } else {
- // This is the first graph in stack. Use bottom_path_[0].y() as
- // reference.
- os << "\t" << name << ": " << i << ": ["
- << ((i == 0) ? bottom_path_[0].x() : top_path_[i].x()) << ", "
- << bottom_path_[0].y() << "] -> [" << top_path_[i].x() << ", "
- << top_path_[i].y() << "]";
- }
- } else {
- // Print lines graph as a list of dots.
- os << "\t" << name << ": " << i << ": -> [" << top_path_[i].x() << ", "
- << top_path_[i].y() << "]";
- }
- os << "\n";
- }
- return os.str();
- }
- #endif
- } // namespace hud_display
- } // namespace ash
|