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