Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-03-13 08:35:29

0001 // This file is part of the actsvg packge.
0002 //
0003 // Copyright (C) 2022 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 #include <iostream>
0012 #include <map>
0013 #include <stdexcept>
0014 #include <string>
0015 #include <vector>
0016 
0017 #include "defs.hpp"
0018 #include "generators.hpp"
0019 #include "style.hpp"
0020 #include "svg.hpp"
0021 #include "utils.hpp"
0022 
0023 namespace actsvg {
0024 
0025 namespace detail {
0026 /** Helper method to estimate ranges of an object
0027  *
0028  * @param _o_ is the svg object in question (to be adapted)
0029  * @param vertices_ are the input vertices
0030  **/
0031 static inline void adapt_range(svg::object &_o_,
0032                                const std::vector<point2> &vertices_) {
0033     for (const auto &v : vertices_) {
0034         _o_._x_range = {std::min(_o_._x_range[0], v[0]),
0035                         std::max(_o_._x_range[1], v[0])};
0036         _o_._y_range = {std::min(_o_._y_range[0], v[1]),
0037                         std::max(_o_._y_range[1], v[1])};
0038 
0039         scalar r = std::sqrt(v[0] * v[0] + v[1] * v[1]);
0040         scalar phi = std::atan2(-v[1], v[0]);
0041         _o_._r_range = {std::min(_o_._r_range[0], r),
0042                         std::max(_o_._r_range[1], r)};
0043         _o_._phi_range = {std::min(_o_._phi_range[0], phi),
0044                           std::max(_o_._phi_range[1], phi)};
0045     }
0046 }
0047 
0048 }  // namespace detail
0049 
0050 /** The draw namespace encapsulates the CORRECT
0051  * left handed x-y system from the AWKWARD SVG draw system, and applies
0052  * the transform to it if necessary
0053  *
0054  * That is its main purpose (togehter with given allowing to
0055  * give an identifier an connect objects.
0056  *
0057  * */
0058 
0059 namespace draw {
0060 
0061 /** Method to draw a simple line
0062  *
0063  * @note will perform the y switch
0064  *
0065  * @param id_ is the identification tag of this line
0066  * @param start_ is the start point of the line
0067  * @param end_ is the end point of the line
0068  * @param stroke_ are the stroke parameters
0069  *
0070  * @param transform_ is an optional transform of the object
0071  *
0072  * @note transform is directly applied and not attached as property
0073  * for raw drawing objects
0074  *
0075  * @return an svg object for the line
0076  */
0077 static inline svg::object line(
0078     const std::string &id_, const point2 &start_, const point2 &end_,
0079     const style::stroke &stroke_ = style::stroke(),
0080     const style::transform &transform_ = style::transform()) {
0081     svg::object l;
0082     l._tag = "line";
0083     l._id = id_;
0084 
0085     // Apply the transform & scale
0086     // - the line needs the scale direcly applied
0087     scalar tx = transform_._tr[0];
0088     scalar ty = transform_._tr[1];
0089     scalar sx = transform_._scale[0];
0090     scalar sy = transform_._scale[1];
0091 
0092     scalar st_x = sx * (start_[0] + tx);
0093     scalar st_y = sx * (-start_[1] - ty);
0094     scalar en_x = sy * (end_[0] + ty);
0095     scalar en_y = sy * (-end_[1] - ty);
0096 
0097     l._barycenter =
0098         utils::barycenter<std::array<scalar, 2>>({{st_x, st_y}, {en_x, en_y}});
0099 
0100     // Draw the line, remember the sign flip
0101     l._attribute_map["x1"] = utils::to_string(st_x);
0102     l._attribute_map["y1"] = utils::to_string(st_y);
0103     l._attribute_map["x2"] = utils::to_string(en_x);
0104     l._attribute_map["y2"] = utils::to_string(en_y);
0105 
0106     // Adapt the range of this object
0107     detail::adapt_range(l, {{st_x, st_y}, {en_x, en_y}});
0108 
0109     // Remember the stroke attributes and add them
0110     l._stroke = stroke_;
0111     return l;
0112 }
0113 
0114 /** Method to draw an arc
0115  *
0116  * @param id_ is the identification tag of this line
0117  * @param r_ the radius
0118  * @param start_ is the start point of the line
0119  * @param end_ is the end point of the line
0120  * @param stroke_ are the stroke parameters
0121  *
0122  *
0123  * @note transform is directly applied and not attached as property
0124  * for raw drawing objects
0125  *
0126  * @return an svg object for the line
0127  */
0128 static inline svg::object arc(
0129     const std::string &id_, scalar r_, const point2 &start_, const point2 &end_,
0130     const style::fill &fill_ = style::fill(),
0131     const style::stroke &stroke_ = style::stroke(),
0132     const style::transform &transform_ = style::transform()) {
0133     svg::object a;
0134     a._tag = "path";
0135     a._id = id_;
0136 
0137     // Apply the transform & scale
0138     // - the arc needs the scale directly attached
0139     scalar tx = transform_._tr[0];
0140     scalar ty = transform_._tr[1];
0141     scalar sx = transform_._scale[0];
0142     scalar sy = transform_._scale[1];
0143 
0144     scalar x_min = sx * (start_[0] + tx);
0145     scalar y_min = -sy * (start_[1] + ty);
0146     scalar x_max = sx * (end_[0] + tx);
0147     scalar y_max = -sy * (end_[1] + ty);
0148 
0149     std::string arc_string =
0150         "M " + utils::to_string(x_min) + " " + utils::to_string(y_min);
0151     arc_string +=
0152         " A " + utils::to_string(sx * r_) + " " + utils::to_string(sy * r_);
0153     arc_string += " 0 0 0 ";
0154     arc_string += utils::to_string(x_max) + " " + utils::to_string(y_max);
0155     a._attribute_map["d"] = arc_string;
0156 
0157     // Adapt the range of this object
0158     detail::adapt_range(a, {{x_min, y_min}, {x_max, y_max}});
0159 
0160     /// @todo add sagitta
0161     a._barycenter = utils::barycenter<std::array<scalar, 2>>(
0162         {{x_min, y_min}, {x_max, y_max}});
0163 
0164     // Remember the stroke attributes and add them
0165     a._stroke = stroke_;
0166     a._fill = fill_;
0167     return a;
0168 }
0169 
0170 /** Draw a circle object
0171  *  - will translate into ellipse to allow for a scale
0172  *
0173  * @param id_ is the identification
0174  * @param p_ the position
0175  * @param r_ the radius
0176  * @param fill_ is the fill style
0177  * @param stroke_ is the stroke style
0178  * @param transform_ is the optional transform
0179  *
0180  * @note transform is directly applied and not attached as property
0181  * for raw drawing objects
0182  *
0183  * @return an svg object for the circle
0184  */
0185 static inline svg::object circle(
0186     const std::string &id_, const point2 &p_, scalar r_,
0187     const style::fill &fill_ = style::fill(),
0188     const style::stroke &stroke_ = style::stroke(),
0189     const style::transform &transform_ = style::transform()) {
0190     // Create the object, tag it, id it (if given)
0191     svg::object e;
0192     e._tag = "ellipse";
0193     e._id = id_;
0194 
0195     // Apply the transform & scale
0196 
0197     scalar sx = transform_._scale[0];
0198     scalar sy = transform_._scale[1];
0199     scalar cx = sx * (p_[0] + transform_._tr[0]);
0200     scalar cy = sy * (-p_[1] - transform_._tr[1]);
0201 
0202     // Fill the points attributes
0203     e._attribute_map["cx"] = utils::to_string(cx);
0204     e._attribute_map["cy"] = utils::to_string(cy);
0205     e._attribute_map["rx"] = utils::to_string(r_ * sx);
0206     e._attribute_map["ry"] = utils::to_string(r_ * sy);
0207 
0208     // Adapt the range of this object
0209     detail::adapt_range(
0210         e, {{cx - sx * r_, cy - sy * r_}, {cx + sx * r_, cy + sy * r_}});
0211 
0212     e._barycenter = {cx, cy};
0213 
0214     // Attach fill, stroke & transform attributes and apply
0215     e._fill = fill_;
0216     e._stroke = stroke_;
0217 
0218     // The svg object is now set up
0219     return e;
0220 }
0221 
0222 /** Draw an ellipse object
0223  *
0224  * @param id_ is the identification
0225  * @param p_ the position
0226  * @param rs_ the radii
0227  * @param fill_ is the fill style
0228  * @param stroke_ is the stroke style
0229  * @param transform_ is the optional transform
0230  *
0231  * @note transform is directly applied and not attached as property
0232  * for raw drawing objects
0233  *
0234  * @return an svg object for the ellipse
0235  */
0236 static inline svg::object ellipse(
0237     const std::string &id_, const point2 &p_, const std::array<scalar, 2> &rs_,
0238     const style::fill &fill_ = style::fill(),
0239     const style::stroke &stroke_ = style::stroke(),
0240     const style::transform &transform_ = style::transform()) {
0241 
0242     // Create the object, tag it, id it (if given)
0243     svg::object e;
0244     e._tag = "ellipse";
0245     e._id = id_;
0246 
0247     // Apply the transform & scale
0248     scalar sx = transform_._scale[0];
0249     scalar sy = transform_._scale[1];
0250     scalar cx = sx * (p_[0] + transform_._tr[0]);
0251     scalar cy = sy * (-p_[1] - transform_._tr[1]);
0252 
0253     // Fill the points attributes
0254     e._attribute_map["cx"] = utils::to_string(cx);
0255     e._attribute_map["cy"] = utils::to_string(cy);
0256     e._attribute_map["rx"] = utils::to_string(rs_[0] * sx);
0257     e._attribute_map["ry"] = utils::to_string(rs_[1] * sy);
0258 
0259     // Adapt the range of this object
0260     detail::adapt_range(e, {{cx - sx * rs_[0], cy - sy * rs_[1]},
0261                             {cx + sx * rs_[0], cy + sy * rs_[1]}});
0262 
0263     // Barycenter
0264     e._barycenter = {cx, cy};
0265 
0266     // Attach fill, stroke & transform attributes and apply
0267     e._fill = fill_;
0268     e._stroke = stroke_;
0269     // The svg object is now set up
0270     return e;
0271 }
0272 
0273 /** Draw a polygon object
0274  *
0275  * @param id_ is the identification
0276  * @param polygon_ the polygon points
0277  * @param fill_ is the fill style
0278  * @param stroke_ is the stroke style
0279  * @param transform_ is the optional transform
0280  *
0281  * @note transform is directly applied and not attached as property
0282  * for raw drawing objects
0283  *
0284  * @return an svg object for the polygon
0285  */
0286 static inline svg::object polygon(
0287     const std::string &id_, const std::vector<point2> &polygon_,
0288     const style::fill &fill_ = style::fill(),
0289     const style::stroke &stroke_ = style::stroke(),
0290     const style::transform &transform_ = style::transform())
0291 
0292 {
0293     // Create the object, tag it, id it (if given)
0294     svg::object p;
0295     p._tag = "polygon";
0296     p._id = id_;
0297     // Apply the scale
0298     scalar tx = transform_._tr[0];
0299     scalar ty = transform_._tr[1];
0300     scalar sx = transform_._scale[0];
0301     scalar sy = transform_._scale[1];
0302     // Write attributes and measure object size, length
0303     std::string svg_points_string;
0304     std::vector<point2> display_vertices;
0305     display_vertices.reserve(polygon_.size());
0306     for (auto v : polygon_) {
0307         scalar alpha = transform_._rot[0];
0308         if (alpha != 0.) {
0309             scalar alpha_rad = static_cast<scalar>(alpha / 180. * M_PI);
0310             v = utils::rotate(v, alpha_rad);
0311         }
0312 
0313         // Add display scaling
0314         v[0] *= sx;
0315         v[1] *= sy;
0316         // Add scaled * translation
0317         v[0] += sx * tx;
0318         v[1] += sy * ty;
0319         v[1] *= -1;
0320         // Per vertex range estimation
0321         detail::adapt_range(p, {v});
0322 
0323         // Convert to string attributes, y-switch
0324         svg_points_string += utils::to_string(v[0]);
0325         svg_points_string += ",";
0326         svg_points_string += utils::to_string(v[1]);
0327         svg_points_string += " ";
0328     }
0329     // Barycenter
0330     p._barycenter = utils::barycenter(display_vertices);
0331     // Fill the points attributes
0332     p._attribute_map["points"] = svg_points_string;
0333     // Attach fill, stroke & transform attributes and apply
0334     p._fill = fill_;
0335     p._stroke = stroke_;
0336     // The svg object is now set up
0337     return p;
0338 }
0339 
0340 /** Draw a rectangle object
0341  *
0342  * @param id_ is the rectangle object id
0343  * @param c_ is the center position
0344  * @param half_x is the halflength in x
0345  * @param half_y is the halflength in y
0346  * @param fill_ are the fill parameters
0347  * @param stroke_ are the stroke parameters
0348  * @param transform_ defines the rectangle transform
0349  *
0350  * @return an svg object for the rectangle
0351  */
0352 static inline svg::object rectangle(
0353     const std::string &id_, const point2 &c_, scalar half_x, scalar half_y,
0354     const style::fill &fill_, const style::stroke &stroke_,
0355     const style::transform &transform_ = style::transform()) {
0356 
0357     svg::object r;
0358     r._id = id_;
0359     r._tag = "rect";
0360     // Size attributes
0361     r._attribute_map["x"] = utils::to_string(c_[0] - half_x);
0362     r._attribute_map["y"] = utils::to_string(-c_[1] - half_y);
0363     r._attribute_map["width"] = utils::to_string(half_x * 2);
0364     r._attribute_map["height"] = utils::to_string(half_y * 2);
0365     // Attach style attributes
0366     r._fill = fill_;
0367     r._stroke = stroke_;
0368     r._transform = transform_;
0369     // Return the rectangle objec t
0370     return r;
0371 }
0372 
0373 /** Draw a text object - unconnected
0374  *
0375  * @param id_ is the text object id
0376  * @param p_ is the text position
0377  * @param text_ is the actual text to be drawn
0378  * @param font_ is the font sytle specification
0379  * @param transform_ defines the text transform
0380  *
0381  * @return an svg object for the text
0382  *
0383  **/
0384 static inline svg::object text(
0385     const std::string &id_, const point2 &p_,
0386     const std::vector<std::string> &text_,
0387     const style::font &font_ = style::font(),
0388     const style::transform &transform_ = style::transform()) {
0389     // Create the object, tag it, id it (if given)
0390     svg::object t;
0391     t._tag = "text";
0392     t._id = id_;
0393     // Apply the scale
0394     scalar x = p_[0];
0395     scalar y = p_[1];
0396 
0397     x *= transform_._scale[0];
0398     y *= transform_._scale[1];
0399 
0400     t._fill = font_._fc;
0401 
0402     // Fill the field
0403     t._field = text_;
0404     t._attribute_map["x"] = utils::to_string(x);
0405     t._attribute_map["y"] = utils::to_string(-y);
0406     t._attribute_map["font-family"] = font_._family;
0407     t._attribute_map["font-size"] = std::to_string(font_._size);
0408     t._field_span = font_._size * font_._line_spacing;
0409 
0410     size_t l = 0;
0411     for (const auto &tl : text_) {
0412         l = l > tl.size() ? l : tl.size();
0413     }
0414 
0415     scalar fs = font_._size;
0416 
0417     detail::adapt_range(
0418         t, {{x, y - l}, {static_cast<scalar>(x + 0.7 * fs * l), y + l}});
0419 
0420     t._barycenter = utils::barycenter<std::array<scalar, 2>>(
0421         {{x, y - l}, {static_cast<scalar>(x + 0.7 * fs * l), y + l}});
0422 
0423     return t;
0424 }
0425 
0426 /** Draw a text object - connected
0427  *
0428  * @param id_ is the text object id
0429  * @param p_ is the text position
0430  * @param text_ is the actual text to be drawn
0431  * @param font_ is the font sytle specification
0432  * @param transform_ defines the text transform
0433  * @param object_ is the connected object
0434  * @param highlight_ are the hightlighting options
0435  *
0436  * @return an svg object with highlight connection
0437  *
0438  **/
0439 static inline svg::object connected_text(
0440     const std::string &id_, const point2 &p_,
0441     const std::vector<std::string> &text_, const style::font &font_,
0442     const style::transform &transform_, const svg::object &object_,
0443     const std::vector<std::string> &highlight_ = {"mouseover", "mouseout"}) {
0444     auto t = text(id_, p_, text_, font_, transform_);
0445 
0446     t._attribute_map["display"] = "none";
0447 
0448     svg::object on;
0449     on._tag = "animate";
0450     on._attribute_map["fill"] = "freeze";
0451     on._attribute_map["attributeName"] = "display";
0452     on._attribute_map["from"] = "none";
0453     on._attribute_map["to"] = "block";
0454     on._attribute_map["begin"] = object_._id + __d + highlight_[1];
0455 
0456     svg::object off;
0457 
0458     off._tag = "animate";
0459     off._attribute_map["fill"] = "freeze";
0460     off._attribute_map["attributeName"] = "display";
0461     off._attribute_map["to"] = "none";
0462     off._attribute_map["from"] = "block";
0463     off._attribute_map["begin"] = object_._id + __d + highlight_[0];
0464 
0465     // Store the animation
0466     t._sub_objects.push_back(on);
0467     t._sub_objects.push_back(off);
0468     return t;
0469 }
0470 
0471 /** Draw a text object - connected
0472  *
0473  * @param id_ is the text object id
0474  * @param p_ is the position of the info box
0475  * @param title_ is the title of the info box
0476  * @param title_fill_ is the fill color of the title
0477  * @param title_font_ is the font style of the title
0478  * @param text_ is the actual text to be drawn
0479  * @param text_fill_ is the fill color of the text box
0480  * @param text_font_ is the font style of the text box
0481  * @param stroke_ is the stroke
0482  * @param object_ is the connected object
0483  * @param highlight_ are the hightlighting options
0484  *
0485  * @return an svg object with highlight connection
0486  *
0487  **/
0488 static inline svg::object connected_info_box(
0489     const std::string &id_, const point2 &p_, const std::string &title_,
0490     const style::fill &title_fill_, const style::font &title_font_,
0491     const std::vector<std::string> &text_, const style::fill &text_fill_,
0492     const style::font &text_font_, const style::stroke &stroke_,
0493     const svg::object &object_,
0494     const std::vector<std::string> &highlight_ = {"mouseover", "mouseout"}) {
0495 
0496     svg::object ib;
0497     ib._tag = "g";
0498     ib._id = id_;
0499 
0500     size_t tew = 0;
0501     for (const auto &t : text_) {
0502         tew = t.size() > tew ? t.size() : tew;
0503     }
0504     scalar tews = (tew + 2) * text_font_._size;
0505     scalar tiws = (title_.size() + 2) * title_font_._size;
0506     scalar ws = std::max(tews, tiws);
0507 
0508     scalar tih = 2 * title_font_._size;
0509     scalar teh =
0510         static_cast<scalar>(1.5 * text_.size() + 0.5) * text_font_._size;
0511 
0512     std::vector<std::array<scalar, 2>> tic = {p_,
0513                                               {p_[0], p_[1] - tih},
0514                                               {p_[0] + ws, p_[1] - tih},
0515                                               {p_[0] + ws, p_[1]}};
0516 
0517     auto tibox = polygon(id_ + "_title_box", tic, title_fill_, stroke_);
0518     ib.add_object(tibox);
0519     auto ti = text(id_ + "_title",
0520                    {p_[0] + title_font_._size,
0521                     static_cast<scalar>(p_[1] - 1.5 * title_font_._size)},
0522                    {title_}, title_font_);
0523     ib.add_object(ti);
0524 
0525     std::vector<std::array<scalar, 2>> tec = {{p_[0], p_[1] - tih},
0526                                               {p_[0] + ws, p_[1] - tih},
0527                                               {p_[0] + ws, p_[1] - tih - teh},
0528                                               {p_[0], p_[1] - tih - teh}};
0529 
0530     auto tebox = polygon(id_ + "_text_box", tec, text_fill_, stroke_);
0531     ib.add_object(tebox);
0532     auto te =
0533         text(id_ + "_text",
0534              {p_[0] + title_font_._size, static_cast<scalar>(p_[1] - tih)},
0535              text_, text_font_);
0536     ib.add_object(te);
0537 
0538     // Connect it
0539     if (object_.is_defined()) {
0540         ib._attribute_map["display"] = "none";
0541 
0542         svg::object on;
0543         on._tag = "animate";
0544         on._attribute_map["fill"] = "freeze";
0545         on._attribute_map["attributeName"] = "display";
0546         on._attribute_map["from"] = "none";
0547         on._attribute_map["to"] = "block";
0548         on._attribute_map["begin"] = object_._id + __d + highlight_[1];
0549 
0550         svg::object off;
0551 
0552         off._tag = "animate";
0553         off._attribute_map["fill"] = "freeze";
0554         off._attribute_map["attributeName"] = "display";
0555         off._attribute_map["to"] = "none";
0556         off._attribute_map["from"] = "block";
0557         off._attribute_map["begin"] = object_._id + __d + highlight_[0];
0558 
0559         // Store the animation
0560         ib._sub_objects.push_back(on);
0561         ib._sub_objects.push_back(off);
0562     }
0563     return ib;
0564 }
0565 
0566 /** Draw a tiled cartesian grid - ready for connecting
0567  *
0568  * @param id_ the grid identification
0569  * @param l0_edges_ are the edges in l0
0570  * @param l1_edges_ are the edges in l1
0571  * @param fill_ is the fill style
0572  * @param stroke_ is the stroke style
0573  * @param transform_ is the optional transform
0574  *
0575  * @return a simple cartesian grid
0576  */
0577 static inline svg::object cartesian_grid(
0578     const std::string &id_, const std::vector<scalar> &l0_edges_,
0579     const std::vector<scalar> &l1_edges_,
0580     const style::stroke &stroke_ = style::stroke(),
0581     const style::transform &transform_ = style::transform()) {
0582     // The grid group object
0583     svg::object grid;
0584     grid._tag = "g";
0585     grid._id = id_;
0586 
0587     scalar l0_min = l0_edges_[0];
0588     scalar l0_max = l0_edges_[l0_edges_.size() - 1];
0589 
0590     scalar l1_min = l1_edges_[0];
0591     scalar l1_max = l1_edges_[l1_edges_.size() - 1];
0592 
0593     for (auto [i0, l0] : utils::enumerate(l0_edges_)) {
0594         grid.add_object(line(id_ + "_l0_" + std::to_string(i0), {l0, l1_min},
0595                              {l0, l1_max}, stroke_, transform_));
0596     }
0597 
0598     for (auto [i1, l1] : utils::enumerate(l1_edges_)) {
0599         grid.add_object(line(id_ + "_l1_" + std::to_string(i1), {l0_min, l1},
0600                              {l0_max, l1}, stroke_, transform_));
0601     }
0602 
0603     return grid;
0604 }
0605 
0606 /** Draw a tiled cartesian grid - ready for connecting
0607  *
0608  * @param id_ the grid identification
0609  * @param l0_edges_ are the edges in l0
0610  * @param l1_edges_ are the edges in l1
0611  * @param fill_ is the fill style
0612  * @param stroke_ is the stroke style
0613  * @param transform_ is the optional transform
0614  *
0615  * @note - the single objects of the grid can be found by
0616  * their unique name "id_+_X_Y" when X is the bin in the
0617  * first local and j the bin in the second local coordinate
0618  *
0619  * @return a tiled grid with internal objects
0620  */
0621 static inline svg::object tiled_cartesian_grid(
0622     const std::string &id_, const std::vector<scalar> &l0_edges_,
0623     const std::vector<scalar> &l1_edges_,
0624     const style::fill &fill_ = style::fill(),
0625     const style::stroke &stroke_ = style::stroke(),
0626     const style::transform &transform_ = style::transform()) {
0627     // The grid group object
0628     svg::object grid;
0629     grid._tag = "g";
0630     grid._id = id_;
0631 
0632     // The list of grid sectors
0633     for (size_t il0 = 1; il0 < l0_edges_.size(); ++il0) {
0634         // Grid svg object
0635         std::string gs = id_ + "_";
0636         gs += std::to_string(il0 - 1);
0637         gs += "_";
0638         for (size_t il1 = 1; il1 < l1_edges_.size(); ++il1) {
0639             std::array<scalar, 2u> llc = {l0_edges_[il0 - 1],
0640                                           l1_edges_[il1 - 1]};
0641             std::array<scalar, 2u> lrc = {l0_edges_[il0], l1_edges_[il1 - 1]};
0642             std::array<scalar, 2u> rrc = {l0_edges_[il0], l1_edges_[il1]};
0643             std::array<scalar, 2u> rlc = {l0_edges_[il0 - 1], l1_edges_[il1]};
0644 
0645             std::vector<std::array<scalar, 2u>> tile = {llc, lrc, rrc, rlc};
0646             // Create the grid tile, add auxiliary info and add it to the grid
0647             auto grid_tile = polygon(gs + std::to_string(il1 - 1), tile, fill_,
0648                                      stroke_, transform_);
0649             grid_tile._aux_info = {std::string("* bin (" +
0650                                                std::to_string(il0 - 1) + "," +
0651                                                std::to_string(il1 - 1) + ")")};
0652             grid.add_object(grid_tile);
0653         }
0654     }
0655     return grid;
0656 }
0657 
0658 /** Draw a simple fan grid
0659  *
0660  * @param id_ the grid identification
0661  * @param x_low_edges_ are the edges in x at low y
0662  * @param x_high_edges_ are the edges in x at high y
0663  * @param y_edges_ are the edges in phi
0664  * @param stroke_ is the stroke style
0665  * @param transform_ is the optional transform
0666  *
0667  * @return a simple faned grid structure
0668  */
0669 static inline svg::object fan_grid(
0670     const std::string &id_, const std::vector<scalar> &x_low_edges_,
0671     const std::vector<scalar> &x_high_edges_,
0672     const std::vector<scalar> &y_edges_,
0673     const style::stroke &stroke_ = style::stroke(),
0674     const style::transform &transform_ = style::transform()) noexcept(false) {
0675     // The list of grid sectors
0676     svg::object grid;
0677     grid._tag = "g";
0678     grid._id = id_;
0679 
0680     if (x_low_edges_.size() != x_high_edges_.size()) {
0681         throw std::invalid_argument(
0682             "fan_grid: mismatch in low/high edge numbers");
0683     }
0684 
0685     scalar x_low_min = x_low_edges_[0];
0686     scalar x_low_max = x_low_edges_[x_low_edges_.size() - 1];
0687 
0688     scalar x_high_min = x_high_edges_[0];
0689     scalar x_high_max = x_high_edges_[x_high_edges_.size() - 1];
0690 
0691     scalar y_min = y_edges_[0];
0692     scalar y_max = y_edges_[y_edges_.size() - 1];
0693 
0694     // Calculate slopes
0695     scalar x_low_slope = (x_high_min - x_low_min) / (y_max - y_min);
0696     scalar x_high_slope = (x_high_max - x_low_max) / (y_max - y_min);
0697 
0698     for (const auto [ix, x] : utils::enumerate(x_low_edges_)) {
0699         scalar x_min = x;
0700         scalar x_max = x_high_edges_[ix];
0701         grid.add_object(line(id_ + "_l0_" + std::to_string(ix), {x_min, y_min},
0702                              {x_max, y_max}, stroke_, transform_));
0703     }
0704 
0705     for (const auto &[iy, y] : utils::enumerate(y_edges_)) {
0706         scalar x_min = x_low_min + (y - y_min) * x_low_slope;
0707         scalar x_max = x_low_max + (y - y_min) * x_high_slope;
0708         grid.add_object(line(id_ + "_l1_" + std::to_string(iy), {x_min, y},
0709                              {x_max, y}, stroke_, transform_));
0710     }
0711 
0712     return grid;
0713 }
0714 
0715 /** Draw a simple fan grid
0716  *
0717  * @param id_ the grid identification
0718  * @param x_low_edges_ are the edges in x at low y
0719  * @param x_high_edges_ are the edges in x at high y
0720  * @param y_edges_ are the edges in phi
0721  * @param fill_ is the fill style
0722  * @param stroke_ is the stroke style
0723  * @param transform_ is the optional transform
0724  *
0725  * @note - the single objects of the grid can be found by
0726  * their unique name "id_+_X_Y" when X is the bin in the
0727  * first local and j the bin in the second local coordinate
0728  *
0729  * @return a tiled grid with contained individual cells
0730  */
0731 static inline svg::object tiled_fan_grid(
0732     const std::string &id_, const std::vector<scalar> &x_low_edges_,
0733     const std::vector<scalar> &x_high_edges_,
0734     const std::vector<scalar> &y_edges_,
0735     const style::fill &fill_ = style::fill(),
0736     const style::stroke &stroke_ = style::stroke(),
0737     const style::transform &transform_ = style::transform()) noexcept(false) {
0738     svg::object grid;
0739     grid._tag = "g";
0740     grid._id = id_;
0741 
0742     // The list of grid sectors
0743     std::vector<svg::object> grid_tiles;
0744 
0745     if (x_low_edges_.size() != x_high_edges_.size()) {
0746         throw std::invalid_argument(
0747             "fan_grid: mismatch in low/high edge numbers");
0748     }
0749 
0750     scalar y_min = y_edges_[0];
0751     scalar y_max = y_edges_[y_edges_.size() - 1];
0752 
0753     std::vector<scalar> slopes;
0754     for (auto [ix, xl] : utils::enumerate(x_low_edges_)) {
0755         scalar xh = x_high_edges_[ix];
0756         slopes.push_back((xh - xl) / (y_max - y_min));
0757     }
0758 
0759     for (auto [iy, yl] : utils::enumerate(y_edges_)) {
0760         if (iy + 1 == y_edges_.size()) {
0761             continue;
0762         }
0763         scalar yh = y_edges_[iy + 1];
0764         for (auto [ix, xll] : utils::enumerate(x_low_edges_)) {
0765             if (ix + 1 == x_low_edges_.size()) {
0766                 continue;
0767             }
0768             scalar xlr = x_low_edges_[ix + 1];
0769 
0770             scalar x_l_slope = slopes[ix];
0771             scalar x_r_slope = slopes[ix + 1];
0772 
0773             // the corrected position given the y values
0774             scalar xll_c = xll + x_l_slope * (yl - y_min);
0775             scalar xlr_c = xlr + x_r_slope * (yl - y_min);
0776 
0777             scalar xhl_c = xll + x_l_slope * (yh - y_min);
0778             scalar xhr_c = xlr + x_r_slope * (yh - y_min);
0779 
0780             // Create the grid tile, add auxiliary info and add it
0781             std::string g_n =
0782                 id_ + "_" + std::to_string(ix) + "_" + std::to_string(iy);
0783             auto grid_tile = draw::polygon(
0784                 g_n, {{xll_c, yl}, {xlr_c, yl}, {xhr_c, yh}, {xhl_c, yh}},
0785                 fill_, stroke_, transform_);
0786             grid_tile._aux_info = {std::string("* bin (" + std::to_string(ix) +
0787                                                "," + std::to_string(iy) + ")")};
0788             grid.add_object(grid_tile);
0789         }
0790     }
0791 
0792     return grid;
0793 }
0794 
0795 /** Draw a simple polar grid
0796  *
0797  * @param id_ the grid identification
0798  * @param r_edges_ are the edges in r
0799  * @param phi_edges_ are the edges in phi
0800  * @param stroke_ is the stroke style
0801  * @param transform_ is the optional transform
0802  *
0803  * @return a simple polar grid object
0804  */
0805 static inline svg::object polar_grid(
0806     const std::string &id_, const std::vector<scalar> &r_edges_,
0807     const std::vector<scalar> &phi_edges_,
0808     const style::stroke &stroke_ = style::stroke(),
0809     const style::transform &transform_ = style::transform()) {
0810     // The list of grid sectors
0811     svg::object grid;
0812     grid._tag = "g";
0813     grid._id = id_;
0814 
0815     scalar r_min = r_edges_[0];
0816     scalar r_max = r_edges_[r_edges_.size() - 1];
0817 
0818     scalar phi_min = phi_edges_[0];
0819     scalar phi_max = phi_edges_[phi_edges_.size() - 1];
0820 
0821     scalar cos_phi_min = std::cos(phi_min);
0822     scalar sin_phi_min = std::sin(phi_min);
0823     scalar cos_phi_max = std::cos(phi_max);
0824     scalar sin_phi_max = std::sin(phi_max);
0825 
0826     style::fill fill;
0827 
0828     for (const auto [ir, r] : utils::enumerate(r_edges_)) {
0829         grid.add_object(draw::arc(id_ + "_r_" + std::to_string(ir), r,
0830                                   {r * cos_phi_min, r * sin_phi_min},
0831                                   {r * cos_phi_max, r * sin_phi_max}, fill,
0832                                   stroke_, transform_));
0833     }
0834 
0835     for (const auto &[iphi, phi] : utils::enumerate(phi_edges_)) {
0836         scalar cos_phi = std::cos(phi);
0837         scalar sin_phi = std::sin(phi);
0838         grid.add_object(draw::line(id_ + "_l_" + std::to_string(iphi),
0839                                    {r_min * cos_phi, r_min * sin_phi},
0840                                    {r_max * cos_phi, r_max * sin_phi}, stroke_,
0841                                    transform_));
0842     }
0843     return grid;
0844 }
0845 
0846 /** Draw a connected polar grid
0847  *
0848  * @param id_ the grid identification
0849  * @param r_edges_ are the edges in r
0850  * @param phi_edges_ are the edges in phi
0851  * @param fill_ is the fill style
0852  * @param stroke_ is the stroke style
0853  * @param transform_ is the optional transform
0854  *
0855  * @note - the single objects of the grid can be found by
0856  * their unique name "id_+_X_Y" when X is the bin in the
0857  * first local and j the bin in the second local coordinate
0858  *
0859  * @return a tiled polar grid in individual objects
0860  */
0861 static inline svg::object tiled_polar_grid(
0862     const std::string &id_, const std::vector<scalar> &r_edges_,
0863     const std::vector<scalar> &phi_edges_,
0864     const style::fill &fill_ = style::fill(),
0865     const style::stroke &stroke_ = style::stroke(),
0866     const style::transform &transform_ = style::transform()) {
0867     svg::object grid;
0868     grid._tag = "g";
0869     grid._id = id_;
0870 
0871     for (size_t ir = 1; ir < r_edges_.size(); ++ir) {
0872         // Grid svg object
0873         std::string gs = id_ + "_";
0874         gs += std::to_string(ir - 1);
0875         gs += "_";
0876         for (size_t iphi = 1; iphi < phi_edges_.size(); ++iphi) {
0877             auto sector_contour = generators::sector_contour(
0878                 r_edges_[ir - 1], r_edges_[ir], phi_edges_[iphi - 1],
0879                 phi_edges_[iphi]);
0880             // Create the grid tile, add auxiliary info and add it
0881             auto grid_tile =
0882                 polygon(gs + std::to_string(iphi - 1), sector_contour, fill_,
0883                         stroke_, transform_);
0884             grid_tile._aux_info = {std::string("* bin (" +
0885                                                std::to_string(ir - 1) + "," +
0886                                                std::to_string(iphi - 1) + ")")};
0887             grid.add_object(grid_tile);
0888         }
0889     }
0890     return grid;
0891 }
0892 
0893 /** Marker definition
0894  *
0895  *  Arrorws types are: <, <<, <|, |<, |<<, |<|, o, x, *
0896  * @param id_ is the marker identification
0897  * @param at_ is the poistion of the marker
0898  * @param marker_ is the marker style
0899  * @param rot_ is the rotation in [pi,phi)]
0900  *
0901  * @return an svg object for the marker group
0902  **/
0903 static inline svg::object marker(const std::string &id_, const point2 &at_,
0904                                  const style::marker &marker_,
0905                                  scalar rot_ = 0.) {
0906     svg::object marker_group;
0907     marker_group._tag = "g";
0908 
0909     std::vector<point2> arrow_head;
0910     auto size = marker_._size;
0911 
0912     // Offset due to measureing
0913     scalar m_offset = 0.;
0914 
0915     // It's a measure type
0916     if (marker_._type.substr(0u, 1u) == "|") {
0917         auto measure_line = line(
0918             id_ + "_line", {at_[0], static_cast<scalar>(at_[1] - 2 * size)},
0919             {at_[0], static_cast<scalar>(at_[1] + 2 * size)}, marker_._stroke);
0920         marker_group.add_object(measure_line);
0921         m_offset = -size;
0922     }
0923     // Still an arrow to draw
0924     if (marker_._type.find("<") != std::string::npos) {
0925         arrow_head = {{at_[0] - size + m_offset, at_[1] - size},
0926                       {at_[0] + size + m_offset, at_[1]},
0927                       {at_[0] - size + m_offset, at_[1] + size}};
0928 
0929         // Modify the arrow-type marker
0930         if (marker_._type.find("<<") != std::string::npos) {
0931             // Filled arrow-head
0932             arrow_head.push_back(
0933                 {static_cast<scalar>(at_[0] - 0.25 * size + m_offset), at_[1]});
0934         } else if (marker_._type.substr(1u, marker_._type.size()).find("|") ==
0935                    std::string::npos) {
0936             arrow_head.push_back(
0937                 {static_cast<scalar>(at_[0] + 1 * size + m_offset), at_[1]});
0938         }
0939     } else if (marker_._type.find("o") != std::string::npos) {
0940         // A dot marker
0941         svg::object dot =
0942             circle(id_, at_, 0.5 * size, marker_._fill, marker_._stroke);
0943         marker_group.add_object(dot);
0944     } else if (marker_._type.find("x") != std::string::npos) {
0945         scalar a_x = at_[0];
0946         scalar a_y = at_[1];
0947         scalar h_s = 0.5 * size;
0948         marker_group.add_object(
0949             line(id_ + "_ml0", {a_x - h_s, a_y - h_s}, {a_x + h_s, a_y + h_s}));
0950         marker_group.add_object(
0951             line(id_ + "_ml1", {a_x - h_s, a_y + h_s}, {a_x + h_s, a_y - h_s}));
0952     }
0953 
0954     // Plot the arrow if not empty
0955     if (not arrow_head.empty()) {
0956         auto arrow = polygon(id_, arrow_head, marker_._fill, marker_._stroke);
0957         marker_group.add_object(arrow);
0958     }
0959 
0960     // We need to rotate the marker group
0961     if (rot_ != 0.) {
0962         style::transform group_transform = style::transform{};
0963         group_transform._rot = {static_cast<scalar>(rot_ * 180. / M_PI), at_[0],
0964                                 at_[1]};
0965         marker_group._transform = group_transform;
0966     }
0967 
0968     return marker_group;
0969 }
0970 
0971 /** Draw a measure
0972  *
0973  * @param id_ is the identification tag of this object
0974  * @param start_ is the start point of the line
0975  * @param end_ is the end point of the line
0976  * @param stroke_ are the stroke parameters
0977  * @param start_marker_ are the marker parmeters at start
0978  * @param end_marker_ are the marker parmeters at start
0979  * @param font_ are the font parameters
0980  * @param label_ is the label associated
0981  * @param label_pos_ is the label position
0982  *
0983  * @return an svg object for the measure
0984  */
0985 static inline svg::object measure(
0986     const std::string &id_, const point2 &start_, const point2 &end_,
0987     const style::stroke &stroke_ = style::stroke(),
0988     const style::marker &start_marker_ = style::marker({"|<"}),
0989     const style::marker &end_marker_ = style::marker({"|<"}),
0990     const style::font &font_ = style::font(), const std::string &label_ = "",
0991     const point2 &label_pos_ = {0., 0.}) {
0992     // Measure group here we go
0993     svg::object measure_group;
0994     measure_group._tag = "g";
0995     measure_group._id = id_;
0996 
0997     auto mline = line(id_ + "_line", start_, end_, stroke_);
0998     measure_group.add_object(mline);
0999 
1000     // Calculate the rotation
1001     scalar theta = std::atan2(end_[1] - start_[1], end_[0] - start_[0]);
1002     if (std::abs(end_[1] - start_[1]) <
1003         std::numeric_limits<scalar>::epsilon()) {
1004         theta = (end_[0] > start_[0]) ? 0. : M_PI;
1005     }
1006 
1007     measure_group.add_object(marker(id_ + "_start_tag", {start_[0], start_[1]},
1008                                     start_marker_,
1009                                     M_PI + static_cast<scalar>(theta)));
1010     measure_group.add_object(marker(id_ + "_end_tag", {end_[0], end_[1]},
1011                                     end_marker_, static_cast<scalar>(theta)));
1012 
1013     if (not label_.empty()) {
1014         auto ltext = text(id_ + "_label", label_pos_, {label_}, font_);
1015         measure_group.add_object(ltext);
1016     }
1017     return measure_group;
1018 }
1019 
1020 /** Draw an arc measure
1021  *
1022  * @param id_ is the identification tag of this object
1023  * @param r_ the radius
1024  * @param start_ is the start point of the line, per definition with smaller phi
1025  * @param end_ is the end point of the line, defines the marker
1026  * @param stroke_ are the stroke parameters
1027  * @param start_marker_ are the marker parmeters
1028  * @param end_marker_ are the marker parmeters
1029  * @param font_ are the font parameters
1030  * @param label_ is the label associated
1031  * @param label_pos_ is the label position
1032  *
1033  * @return an svg object for the measure
1034  */
1035 static inline svg::object arc_measure(
1036     const std::string &id_, scalar r_, const point2 &start_, const point2 &end_,
1037     const style::stroke &stroke_ = style::stroke(),
1038     const style::marker &start_marker_ = style::marker(),
1039     const style::marker &end_marker_ = style::marker({"|<"}),
1040     const style::font &font_ = style::font(), const std::string &label_ = "",
1041     const point2 &label_pos_ = {0., 0.}) {
1042     // Measure group here we go
1043     svg::object measure_group;
1044     measure_group._tag = "g";
1045     measure_group._id = id_;
1046 
1047     measure_group.add_object(
1048         arc((id_ + "_arc"), r_, start_, end_, style::fill(), stroke_));
1049 
1050     // Arrow is at end point
1051     if (not start_marker_._type.empty() and
1052         start_marker_._type != std::string("none")) {
1053         scalar theta_start = atan2(start_[1], start_[0]);
1054         measure_group.add_object(
1055             marker(id_ + "_start_tag", {start_[0], start_[1]}, start_marker_,
1056                    static_cast<scalar>(theta_start - 0.5 * M_PI)));
1057     }
1058 
1059     scalar theta_end = atan2(end_[1], end_[0]);
1060     measure_group.add_object(
1061         marker(id_ + "_end_tag", {end_[0], end_[1]}, end_marker_,
1062                static_cast<scalar>(theta_end + 0.5 * M_PI)));
1063 
1064     if (not label_.empty()) {
1065         auto ltext = text(id_ + "_label", label_pos_, {label_}, font_);
1066         measure_group.add_object(ltext);
1067     }
1068 
1069     return measure_group;
1070 }
1071 
1072 /** Draw an arrow
1073  *
1074  * @param id_ is the identification tag of this object
1075  * @param start_ is the start point of the line
1076  * @param end_ is the end point of the line
1077  * @param stroke_ are the stroke parameters
1078  * @param start_marker_ are the marker parmeters at start
1079  * @param end_marker_ are the marker parmeters at start
1080  *
1081  * @return an svg object for the arrow
1082  */
1083 static inline svg::object arrow(
1084     const std::string &id_, const point2 &start_, const point2 &end_,
1085     const style::stroke &stroke_ = style::stroke(),
1086     const style::marker &start_marker_ = style::marker({"|<"}),
1087     const style::marker &end_marker_ = style::marker({"|<"})) {
1088 
1089     return measure(id_, start_, end_, stroke_, start_marker_, end_marker_);
1090 }
1091 
1092 /** Draw an x-y axes system
1093  *
1094  * @param id_ is the id tag of this object
1095  * @param x_range_ is the x range of the axes to be drawn
1096  * @param y_range_ is the y range of the axes to be drawn
1097  * @param stroke_ are the stroke parameters
1098  * @param x_label_ is the x label of the axis system
1099  * @param y_label_ is the y label of the axis system
1100  * @param font_ are the font parameters
1101  * @param markers_ are the 4 markers on each axis end
1102  *
1103  * @return an svg object representing the axes
1104  */
1105 static inline svg::object x_y_axes(
1106     const std::string &id_, const std::array<scalar, 2> &x_range_,
1107     const std::array<scalar, 2> &y_range_,
1108     const style::stroke &stroke_ = style::stroke(),
1109     const std::string &x_label_ = "", const std::string &y_label_ = "",
1110     const style::font &font_ = style::font(),
1111     const style::axis_markers<2u> &markers_ = {__standard_axis_markers,
1112                                                __standard_axis_markers}) {
1113     svg::object axes;
1114     axes._tag = "g";
1115     axes._id = id_;
1116 
1117     auto x =
1118         line(id_ + "_x_axis", {x_range_[0], 0.}, {x_range_[1], 0.}, stroke_);
1119     auto y =
1120         line(id_ + "_y_axis", {0., y_range_[0]}, {0., y_range_[1]}, stroke_);
1121 
1122     axes.add_object(x);
1123     axes.add_object(y);
1124 
1125     /** Helper method to add marker heads
1126      *
1127      * @param p_ is the position of the marker
1128      * @param b0_ and @param b1_ are the accessors into the marker styles
1129      * @param rot_ is the rotation parameter
1130      * @param mid_ is the marker identification
1131      *
1132      * */
1133     auto add_marker = [&](const point2 &p_, unsigned int b0_, unsigned int b1_,
1134                           scalar rot_, const std::string &mid_) -> void {
1135         auto lmarker = markers_[b0_][b1_];
1136         if (not lmarker._type.empty()) {
1137             axes.add_object(marker(mid_, {p_[0], p_[1]}, lmarker, rot_));
1138         }
1139     };
1140 
1141     // Add the markers to the arrows
1142     add_marker({x_range_[0], 0.}, 0, 0, M_PI, id_ + "_neg_x_head");
1143     add_marker({x_range_[1], 0.}, 0, 1, 0., id_ + "pos_x_head");
1144     add_marker({0., y_range_[0]}, 1, 0, -0.5 * M_PI, id_ + "neg_y_head");
1145     add_marker({0., y_range_[1]}, 1, 1, 0.5 * M_PI, id_ + "pos_y_head");
1146 
1147     // Add the labels: x
1148     if (not x_label_.empty()) {
1149         scalar size = markers_[0][1]._size;
1150         auto xlab = text(id_ + "_x_label", {x_range_[1] + 2 * size, size},
1151                          {x_label_}, font_);
1152         axes.add_object(xlab);
1153     }
1154     // Add the labels: y
1155     if (not y_label_.empty()) {
1156         scalar size = markers_[1][1]._size;
1157         auto ylab = text(id_ + "_y_label", {-size, y_range_[1] + 2 * size},
1158                          {y_label_}, font_);
1159         axes.add_object(ylab);
1160     }
1161 
1162     return axes;
1163 }
1164 
1165 /** Place a copy with different attributes via xling
1166  *
1167  * @param id_ the identification of this surface
1168  * @param ro_ the reference object
1169  * @param f_ the fill of the new object
1170  * @param s_ the stroke of the new object
1171  * @param t_ the transform of the new object
1172  *
1173  * @return the create object from reference
1174  */
1175 static inline svg::object from_template(
1176     const std::string &id_, const svg::object &ro_,
1177     const style::fill &f_ = style::fill(),
1178     const style::stroke &s_ = style::stroke(),
1179     const style::transform t_ = style::transform()) {
1180     // Create new svg object
1181     svg::object nsvg;
1182     nsvg._sterile = true;
1183     nsvg._id = id_;
1184     nsvg._tag = "g";
1185 
1186     // Refer to as the linker object
1187     svg::object use_obj;
1188     use_obj._tag = "use";
1189     use_obj._id = id_ + "_use";
1190     use_obj._attribute_map["xlink:href"] = "#" + ro_._id;
1191     use_obj._definitions.push_back(ro_);
1192 
1193     // Barycenter is shifted
1194     use_obj._barycenter = {(ro_._barycenter[0] + t_._tr[0]),
1195                            (ro_._barycenter[1] + t_._tr[1])};
1196 
1197     // Set the fill attributes
1198     use_obj._fill = f_;
1199     use_obj._stroke = s_;
1200     use_obj._transform = t_;
1201 
1202     // Add it to the main object
1203     nsvg.add_object(use_obj);
1204 
1205     return nsvg;
1206 }
1207 
1208 }  // namespace draw
1209 
1210 }  // namespace actsvg