1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
ash / hud_display / graph.cc [blame]
// Copyright 2020 The Chromium Authors
// 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::kBaselineBottom) ? -1 : 1;
// Bottom path is always base. So it's visually below the current line when
// kBaselineBottom 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::kBaselineBottom ? -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::kBaselineBottom ? 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::kNone && !top_path_.empty()) {
gfx::RectF graph_bounds_f(graph_bounds);
if (baseline_ == Baseline::kBaselineBottom) {
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::kLines paths.
const auto draw_lines_point =
[](const SkPoint& point, const SkPoint& /*previous_point*/,
SkPath& out_path) { out_path.lineTo(point); };
// Top and bottom Style::kSkyline 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::kLines:
draw_top_line(top_path_, draw_lines_point, path);
break;
case Style::kSkyline:
draw_top_line(top_path_, draw_skyline_point, path);
break;
}
if (fill_ == Graph::Fill::kSolid) {
switch (bottom_path_style_) {
case Style::kLines:
draw_bottom_line(bottom_path_, draw_lines_point, path);
break;
case Style::kSkyline:
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::kNone)
? 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::kSolid) {
// 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