Stephan Pleines | 85b295c | 2024-02-04 17:50:26 -0800 | [diff] [blame] | 1 | #include "aos/analysis/in_process_plotter.h" |
James Kuszmaul | 4867136 | 2020-12-24 13:54:16 -0800 | [diff] [blame] | 2 | |
Stephan Pleines | 31f98da | 2024-05-22 17:31:23 -0700 | [diff] [blame] | 3 | #include <algorithm> |
| 4 | #include <ostream> |
| 5 | |
Austin Schuh | 99f7c6a | 2024-06-25 22:07:44 -0700 | [diff] [blame^] | 6 | #include "absl/log/check.h" |
| 7 | #include "absl/log/log.h" |
Stephan Pleines | 31f98da | 2024-05-22 17:31:23 -0700 | [diff] [blame] | 8 | #include "flatbuffers/flatbuffer_builder.h" |
| 9 | #include "flatbuffers/vector.h" |
Stephan Pleines | 31f98da | 2024-05-22 17:31:23 -0700 | [diff] [blame] | 10 | |
James Kuszmaul | 4867136 | 2020-12-24 13:54:16 -0800 | [diff] [blame] | 11 | #include "aos/configuration.h" |
| 12 | |
Stephan Pleines | 9e40c8e | 2024-02-07 20:58:28 -0800 | [diff] [blame] | 13 | namespace aos::analysis { |
James Kuszmaul | 4867136 | 2020-12-24 13:54:16 -0800 | [diff] [blame] | 14 | |
| 15 | namespace { |
James Kuszmaul | ac0912d | 2024-05-21 15:56:59 -0700 | [diff] [blame] | 16 | const char *kDataPath = "../" AOS_REPO_NAME "/aos/analysis/cpp_plot"; |
| 17 | const char *kConfigPath = "../" AOS_REPO_NAME "/aos/analysis/plotter.json"; |
James Kuszmaul | 4867136 | 2020-12-24 13:54:16 -0800 | [diff] [blame] | 18 | } // namespace |
| 19 | |
| 20 | Plotter::Plotter() |
| 21 | : config_(aos::configuration::ReadConfig(kConfigPath)), |
| 22 | event_loop_factory_(&config_.message()), |
| 23 | event_loop_(event_loop_factory_.MakeEventLoop("plotter")), |
| 24 | plot_sender_(event_loop_->MakeSender<Plot>("/analysis")), |
James Kuszmaul | b67409b | 2022-06-20 16:25:03 -0700 | [diff] [blame] | 25 | web_proxy_(event_loop_.get(), event_loop_factory_.scheduler_epoll(), |
| 26 | aos::web_proxy::StoreHistory::kYes, -1), |
James Kuszmaul | 4867136 | 2020-12-24 13:54:16 -0800 | [diff] [blame] | 27 | builder_(plot_sender_.MakeBuilder()) { |
| 28 | web_proxy_.SetDataPath(kDataPath); |
| 29 | event_loop_->SkipTimingReport(); |
Austin Schuh | ea62f60 | 2022-07-18 16:53:04 -0700 | [diff] [blame] | 30 | |
| 31 | color_wheel_.emplace_back(ColorWheelColor{.name = "red", .color = {1, 0, 0}}); |
| 32 | color_wheel_.emplace_back( |
| 33 | ColorWheelColor{.name = "green", .color = {0, 1, 0}}); |
| 34 | color_wheel_.emplace_back( |
| 35 | ColorWheelColor{.name = "purple", .color = {0.54, 0.3, 0.75}}); |
| 36 | color_wheel_.emplace_back( |
| 37 | ColorWheelColor{.name = "blue", .color = {0, 0, 1}}); |
| 38 | color_wheel_.emplace_back( |
| 39 | ColorWheelColor{.name = "yellow", .color = {1, 1, 0}}); |
| 40 | color_wheel_.emplace_back( |
| 41 | ColorWheelColor{.name = "teal", .color = {0, 1, 1}}); |
| 42 | color_wheel_.emplace_back( |
| 43 | ColorWheelColor{.name = "pink", .color = {1, 0, 1}}); |
| 44 | color_wheel_.emplace_back( |
| 45 | ColorWheelColor{.name = "orange", .color = {1, 0.6, 0}}); |
| 46 | color_wheel_.emplace_back( |
| 47 | ColorWheelColor{.name = "brown", .color = {0.6, 0.3, 0}}); |
| 48 | color_wheel_.emplace_back( |
| 49 | ColorWheelColor{.name = "white", .color = {1, 1, 1}}); |
James Kuszmaul | 4867136 | 2020-12-24 13:54:16 -0800 | [diff] [blame] | 50 | } |
| 51 | |
James Kuszmaul | b67409b | 2022-06-20 16:25:03 -0700 | [diff] [blame] | 52 | void Plotter::Spin() { |
| 53 | // Set non-infinite replay rate to avoid pegging a full CPU. |
| 54 | event_loop_factory_.SetRealtimeReplayRate(1.0); |
| 55 | event_loop_factory_.Run(); |
| 56 | } |
James Kuszmaul | 4867136 | 2020-12-24 13:54:16 -0800 | [diff] [blame] | 57 | |
| 58 | void Plotter::Title(std::string_view title) { |
| 59 | title_ = builder_.fbb()->CreateString(title); |
| 60 | } |
| 61 | |
| 62 | void Plotter::AddFigure(std::string_view title, double width, double height) { |
| 63 | MaybeFinishFigure(); |
| 64 | |
| 65 | if (!title.empty()) { |
| 66 | figure_title_ = builder_.fbb()->CreateString(title); |
| 67 | } |
| 68 | |
| 69 | // For positioning, just stack figures vertically. |
| 70 | auto position_builder = builder_.MakeBuilder<Position>(); |
| 71 | position_builder.add_top(next_top_); |
| 72 | position_builder.add_left(0); |
| 73 | position_builder.add_width(width); |
| 74 | position_builder.add_height(height); |
| 75 | position_ = position_builder.Finish(); |
| 76 | |
| 77 | next_top_ += height; |
| 78 | } |
| 79 | |
| 80 | void Plotter::XLabel(std::string_view label) { |
| 81 | xlabel_ = builder_.fbb()->CreateString(label); |
| 82 | } |
| 83 | |
| 84 | void Plotter::YLabel(std::string_view label) { |
| 85 | ylabel_ = builder_.fbb()->CreateString(label); |
| 86 | } |
| 87 | |
| 88 | void Plotter::AddLine(const std::vector<double> &x, |
Austin Schuh | ea62f60 | 2022-07-18 16:53:04 -0700 | [diff] [blame] | 89 | const std::vector<double> &y, LineOptions options) { |
Austin Schuh | 0318e5e | 2023-02-20 17:39:34 -0800 | [diff] [blame] | 90 | CHECK_EQ(x.size(), y.size()) << ": " << options.label; |
James Kuszmaul | 4867136 | 2020-12-24 13:54:16 -0800 | [diff] [blame] | 91 | CHECK(!position_.IsNull()) |
| 92 | << "You must call AddFigure() before calling AddLine()."; |
| 93 | |
| 94 | flatbuffers::Offset<flatbuffers::String> label_offset; |
Austin Schuh | ea62f60 | 2022-07-18 16:53:04 -0700 | [diff] [blame] | 95 | if (!options.label.empty()) { |
| 96 | label_offset = builder_.fbb()->CreateString(options.label); |
James Kuszmaul | 4867136 | 2020-12-24 13:54:16 -0800 | [diff] [blame] | 97 | } |
| 98 | |
| 99 | std::vector<Point> points; |
| 100 | for (size_t ii = 0; ii < x.size(); ++ii) { |
| 101 | points.emplace_back(x[ii], y[ii]); |
| 102 | } |
milind | 1f1dca3 | 2021-07-03 13:50:07 -0700 | [diff] [blame] | 103 | const flatbuffers::Offset<flatbuffers::Vector<const Point *>> points_offset = |
| 104 | builder_.fbb()->CreateVectorOfStructs(points); |
James Kuszmaul | 4867136 | 2020-12-24 13:54:16 -0800 | [diff] [blame] | 105 | |
Austin Schuh | ea62f60 | 2022-07-18 16:53:04 -0700 | [diff] [blame] | 106 | const Color *color; |
| 107 | if (options.color.empty()) { |
| 108 | color = &color_wheel_.at(color_wheel_position_).color; |
| 109 | color_wheel_position_ = (color_wheel_position_ + 1) % color_wheel_.size(); |
| 110 | } else { |
| 111 | auto it = std::find_if( |
| 112 | color_wheel_.begin(), color_wheel_.end(), |
| 113 | [options_color = options.color](const ColorWheelColor &color) { |
| 114 | return color.name == options_color; |
| 115 | }); |
| 116 | CHECK(it != color_wheel_.end()) << ": Failed to find " << options.color; |
| 117 | color = &(it->color); |
| 118 | } |
James Kuszmaul | 4867136 | 2020-12-24 13:54:16 -0800 | [diff] [blame] | 119 | |
James Kuszmaul | 19217a4 | 2022-06-17 10:54:29 -0700 | [diff] [blame] | 120 | LineStyle::Builder style_builder = builder_.MakeBuilder<LineStyle>(); |
Austin Schuh | ea62f60 | 2022-07-18 16:53:04 -0700 | [diff] [blame] | 121 | if (options.line_style.find('*') != options.line_style.npos) { |
Austin Schuh | 69d0b73 | 2022-07-20 21:19:32 -0700 | [diff] [blame] | 122 | style_builder.add_point_size(options.point_size); |
James Kuszmaul | 19217a4 | 2022-06-17 10:54:29 -0700 | [diff] [blame] | 123 | } else { |
| 124 | style_builder.add_point_size(0.0); |
| 125 | } |
Austin Schuh | ea62f60 | 2022-07-18 16:53:04 -0700 | [diff] [blame] | 126 | style_builder.add_draw_line(options.line_style.find('-') != |
| 127 | options.line_style.npos); |
James Kuszmaul | 19217a4 | 2022-06-17 10:54:29 -0700 | [diff] [blame] | 128 | const flatbuffers::Offset<LineStyle> style_offset = style_builder.Finish(); |
| 129 | |
James Kuszmaul | 4867136 | 2020-12-24 13:54:16 -0800 | [diff] [blame] | 130 | auto line_builder = builder_.MakeBuilder<Line>(); |
| 131 | line_builder.add_label(label_offset); |
| 132 | line_builder.add_points(points_offset); |
| 133 | line_builder.add_color(color); |
James Kuszmaul | 19217a4 | 2022-06-17 10:54:29 -0700 | [diff] [blame] | 134 | line_builder.add_style(style_offset); |
James Kuszmaul | 4867136 | 2020-12-24 13:54:16 -0800 | [diff] [blame] | 135 | lines_.push_back(line_builder.Finish()); |
| 136 | } |
| 137 | |
| 138 | void Plotter::MaybeFinishFigure() { |
| 139 | if (!lines_.empty()) { |
| 140 | const flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Line>>> |
| 141 | lines_offset = builder_.fbb()->CreateVector(lines_); |
| 142 | auto figure_builder = builder_.MakeBuilder<Figure>(); |
| 143 | figure_builder.add_title(figure_title_); |
| 144 | figure_builder.add_position(position_); |
| 145 | figure_builder.add_lines(lines_offset); |
| 146 | figure_builder.add_xlabel(xlabel_); |
| 147 | figure_builder.add_share_x_axis(share_x_axis_); |
| 148 | figure_builder.add_ylabel(ylabel_); |
| 149 | figures_.push_back(figure_builder.Finish()); |
| 150 | } |
| 151 | lines_.clear(); |
| 152 | figure_title_.o = 0; |
| 153 | xlabel_.o = 0; |
| 154 | ylabel_.o = 0; |
| 155 | position_.o = 0; |
| 156 | share_x_axis_ = false; |
| 157 | color_wheel_position_ = 0; |
| 158 | } |
| 159 | |
| 160 | void Plotter::Publish() { |
| 161 | MaybeFinishFigure(); |
| 162 | const flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Figure>>> |
| 163 | figures_offset = builder_.fbb()->CreateVector(figures_); |
| 164 | |
| 165 | auto plot_builder = builder_.MakeBuilder<Plot>(); |
| 166 | plot_builder.add_title(title_); |
| 167 | plot_builder.add_figures(figures_offset); |
| 168 | |
Austin Schuh | ea62f60 | 2022-07-18 16:53:04 -0700 | [diff] [blame] | 169 | CHECK_EQ(builder_.Send(plot_builder.Finish()), aos::RawSender::Error::kOk); |
James Kuszmaul | 4867136 | 2020-12-24 13:54:16 -0800 | [diff] [blame] | 170 | |
| 171 | builder_ = plot_sender_.MakeBuilder(); |
| 172 | |
| 173 | title_.o = 0; |
| 174 | figures_.clear(); |
| 175 | next_top_ = 0; |
| 176 | } |
| 177 | |
Stephan Pleines | 9e40c8e | 2024-02-07 20:58:28 -0800 | [diff] [blame] | 178 | } // namespace aos::analysis |