File indexing completed on 2025-12-28 09:13:03
0001
0002
0003
0004
0005
0006
0007
0008
0009 #pragma once
0010
0011 #include <algorithm>
0012 #include <string>
0013 #include <utility>
0014 #include <vector>
0015
0016 #include "actsvg/core.hpp"
0017 #include "actsvg/proto/cluster.hpp"
0018 #include "actsvg/proto/track.hpp"
0019
0020 namespace actsvg {
0021
0022 using namespace defaults;
0023
0024 namespace display {
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039 template <size_t DIM>
0040 std::pair<svg::object, svg::object> cluster(
0041 const svg::object& grid_, const std::string& id_,
0042 const proto::cluster<DIM>& cluster_,
0043 const style::fill& fill_low_ = style::fill{style::color{{255, 255, 0}}},
0044 const style::fill& fill_high_ = style::fill{style::color{{250, 0, 0}}},
0045 const style::fill& fill_m_ = style::fill{style::color{{0, 0, 255}}},
0046 const std::array<unsigned int, 2>& expand_ = {2, 2}) {
0047
0048 svg::object cluster_group = svg::object::create_group(id_);
0049
0050
0051 unsigned int i_min = std::numeric_limits<unsigned int>::max();
0052 unsigned int i_max = std::numeric_limits<unsigned int>::min();
0053 unsigned int j_min = std::numeric_limits<unsigned int>::max();
0054 unsigned int j_max = std::numeric_limits<unsigned int>::min();
0055
0056
0057 scalar low_data = std::numeric_limits<scalar>::max();
0058 scalar high_data = std::numeric_limits<scalar>::lowest();
0059
0060 for (const auto& c : cluster_._channels) {
0061 low_data = std::min(low_data, c._data);
0062 high_data = std::max(high_data, c._data);
0063 }
0064
0065 scalar delta_data = high_data - low_data;
0066
0067 int low_r = fill_low_._fc._rgb[0];
0068 int low_g = fill_low_._fc._rgb[1];
0069 int low_b = fill_low_._fc._rgb[2];
0070 int delta_r = (fill_high_._fc._rgb[0] - low_r);
0071 int delta_g = (fill_high_._fc._rgb[1] - low_g);
0072 int delta_b = (fill_high_._fc._rgb[2] - low_b);
0073
0074
0075 scalar t_x = 0.;
0076 scalar t_y = 0.;
0077 scalar t_r = 0.;
0078 scalar t_phi = 0.;
0079
0080 scalar m_x = 0.;
0081 scalar m_y = 0.;
0082 scalar m_r = 0.;
0083 scalar m_phi = 0.;
0084
0085 scalar d_r = 0.;
0086 scalar d_phi = 0.;
0087
0088 scalar c_x = 0.;
0089 scalar c_y = 0.;
0090 scalar c_r = 0.;
0091
0092 std::array<scalar, 2> x_range = {std::numeric_limits<scalar>::max(),
0093 std::numeric_limits<scalar>::lowest()};
0094 std::array<scalar, 2> y_range = x_range;
0095 std::array<scalar, 2> r_range = x_range;
0096 std::array<scalar, 2> phi_range = x_range;
0097
0098
0099
0100
0101 auto generate_fill =
0102 [&](const proto::channel<DIM>& channel_) -> style::fill {
0103
0104 scalar rel_delta_data = (channel_._data - low_data) / delta_data;
0105
0106 int ch_r = low_r + int(rel_delta_data * delta_r);
0107 int ch_g = low_g + int(rel_delta_data * delta_g);
0108 int ch_b = low_b + int(rel_delta_data * delta_b);
0109
0110 return style::fill{style::color{{ch_r, ch_g, ch_b}}};
0111 };
0112
0113 if constexpr (DIM == 2) {
0114
0115 if (cluster_._type == proto::cluster<DIM>::e_polar) {
0116
0117 m_r = cluster_._measurement[DIM - 2];
0118 m_phi = cluster_._measurement[DIM - 1];
0119
0120 d_r = cluster_._variance[DIM - 2];
0121 d_phi = cluster_._variance[DIM - 1];
0122
0123 t_r = cluster_._measurement[DIM - 2];
0124 t_phi = cluster_._measurement[DIM - 1];
0125
0126 } else {
0127
0128 m_x = cluster_._measurement[DIM - 2];
0129 m_y = cluster_._measurement[DIM - 1];
0130 c_x = cluster_._variance[DIM - 2];
0131 c_y = cluster_._variance[DIM - 1];
0132 t_x = cluster_._truth[DIM - 2];
0133 t_y = cluster_._truth[DIM - 1];
0134
0135
0136 scalar corr = cluster_._correlation;
0137 corr = corr < -1._scalar ? -1._scalar
0138 : (corr > 1._scalar ? 1._scalar : corr);
0139 c_r = -corr * 45;
0140 }
0141 }
0142
0143
0144 for (const auto& c : cluster_._channels) {
0145
0146 size_t i = 0;
0147 size_t j = 0;
0148
0149 if constexpr (DIM == 2) {
0150 i = c._cid[DIM - 2];
0151 j = c._cid[DIM - 1];
0152 }
0153
0154 if constexpr (DIM == 1) {
0155 if (cluster_._coords[DIM - 1] == proto::cluster<DIM>::e_r or
0156 cluster_._coords[DIM - 1] == proto::cluster<DIM>::e_x) {
0157 i = c._cid[DIM - 1];
0158 } else {
0159 j = c._cid[DIM - 1];
0160 }
0161 }
0162
0163 i_min = i < i_min ? i : i_min;
0164 i_max = i_max < i ? i : i_max;
0165 j_min = j < j_min ? j : j_min;
0166 j_max = j_max < j ? j : j_max;
0167
0168
0169
0170 std::string tile_id =
0171 grid_._id + "_" + std::to_string(i) + "_" + std::to_string(j);
0172 std::optional<svg::object> grid_tile = grid_.find_object(tile_id);
0173
0174 if (grid_tile.has_value()) {
0175
0176 svg::object tile = grid_tile.value();
0177
0178 x_range[0] = std::min(x_range[0], tile._x_range[0]);
0179 x_range[1] = std::max(x_range[1], tile._x_range[1]);
0180 y_range[0] = std::min(y_range[0], tile._y_range[0]);
0181 y_range[1] = std::max(y_range[1], tile._y_range[1]);
0182 r_range[0] = std::min(r_range[0], tile._r_range[0]);
0183 r_range[1] = std::max(r_range[1], tile._r_range[1]);
0184 phi_range[0] = std::min(phi_range[0], tile._phi_range[0]);
0185 phi_range[1] = std::max(phi_range[1], tile._phi_range[1]);
0186
0187 auto points = tile._attribute_map.find("points");
0188 if (points != tile._attribute_map.end()) {
0189 svg::object cell;
0190 cell._tag = "polygon";
0191 cell._id = id_ + "_cell_" + std::to_string(i) + "_" +
0192 std::to_string(j);
0193 cell._fill = generate_fill(c);
0194 cell._stroke = style::stroke();
0195 cell._attribute_map["points"] = points->second;
0196
0197 cluster_group.add_object(cell);
0198 }
0199 }
0200 }
0201
0202
0203 if constexpr (DIM == 1) {
0204 if (cluster_._coords[DIM - 1] == proto::cluster<DIM>::e_r) {
0205 m_r = cluster_._measurement[DIM - 1];
0206 t_r = cluster_._truth[DIM - 1];
0207
0208 d_r = cluster_._variance[DIM - 1];
0209 d_phi = std::abs(phi_range[1] - phi_range[0]) /
0210 static_cast<scalar>(std::sqrt(12.));
0211
0212 m_phi = 0.5_scalar * (phi_range[0] + phi_range[1]);
0213 t_phi = m_phi;
0214 } else if (cluster_._coords[DIM - 1] == proto::cluster<DIM>::e_phi) {
0215 m_phi = cluster_._measurement[DIM - 1];
0216 t_phi = cluster_._truth[DIM - 1];
0217
0218 d_r = std::abs(r_range[1] - r_range[0]) /
0219 static_cast<scalar>(std::sqrt(12.));
0220 d_phi = cluster_._variance[DIM - 1];
0221
0222 m_r = 0.5_scalar * (r_range[0] + r_range[1]);
0223 t_r = m_r;
0224 } else if (cluster_._coords[DIM - 1] == proto::cluster<DIM>::e_x) {
0225 m_x = cluster_._measurement[DIM - 1];
0226 t_x = cluster_._truth[DIM - 1];
0227 c_x = cluster_._variance[DIM - 1];
0228 c_y = std::abs(y_range[1] - y_range[0]) /
0229 static_cast<scalar>(std::sqrt(12.));
0230 m_y = 0.5_scalar * (y_range[0] + y_range[1]);
0231 t_y = m_y;
0232
0233
0234 scalar corr = cluster_._correlation;
0235 corr = corr < -1._scalar ? -1._scalar
0236 : (corr > 1._scalar ? 1._scalar : corr);
0237 c_r = -corr * 45;
0238
0239 } else {
0240 m_y = cluster_._measurement[DIM - 1];
0241 t_y = cluster_._truth[DIM - 1];
0242 c_y = cluster_._variance[DIM - 1];
0243 c_x = std::abs(x_range[1] - x_range[0]) /
0244 static_cast<scalar>(std::sqrt(12.));
0245 m_x = 0.5_scalar * (x_range[0] + x_range[1]);
0246 t_x = m_x;
0247
0248
0249 scalar corr = cluster_._correlation;
0250 corr = corr < -1._scalar ? -1._scalar
0251 : (corr > 1._scalar ? 1._scalar : corr);
0252 c_r = corr * 45;
0253 }
0254 }
0255
0256
0257 if (cluster_._type == proto::cluster<DIM>::e_polar) {
0258
0259 m_x = m_r * std::cos(m_phi);
0260 m_y = m_r * std::sin(m_phi);
0261
0262 scalar cos_phi = std::cos(m_phi);
0263 scalar sin_phi = std::sin(m_phi);
0264
0265 c_x = std::abs(cos_phi * d_r - m_r * sin_phi * d_phi);
0266 c_y = std::abs(sin_phi * d_r + m_r * cos_phi * d_phi);
0267
0268 c_r = m_phi * 180 / pi;
0269
0270 t_x = t_r * std::cos(t_phi);
0271 t_y = t_r * std::sin(t_phi);
0272 }
0273
0274
0275 style::fill fill_c = fill_m_;
0276 fill_c._fc._opacity = 0.5;
0277 style::stroke stroke_c;
0278 style::transform t_c;
0279 t_c._rot = {c_r, m_x, m_y};
0280 cluster_group.add_object(
0281 draw::ellipse("c", {m_x, m_y}, {c_x, c_y}, fill_c, stroke_c, t_c));
0282
0283
0284 style::marker marker{"o"};
0285 marker._fill = fill_m_;
0286 cluster_group.add_object(draw::marker("m", {m_x, m_y}, marker));
0287
0288 if (cluster_._mc) {
0289 style::marker truth_marker{"o"};
0290 cluster_group.add_object(draw::marker("t", {t_x, t_y}, truth_marker));
0291 }
0292
0293
0294 svg::object grid_area = svg::object::create_group(id_ + "_focussed_grid");
0295
0296
0297 i_min = (i_min >= expand_[0]) ? i_min - expand_[0] : 0;
0298 i_max += expand_[0];
0299 j_min = (j_min >= expand_[1]) ? j_min - expand_[1] : 0;
0300 j_max += expand_[1];
0301
0302 for (unsigned int i = i_min; i <= i_max; ++i) {
0303 for (unsigned int j = j_min; j < j_max; ++j) {
0304 std::string tile_id =
0305 grid_._id + "_" + std::to_string(i) + "_" + std::to_string(j);
0306 std::optional<svg::object> grid_tile = grid_.find_object(tile_id);
0307 if (grid_tile.has_value()) {
0308 grid_area.add_object(grid_tile.value());
0309 }
0310 }
0311 }
0312
0313 return {cluster_group, grid_area};
0314 }
0315
0316
0317
0318
0319
0320
0321
0322
0323
0324
0325
0326 template <typename trajectory_type, typename view_type>
0327 svg::object trajectory(const std::string& id,
0328 const trajectory_type& trajectory_,
0329 const view_type& view_, bool bezier_ = false) {
0330 svg::object trajectory_group = svg::object::create_group(id);
0331
0332 std::vector<point2> points;
0333 points.reserve(trajectory_._path.size());
0334 std::vector<point2> directions;
0335 directions.reserve(trajectory_._path.size());
0336 for (const auto& [pos, dir] : trajectory_._path) {
0337 points.push_back(view_.point(pos));
0338 if (dir.has_value()) {
0339 point2 dir2 = view_.point(dir.value());
0340
0341 scalar norm = std::sqrt(dir2[0] * dir2[0] + dir2[1] * dir2[1]);
0342 dir2[0] /= norm;
0343 dir2[1] /= norm;
0344 directions.push_back(view_.point(dir2));
0345 }
0346 }
0347
0348
0349 if (directions.empty() or !bezier_) {
0350 trajectory_group.add_object(
0351 draw::polyline(id + "_path", points, trajectory_._path_stroke));
0352 } else {
0353
0354 }
0355
0356
0357 if (trajectory_._origin_size > 0.) {
0358 trajectory_group.add_object(
0359 draw::circle(id + "_origin", view_.point(trajectory_._origin),
0360 trajectory_._origin_size, trajectory_._origin_fill,
0361 trajectory_._origin_stroke));
0362 }
0363
0364
0365 if (trajectory_._path_arrow._size > 0. and not directions.empty()) {
0366 point2 arrow_pos = points.back();
0367 point2 arrow_dir = directions.back();
0368 scalar arrow_phi = std::atan2(arrow_dir[1], arrow_dir[0]);
0369 trajectory_group.add_object(draw::marker(
0370 id + "_arrow", arrow_pos, trajectory_._path_arrow, arrow_phi));
0371 }
0372
0373
0374 return trajectory_group;
0375 }
0376
0377
0378
0379
0380
0381
0382
0383
0384
0385
0386
0387 template <typename seed_type, typename view_type>
0388 svg::object seed(const std::string& id_, const seed_type& seed_,
0389 const view_type& view_) {
0390 svg::object seed_group = svg::object::create_group(id_);
0391
0392
0393 if (seed_._trajectory.has_value()) {
0394 auto traj = trajectory(id_ + "_traj", seed_._trajectory.value(), view_);
0395 seed_group.add_object(traj);
0396 }
0397
0398
0399 for (auto [is, sp] : utils::enumerate(seed_._space_points)) {
0400 auto pos = view_.point(sp);
0401 seed_group.add_object(draw::circle(
0402 id_ + "_sp_" + std::to_string(is), pos, seed_._space_point_size,
0403 seed_._space_point_fill, seed_._space_point_stroke));
0404 }
0405
0406 return seed_group;
0407 }
0408
0409 }
0410 }