izzi
SVG SUBSET C++ API
Loading...
Searching...
No Matches
a60-svg-render-basics.h
Go to the documentation of this file.
1// svg render basics -*- mode: C++ -*-
2
3// Copyright (C) 2014-2026 Benjamin De Kosnik <b.dekosnik@gmail.com>
4
5// This file is part of the alpha60-MiL SVG library. This library is
6// free software; you can redistribute it and/or modify it under the
7// terms of the GNU General Public License as published by the Free
8// Software Foundation; either version 3, or (at your option) any
9// later version.
10
11// This library is distributed in the hope that it will be useful, but
12// WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14// General Public License for more details.
15
16#ifndef MiL_SVG_RENDER_BASICS_H
17#define MiL_SVG_RENDER_BASICS_H 1
18
19#include <numbers>
20#include <format>
21//#include "a60-svg-codecvt.h"
23
24namespace svg {
25
26double
27scale_proportional_to_area(double radius, double weight)
28{
29 // Scale proportional to area of generated circle.
30 // π = 3.14159265358979323846
31 const double pi(22/7);
32 double a1 = radius * radius * pi;
33 double ap = a1 * weight;
34 double rpa = std::sqrt(ap/pi);
35 return rpa;
36}
37
38
39double
40scale_proportional_to_weight(double radius, double weight)
41{
42 double rpr = radius * weight;
43 return rpr;
44}
45
46
47/// Text element at @param origin, with style and optional transform.
48text_element
49style_text(const string text, const point_2t origin, const typography typo,
50 const string xtransf = "")
51{
52 auto [ x, y ] = origin;
53 text_element::data dt = { x, y, text, typo };
55 t.start_element();
56 t.add_data(dt, xtransf);
58 return t;
59}
60
61
62/// Text element at @param origin, rotated with @param deg centered at
63/// @param rorigin in a @param rr direction. With typograph and style
64/// via @param typo.
65text_element
66style_text_r(const string text, const point_2t origin, const typography& typo,
67 const double deg, const point_2t rorigin,
68 const k::rrotation rr = k::rrotation::none)
69{
70 auto [ rx, ry ] = rorigin;
71 typography typor(typo);
73 if (rr == k::rrotation::cw)
74 {
77 }
78 if (rr == k::rrotation::ccw)
79 {
82 }
83 const string tx = svg::transform::rotate(deg, rx, ry);
84 text_element t = style_text(text, origin, typor, tx);
85 return t;
86}
87
88
89/// Text at @param origin, with style and transform
90void
91styled_text(element_base& obj, const string text, const point_2t origin,
92 const typography typo, const string xform = "")
93{
94 text_element t = style_text(text, origin, typo, xform);
95 obj.add_element(t);
96}
97
98
99/// Text at @param origin, with style and ...
100/// a transformation=rotation of @param deg about origin.
101void
102styled_text_r(element_base& obj, const string text, const point_2t origin,
103 const typography typo, const double deg)
104{
105 text_element t = style_text_r(text, origin, typo, deg, origin);
106 obj.add_element(t);
107}
108
109
110/// Text at @param origin, with style and ...
111/// a transformation=rotation of @param deg about @param rorigin.
112void
113styled_text_r(element_base& obj, const string text, const point_2t origin,
114 const typography typo, const double deg, const point_2t rorigin)
115{
116 text_element t = style_text_r(text, origin, typo, deg, rorigin);
117 obj.add_element(t);
118}
119
120
121/// Text at @param origin, with style and link.
122void
123styled_text_link(element_base& obj, const string text, const point_2t origin,
124 const typography typo, const string uri)
125{
126 // Convert uri to utf8 so that it can be inserted into svg_element
127 // Fucking '&', mostly.
128 // string uriconv = convert_to_utf8(uri);
129 string uriconv(uri);
130 size_t pos = 0;
131 while ((pos = uriconv.find('&', pos)) != std::string::npos)
132 {
133 uriconv.replace(pos, 1, "&amp;");
134 pos += 5;
135 }
136
137 string astart = "<a href=";
138 astart += k::quote;
139 astart += uriconv;
140 astart += k::quote;
141 astart +=">";
142 astart += k::newline;
143 obj.add_raw(astart);
144
145 text_element t = style_text(text, origin, typo);
146 obj.add_element(t);
147
148 string afinish = "</a>";
149 afinish += k::newline;
150 obj.add_raw(afinish);
151}
152
153
154/// Text at size.
155void
156sized_text(element_base& obj, svg::typography typo, const int sz,
157 const string text, const int tx, const int ty)
158{
159 typo._M_size = sz;
160 styled_text(obj, text, {tx, ty}, typo);
161}
162
163
164/// Text at size, with a transformation=rotation.
165void
167 const string text, const int tx, const int ty, const double deg)
168{
169 typo._M_size = sz;
170 styled_text_r(obj, text, {tx, ty}, typo, deg);
171}
172
173
174/// Text of maxlen length, overflow goes on line below.
175uint
176text_line_n(svg_element& obj, const point_2t origin, const string text,
177 const svg::typography typo, const int sz, const uint maxlen)
178{
179 auto [ x, y ] = origin;
180 string textcut(text);
181 while (textcut.size() > maxlen)
182 {
183 // Find last space character in the specified maxium range, aka mark.
184 auto sppos = textcut.find_last_of(k::space, maxlen);
185 if (sppos == string::npos)
186 sppos = maxlen;
187 else
188 {
189 // Cut after space (mark).
190 sppos += 1;
191 }
192
193 string namesubs = textcut.substr(0, sppos);
194 sized_text(obj, typo, sz, namesubs, x, y);
195 textcut = textcut.substr(sppos);
196 y += sz;
197 }
198 sized_text(obj, typo, sz, textcut, x, y);
199 return y;
200}
201
202
203/// Text of maxlen length rotated, overflow goes on line below.
204uint
205text_line_n_r(svg_element& obj, const point_2t origin, const string text,
206 const svg::typography typo, const uint sz, const uint maxlen,
207 const uint lettingsz = 0)
208{
209 const auto [ x, y ] = origin;
210 const double line_max = std::ceil(double(text.size()) / maxlen);
211 const uint lines(line_max);
212
213 uint xmin(x);
214 string textcut(text);
215
216 uint linen(0);
217 while (linen < lines)
218 {
219 // Find x offset, with first line being the max length above the
220 // origin, working down. The last line is at the origin.
221 auto xp = x - ((sz + lettingsz) * (lines - linen - 1));
222
223 size_t epos(0);
224 if (textcut.size() < maxlen)
225 epos = textcut.size();
226 else
227 {
228 // Find last space character in the specified maxium range, aka mark.
229 auto sppos = textcut.find_last_of(k::space, maxlen);
230 if (sppos == string::npos)
231 sppos = maxlen;
232 else
233 {
234 // Cut after space (mark).
235 sppos += 1;
236 }
237 epos = sppos;
238 }
239
240 string namesubs = textcut.substr(0, epos);
241 sized_text_r(obj, typo, sz, namesubs, xp, y, -90);
242 textcut = textcut.substr(epos);
243 ++linen;
244 xmin = std::min(xmin, uint(xp));
245 }
246 return xmin;
247}
248
249
250/// Create rect_element at origin
251rect_element
252make_rect(const point_2t origin, const style s, const area<> a,
253 const string filterstr = "", const string xform = "")
254{
255 auto [ width, height ] = a;
256 auto [ x, y ] = origin;
257
258 rect_element rect;
259 rect_element::data dr = { x, y, width, height };
260 rect.start_element();
261 rect.add_data(dr);
262
263 if (!xform.empty())
264 rect.add_transform(xform);
265
266 rect.add_style(s);
267
268 // Add blur with filter here.
269 if (!filterstr.empty())
270 rect.add_filter(filterstr);
271
272 rect.finish_element();
273 return rect;
274}
275
276
277/// Create rect_element centered at origin
278rect_element
279make_rect_centered(const point_2t origin, const style s, const area<> a,
280 const string filterstr = "", const string xform = "")
281{
282 auto [ width, height ] = a;
283 auto [ x, y ] = origin;
284 x -= (width / 2);
285 y -= (height / 2);
286 point_2t oprime { x, y };
287
288 rect_element rect = make_rect(oprime, s, a, filterstr, xform);
289 return rect;
290}
291
292
293/// Create rectangle element with title and tooltip information.
294rect_element
295make_rect_marker(const point_2t origin, const style s,
296 const space_type r, const string title,
297 const string filterstr = "", const string imgid = "")
298{
299 auto [ cx, cy ] = origin;
300
301 rect_element rect;
302 rect_element::data dr = { cx - r, cy - r, 2 * r, 2 * r };
303 rect.start_element();
304 rect.add_data(dr);
305 rect.add_style(s);
306 if (!filterstr.empty())
307 rect.add_filter(filterstr);
308 if (!imgid.empty())
309 rect.add_raw(imgid);
311 rect.add_title(title);
312 rect.add_raw(string { rect_element::pair_finish_tag } + k::newline);
313
314 return rect;
315}
316
317
318/// Make circle element.
319/// @param origin is the point (x,y) that is the center of the circle
320/// @param s is the visual style
321/// @param r is the circle radius
322/// @param xform is the transform, if any.
323circle_element
324make_circle(const point_2t origin, const style s,
325 const space_type r, const string xform = "")
326{
328 auto [ x, y ] = origin;
329 circle_element::data dc = { x, y, r };
330 c.start_element();
331 c.add_data(dc, xform);
332 c.add_style(s);
333 c.finish_element();
334 return c;
335}
336
337
338/// Make circle element with title and tooltip information.
339/// @param origin is the point (x,y) that is the center of the circle
340/// @param s is the visual style
341/// @param r is the circle radius
342/// @param title is the text to be displayed as a title tooltip
343/// @param img is a link to be displayed as a hover tooltip
344/// @param xform is the transform, if any.
345circle_element
346make_circle_marker(const point_2t origin, const style s,
347 const space_type r, const string title,
348 const string xform = "", const string imgid = "")
349{
351 auto [ x, y ] = origin;
352 circle_element::data dc = { x, y, r };
353 c.start_element();
354 c.add_data(dc, xform);
355 c.add_style(s);
356 if (!imgid.empty())
357 c.add_raw(imgid);
359 c.add_title(title);
360 c.add_raw(string { circle_element::pair_finish_tag } + k::newline);
361 return c;
362}
363
364
365/// Draws a ring centered at origin of radius r, with outer and inner
366/// radial gradient of blurspace in each direction.
367/// klr == fade from
368/// fadeklr == fade to. Background is transparent if none.
369void
371 const space_type radius, const double blurspace,
372 const svg::color klr,
373 const svg::color fadeklr = color::none,
374 const double opacity = 1)
375{
376 using atype = decltype(obj._M_area)::atype;
377
378 auto [ xd, yd ] = origin;
379 const atype x(xd);
380 const atype y(yd);
381
382 // outer ring == upper bound, radius + variance.
383 const double oring = radius + blurspace;
384
385 // inner ring == lower bound, radius - variance.
386 const double iring = radius - blurspace;
387
388 // mangled args for name.
389 std::ostringstream oss;
390 oss << "x" << std::to_string(x) << k::hyphen
391 << "y" << std::to_string(y) << k::hyphen
392 << "r" << std::to_string(radius) << k::hyphen
393 << "blurspace" << std::to_string(blurspace);
394 const string mangle(oss.str());
395
396 // outer
397 // strategy: make a bigger circle cprime, then do a radial gradient to it
398 // starting gradient from color at radius to 100% white/tranparent at cprime.
399 const string rgrado_name(string("radialout") + k::hyphen + mangle);
400 radial_gradient rgrado;
401 rgrado.start_element(rgrado_name);
402 rgrado.stop(rgrado.offset_percentage(radius, oring), fadeklr, 0);
403 rgrado.stop(rgrado.offset_percentage(radius, oring), klr, opacity);
404 rgrado.stop("100%", color::white, 0);
405 rgrado.finish_element();
406 obj.add_element(rgrado);
407
409 circle_element::data dco = { x, y, atype(oring) };
410 co.start_element();
411 co.add_data(dco);
412 co.add_fill(rgrado_name);
413 co.finish_element();
414 obj.add_element(co);
415
416 // inner
417 // strategy: make a smaller circle cprime, then do a radial gradient from it
418 // starting gradient from white/transparent at cprime to color at r.
419 const string rgradi_name(string("radialin") + k::hyphen + mangle);
420 radial_gradient rgradi;
421 rgradi.start_element(rgradi_name);
422 rgradi.stop(rgradi.offset_percentage(iring, radius), fadeklr, 0);
423 rgradi.stop("100%", klr, opacity);
424 rgradi.finish_element();
425 obj.add_element(rgradi);
426
427 // Float/Int conversion and rounding, add one to radius to close gap.
429 circle_element::data dci = { x, y, radius + 1 };
430 ci.start_element();
431 ci.add_data(dci);
432 ci.add_fill(rgradi_name);
433 ci.finish_element();
434 obj.add_element(ci);
435}
436
437
438/// Make polygon element.
439/// @param points vertices in polygon
440/// @param s is the visual style
441polygon_element
442make_polygon(const vrange& points, const style s)
443{
445 c.start_element();
446 c.add_data(points);
447 c.add_style(s);
448 c.finish_element();
449 return c;
450}
451
452
453/// Make polygon element.
454/// @param points vertices in polygon
455/// @param s is the visual style
456polygon_element
457make_polygon_marker(const vrange& points, const style s, const string title,
458 const string xttr = "", const string imgid = "")
459{
461 c.start_element();
462 c.add_data(points);
463 c.add_style(s);
464 if (!imgid.empty())
465 c.add_raw(imgid);
466 if (!xttr.empty())
467 c.add_raw(xttr);
469 c.add_title(title);
470 c.add_raw(string { polygon_element::pair_finish_tag } + k::newline);
471 return c;
472}
473
474
475/// Line primitive.
476line_element
477make_line(const point_2t origin, const point_2t end, style s,
478 const string dasharray = "")
479{
480 auto [ xo, yo ] = origin;
481 auto [ xe, ye ] = end;
482 line_element l;
483 line_element::data dr = { xo, xe, yo, ye };
484 l.start_element();
485 l.add_data(dr, dasharray);
486 l.add_style(s);
487 l.finish_element();
488 return l;
489}
490
491
492/// Lines radiating from center point (x,y).
493group_element
494make_line_rays(const point_2t origin, const style s,
495 const space_type r = 4, const uint nrays = 10)
496{
497 // End points on the ray.
498 // Pick a random ray, use an angle in the range [0, 2pi].
499 static std::mt19937_64 rg(std::random_device{}());
500 auto distr = std::uniform_real_distribution<>(0.0, 2 * 22/7);
501 auto disti = std::uniform_int_distribution<>(-3, 3);
502 auto [ x, y ] = origin;
503
505 g.start_element("rays-" + std::to_string(nrays) + "-" + std::to_string(r));
506 for (uint i = 0; i < nrays; ++i)
507 {
508 double theta = distr(rg);
509 double rvary = disti(rg);
510
511 double xe = x + (r + rvary) * std::cos(theta);
512 double ye = y + (r + rvary) * std::sin(theta);
513
514 line_element::data dr = { x, xe, y, ye };
515 line_element ray;
516 ray.start_element();
517 ray.add_data(dr);
518 ray.add_style(s);
519 ray.finish_element();
520
521 g.add_element(ray);
522 }
523 g.finish_element();
524 return g;
525}
526
527
528/// Polyline primitive.
529/// @param points the points in the polyline
530/// @param s style for the polyline
531/// @param s stroke_style for the polyline
532polyline_element
533make_polyline(const vrange& points, const style s,
534 const stroke_style sstyle = { })
535{
536 polyline_element pl(points);
537 pl.start_element();
538 pl.add_data(sstyle);
539 pl.add_style(s);
540 pl.finish_element();
541 return pl;
542}
543
544
545/// Angle in radians.
547get_circumference_point_r(const double angler, const double r,
548 const point_2t origin)
549{
550 auto [ cx, cy ] = origin;
551 double x(cx + (r * std::cos(angler)));
552 double y(cy - (r * std::sin(angler)));
553 return std::make_tuple(x, y);
554}
555
556
557/// Angle in degrees.
559get_circumference_point_d(const double ad, const double r,
560 const point_2t origin)
561{
562 double angler = (k::pi / 180.0) * ad;
563 return get_circumference_point_r(angler, r, origin);
564}
565
566
567/// Zero degrees is top, going clockwise (cw).
568double
570{
571 // Change rotation to CW instead of CCW (or anti-clockwise).
572 angled = 360 - angled;
573
574 // Rotate 90 CCW, so that the first element will be at the top
575 // vertical axis, instead of the right middle axis.
576 angled += 90;
577
578 return angled;
579}
580
581
582/// Zero degrees is top, going clockwise (cw).
583double
585{
586 // Rotate 90 CCW, so that the first element will be at the top
587 // vertical axis, instead of the right middle axis.
588 angled += 90;
589
590 return angled;
591}
592
593
594/// Make single path segment.
595string
597{
598 std::ostringstream ossa;
599 for (uint i = 0; i < lpoints.size(); ++i)
600 {
601 auto [x, y ] = lpoints[i];
602 // SVG path_element.
603 // start at "M x y" and
604 // each subsequent line segment is of form "L x y"
605 if (i == 0)
606 ossa << "M" << k::space;
607 else
608 ossa << "L" << k::space;
609 ossa << x << svg::k::space << y << k::space;
610 }
611 return ossa.str();
612}
613
614
615/// Draw path given serialized path data.
616/// Can be used to make pinstripes, ie top and bottom line layers.
617/// top style defaults: fill opac(0), stroke opac(1), stroke sz 1
618/// bottom style defaults: fill opac(0), stroke opac(1), stroke sz 1.25
619path_element
620make_path(const string& pathda, const style& styl, const string id = "",
621 const bool selfclosingtagp = true, const string xattr = "")
622{
623 // Draw path with this endpoint.
624 path_element pe;
626 {
627 path_element::data da = { pathda, 0 };
628 if (id.empty())
629 pe.start_element();
630 else
631 pe.start_element(id);
632 pe.add_data(da);
633 pe.add_style(styl);
634 if (!xattr.empty())
635 pe.add_raw(xattr);
636 if (selfclosingtagp)
637 pe.finish_element();
638 else
640 }
641 return pe;
642}
643
644
645/// Center an polygon at this point.
646/// radius 4 is pixels to draw out from center point.
647/// pointsn is number of points to draw (8 for octogon)
648path_element
649make_path_polygon(const point_2t origin, const style s,
650 const double r, const uint pointsn,
651 const bool selfclosingtagp = true, const string xattr = "")
652{
653 // Find points: orig, orig + (120 x 1), orig + (120 x 2).
654 const double angle(360.0/pointsn);
655 double zo = zero_angle_north_cw(angle);
656
657 // Find n points on a circle, connnected.
658 vrange pointz;
659 for (uint i = 0; i < pointsn; ++i)
660 {
661 point_2t p = get_circumference_point_d(zo + (angle * i), r, origin);
662 pointz.push_back(p);
663 }
664
665 // Final point to close path.
666 pointz.push_back(pointz.front());
667 string pathda = make_path_data_from_points(pointz);
668
669 const string id = "polygon-n" + std::to_string(pointsn) + "-r" + std::to_string(r);
670 path_element polyg = make_path(pathda, s, id, selfclosingtagp, xattr);
671 return polyg;
672}
673
674
675/// Make a polygon marker for line graphs.
676path_element
677make_path_marker(const point_2t origin, const style s,
678 const double r, const uint pointsn, const string title,
679 const string xattr = "")
680{
681 path_element polyg = make_path_polygon(origin, s, r, pointsn, false, xattr);
682 polyg.add_title(title);
683 polyg.add_raw(string { path_element::pair_finish_tag } + k::newline);
684 return polyg;
685}
686
687
688/// Make path segment between two points on a circumference of radius r.
689/// Points like: get_circumference_point_d(zero_angle_north_cw(0), r, origin)
690string
692 const space_type r, const int arcflag = 0,
693 const int sweepflag = 1)
694{
695 // Define arc.
696 // A rx ry x-axis-rotation large-arc-flag sweep-flag x y
697 std::ostringstream oss;
698 oss << "M" << k::space << to_string(start) << k::space;
699 oss << "A" << k::space;
700 oss << std::to_string(r) << k::space << std::to_string(r) << k::space;
701 oss << 0 << k::space << arcflag << k::space << sweepflag << k::space;
702 oss << to_string(end) << k::space;
703 return oss.str();
704}
705
706
707/// Make closed path between two points and the center of a circle of radius r.
708/// Points like: get_circumference_point_d(zero_angle_north_cw(0), r, origin)
709string
710make_path_arc_closed(const point_2t& origin, const point_2t& start,
711 const point_2t& end, const space_type r,
712 const int arcflag = 0, const int sweepflag = 0)
713{
714 // Define path as starting at origin, line to circumference point start,
715 // arc to circumfernce point end, line back to origin.
716 // A rx ry x-axis-rotation large-arc-flag sweep-flag x y
717 // where (large) arc flag is true if arc angle delta is > 180
718 // where sweep flag is
719 // true if outer (movement CW)
720 // false if inner (CCW).
721 std::ostringstream oss;
722 oss << "M" << k::space << to_string(origin) << k::space;
723 oss << "L" << k::space << to_string(start) << k::space;
724 oss << "A" << k::space;
725 oss << std::to_string(r) << k::space << std::to_string(r) << k::space;
726 oss << 0 << k::space << arcflag << k::space << sweepflag << k::space;
727 oss << to_string(end) << k::space;
728 oss << "L" << k::space << to_string(origin) << k::space;
729 return oss.str();
730}
731
732
733/// Same but with degree range arguments instead of points.
734/// NB: Assumes appropriate zero_angle_north_cw/ccw adjustments on startd/endd.
735string
736make_path_arc_closed(const point_2t& origin, const double startd,
737 const double endd, const space_type r,
738 const int arcflag = 0, const int sweepflag = 0)
739{
740 const point_2t start = get_circumference_point_d(startd, r, origin);
741 const point_2t end = get_circumference_point_d(endd, r, origin);
742 return make_path_arc_closed(origin, start, end, r, arcflag, sweepflag);
743}
744
745
746/// Make blob shape
747/// @param origin center of the shape.
748/// @param s style for the polyline
749/// @param size radius of mark
750/// @param numCurves number of curves between 5-8 optimal
751path_element
752make_path_blob(const point_2t origin, const style s, const double size,
753 const int numCurves = 5 + std::rand() % 4,
754 const string tipstr = "")
755{
756 auto [ ox, oy ] = origin;
757 std::srand(std::time(0));
758
759 // Generate main points
760 vrange points;
761 vrange controlPoints;
762 for (int i = 0; i < numCurves; i++)
763 {
764 double angle = (2 * k::pi * i) / numCurves;
765 double variation = 0.5 + (std::rand() % 100) / 100.0;
766 double radius = size * variation;
767
768 double x = ox + radius * cos(angle);
769 double y = oy + radius * sin(angle);
770 points.push_back({x, y});
771
772 // Generate control point for this segment
773 double controlAngle = angle + k::pi / numCurves;
774 double controlRadius = size * (0.3 + 0.4 * (std::rand() % 100) / 100.0);
775 double cx = ox + controlRadius * cos(controlAngle);
776 double cy = oy + controlRadius * sin(controlAngle);
777 controlPoints.push_back({cx, cy});
778 }
779
780 // Start the path data
781 std::stringstream data;
782 auto [ p0x, p0y ] = points[0];
783 data << "M" << p0x << "," << p0y << k::space;
784
785 // Create smooth quadratic curves
786 for (int i = 0; i < numCurves; i++)
787 {
788 int nextIdx = (i + 1) % numCurves;
789
790 // Use control point from next segment for smooth transition
791 auto [ cx, cy ] = controlPoints[nextIdx];
792 auto [ pix, piy ] = points[i];
793 auto [ pnx, pny ] = points[nextIdx];
794
795 // Adjust control point to ensure smooth connection
796 double smoothX = (pix + pnx) / 2;
797 double smoothY = (piy + pny) / 2;
798
799 cx = cx * 0.6 + smoothX * 0.4;
800 cy = cy * 0.6 + smoothY * 0.4;
801
802 data << " Q" << cx << "," << cy << " " << pnx << "," << pny;
803 }
804
805 data << " Z";
806 string pdata = data.str();
807
808 string id = "blob-" + std::to_string(size);
809 if (tipstr.empty())
810 {
811 path_element p = make_path(pdata, s, id);
812 return p;
813 }
814 else
815 {
816 path_element p = make_path(pdata, s, id, false);
817 p.add_title(tipstr);
818 p.add_raw(string { path_element::pair_finish_tag } + k::newline);
819 return p;
820 }
821}
822
823
824/// Plus or x tilt mark as closed path that can be filled.
825path_element
826make_path_center_mark(const point_2t& origin, const style styl,
827 const int len, const int width,
828 const string xform = "", const string tipstr = "")
829{
830 // Define path as starting at origin, move half width up and to left then
831 // move around as if making a plus sign.
832 const auto [ xo, yo ] = origin;
833
834 // Move to top left of origin, start here.
835 const double whalf(width / 2);
836 const int lenw = len - whalf;
837 const auto x = xo - whalf;
838 const auto y = yo - whalf;
839
840 std::ostringstream oss;
841 oss << "M" << k::space << x << k::comma << y << k::space;
842
843 // left
844 oss << "H" << k::space << x - lenw << k::space;
845 oss << "V" << k::space << y + width << k::space;
846 oss << "H" << k::space << x << k::space;
847
848 // bottom
849 oss << "V" << k::space << y + lenw + width << k::space;
850 oss << "H" << k::space << x + width << k::space;
851 oss << "V" << k::space << y + width << k::space; // bottom part
852
853 // right
854 oss << "H" << k::space << x + lenw + width << k::space;
855 oss << "V" << k::space << y << k::space;
856 oss << "H" << k::space << x + width << k::space;
857
858 // top
859 oss << "V" << k::space << y - lenw << k::space;
860 oss << "H" << k::space << x << k::space;
861 oss << "V" << k::space << y << k::space;
862
863 const string pathdata = oss.str();
864
865 string id("center-mark-");
866 string attr(std::to_string(width) + "-" + std::to_string(len));
867 id += attr;
868
869 if (tipstr.empty())
870 {
871 path_element p = make_path(pathdata, styl, id, true, xform);
872 return p;
873 }
874 else
875 {
876 path_element p = make_path(pathdata, styl, id, false, xform);
877 p.add_title(tipstr);
878 p.add_raw(string { path_element::pair_finish_tag } + k::newline);
879 return p;
880 }
881}
882
883
884/// Make waves.
885/// @param points the points in the polyline
886/// @param s style for the polyline
887/// @param s stroke_style for the polyline
888path_element
889make_path_ripple(const point_2t origin, const style s, const double length,
890 const double amplitude, const double decay,
891 const int cycles = 3,
892 const string tipstr = "")
893{
894 // Sine Wave with Decay
895 auto [ startX, startY ] = origin;
896
897 // Calculate key inflection points (minimal set)
898 vrange keyPoints;
899 keyPoints.reserve(5);
900
901 // Add start point.
902 keyPoints.emplace_back(startX, startY);
903
904 // Calculate quarter cycle points
905 for (int i = 1; i <= cycles * 4; ++i)
906 {
907 double t = static_cast<double>(i) / (cycles * 4);
908 double angle = t * cycles * 2 * svg::k::pi;
909 double x = startX + t * length;
910 double decayFactor = exp(-decay * t);
911 double y = startY - amplitude * decayFactor * sin(angle);
912 keyPoints.push_back({x, y});
913 }
914
915 // Add end and bottom points for closure
916 keyPoints.push_back({startX + length, startY + amplitude * 0.1});
917 keyPoints.push_back({startX, startY + amplitude * 0.1});
918
919 // Build path with quadratic curves
920 auto [ kp0x, kp0y ] = keyPoints[0];
921 string pdata = std::format("M{:.2f},{:.2f}", kp0x, kp0y);
922 for (size_t i = 1; i < keyPoints.size() - 2; i += 2)
923 {
924 // Use every other point as control point
925 if (i + 1 < keyPoints.size())
926 {
927 auto [ kpix, kpiy ] = keyPoints[i];
928 auto [ kpnx, kpny ] = keyPoints[i + 1];
929 pdata += std::format(" Q{:.2f},{:.2f} {:.2f},{:.2f}", kpix, kpiy, kpnx, kpny);
930 }
931 }
932 pdata += " Z";
933
934 string id = "sine-decay-" + std::to_string(length) + k::hyphen + std::to_string(cycles);
935 if (tipstr.empty())
936 {
937 path_element p = make_path(pdata, s, id);
938 return p;
939 }
940 else
941 {
942 path_element p = make_path(pdata, s, id, false);
943 p.add_title(tipstr);
944 p.add_raw(string { path_element::pair_finish_tag } + k::newline);
945 return p;
946 }
947}
948
949
950/// Rectangles of various sizes rotated from center point (x,y).
951group_element
952make_sunburst(const point_2t origin, const style s,
953 const space_type r = 4, const uint nrays = 10,
954 const string tipstr = "")
955{
956 auto [ ox, oy ] = origin;
957 const space_type rwidth = r / 6; // r/6, center mark uses r/3
958
960 string gid = "sunburst-" + std::to_string(nrays) + "-" + std::to_string(r);
961 string xformg = transform::translate(ox, oy);
962 g.start_element(gid, xformg);
963
964 // Calculate angle step between rectangles
965 double angledelta = 2 * k::pi / nrays;
966 for (uint i = 0; i < nrays; ++i)
967 {
968 double angle = i * angledelta;
969
970 // Calculate position on circle
971 double xPos = r / 2 * std::cos(angle);
972 double yPos = r / 2 * std::sin(angle);
973
974 // Create transformation string
975 // Translate to position, then rotate around the origin
976 // Rectangles are positioned with their centers at the circle point
977 string xformrt = transform::translate(xPos, yPos);
978 string xformrr = transform::rotate(angle * 180 / k::pi);
979 string xformrmega = xformrt + k::space + xformrr;
980
981 area<> a = { rwidth, r };
982 point_2t rcp = { -(rwidth / 2), -(r / 2)};
983 rect_element ray = make_rect_centered(rcp, s, a, "", xformrmega);
984 g.add_element(ray);
985 }
986
987 if (!tipstr.empty())
988 g.add_title(tipstr);
989 g.finish_element();
990 return g;
991}
992
993
994/// Make lauburu
995/// @param origin center of the shape.
996/// @param s style for the polyline
997/// @param size radius of mark
998path_element
999make_lauburu(const point_2t origin, const style s, const double size,
1000 double swirlFactor = 0.5, int pointsPerSwirl = 8,
1001 const string tipstr = "")
1002{
1003 using namespace std::numbers;
1004 const double pi = pi_v<double>;
1005
1006 auto [ centerX, centerY ] = origin;
1007
1008 // Traditional lauburu has 4 comma-shaped swirls
1009 const int swirls = 4;
1010 const double swirlAngle = 2 * pi / swirls;
1011
1012 // Start at the outer point of first swirl
1013 double startAngle = -pi / 4;
1014 double startX = centerX + size * std::cos(startAngle);
1015 double startY = centerY + size * std::sin(startAngle);
1016
1017 std::string svgPath;
1018 svgPath = std::format("M{:.2f},{:.2f}", startX, startY);
1019
1020 // Generate each comma swirl
1021 for (int swirl = 0; swirl < swirls; ++swirl)
1022 {
1023 double baseAngle = startAngle + swirl * swirlAngle;
1024
1025 // Generate points for one comma swirl
1026 std::vector<std::pair<double, double>> points;
1027
1028 for (int i = 0; i <= pointsPerSwirl; ++i)
1029 {
1030 double t = static_cast<double>(i) / pointsPerSwirl;
1031 double angle = baseAngle + t * (3 * pi / 2); // 270 degree swirl
1032
1033 // Radius decreases as we swirl inward
1034 double radius = size * (1.0 - t * swirlFactor);
1035
1036 double x = centerX + radius * std::cos(angle);
1037 double y = centerY + radius * std::sin(angle);
1038 points.emplace_back(x, y);
1039 }
1040
1041 // Create smooth curve through points
1042 for (size_t i = 1; i < points.size(); ++i)
1043 {
1044 double prevX = points[i-1].first;
1045 double prevY = points[i-1].second;
1046 double currX = points[i].first;
1047 double currY = points[i].second;
1048
1049 // Calculate control point for quadratic Bézier
1050 double midX = (prevX + currX) / 2;
1051 double midY = (prevY + currY) / 2;
1052
1053 // Offset control point for smoothness
1054 double offset = size * 0.1;
1055 double ctrlX = midX + offset * std::cos(baseAngle + pi/2);
1056 double ctrlY = midY + offset * std::sin(baseAngle + pi/2);
1057
1058 svgPath += std::format(" Q{:.2f},{:.2f} {:.2f},{:.2f}",
1059 ctrlX, ctrlY, currX, currY);
1060 }
1061 }
1062
1063 svgPath += " Z";
1064
1065 string id = "lauburu-" + std::to_string(size);
1066
1067 if (tipstr.empty())
1068 {
1069 path_element p = make_path(svgPath, s, id);
1070 return p;
1071 }
1072 else
1073 {
1074 path_element p = make_path(svgPath, s, id, false);
1075 p.add_title(tipstr);
1076 p.add_raw(string { path_element::pair_finish_tag } + k::newline);
1077 return p;
1078 }
1079}
1080
1081
1082
1083// Hexagon and tessalations.
1084
1085/// Center rings of hexagons at this point.
1086/// @param origin is the center point
1087/// @param r is the radius/side length of hexagon.
1088/// @param hexn is the number of hexagons total
1089/// @param cfillp is the center of the hexagon filled or open
1090/// @param styl apply as style to this element
1091/// @param xform any optional transform
1092group_element
1093make_hexagon_honeycomb(const point_2t origin, const double r,
1094 const uint hexn, const bool cfillp,
1095 const style styl, const string xform = "")
1096{
1097 using std::to_string;
1098
1099 group_element g;
1100 string gbase = "hexagon-honeycomb-";
1101 string gname = gbase + to_string(uint(r)) + k::hyphen + to_string(hexn);
1102 g.start_element(gname, xform);
1103
1104 auto hexpoints = radiate_hexagon_honeycomb(origin, r, hexn, cfillp);
1105 for (const auto& phex : hexpoints)
1106 {
1107 // Make hexagon spiral.
1108 //auto [ p, d ] = phex;
1109 path_element pth = make_path_polygon(phex, styl, r, 6);
1110 g.add_element(pth);
1111 }
1112
1113 g.finish_element();
1114 return g;
1115}
1116
1117
1118/// Center rings of text in a hexagon pattern at this point.
1119/// @param origin is the center point
1120/// @param r is the radius/side length of hexagon.
1121/// @param hexn is the number of hexagons total
1122/// @param cfillp is the center of the hexagon filled or open
1123/// @param s is the text
1124/// @param typo is the typography for the text
1125/// @param xform any optional transform
1126group_element
1127make_text_honeycomb(const point_2t origin, const double r,
1128 const uint hexn, const bool cfillp, const string s,
1129 const typography typo, const string xform = "")
1130{
1131 using std::to_string;
1132
1133 group_element g;
1134 string gbase = "text-honeycomb-";
1135 string gname = gbase + to_string(uint(r)) + k::hyphen + to_string(hexn);
1136 g.start_element(gname, xform);
1137
1138 // Group all text objects here, and specify that rotation for the
1139 // group is to be centered at the origin.
1140 auto [ x, y ] = origin;
1141 const string txrotatepoint = svg::transform::rotate(0, x, y);
1142 group_element ginner;
1143 ginner.start_element(gbase + "inner", txrotatepoint);
1144
1145 auto hexpoints = radiate_hexagon_honeycomb(origin, r, hexn, cfillp);
1146 auto hexangles = get_honeycomb_angles(origin, hexpoints, true);
1147 for (uint i = 0; !s.empty() && i < hexpoints.size(); i++)
1148 {
1149 const auto& p = hexpoints[i];
1150 const double d = hexangles[i];
1151 text_element t = style_text_r(s, p, typo, d, p, k::rrotation::cw);
1152 //text_element t = style_text_r(s, p, typo, d, origin, k::rrotation::cw);
1153 ginner.add_element(t);
1154 }
1155
1156 ginner.finish_element();
1157
1158 g.add_element(ginner);
1159 g.finish_element();
1160
1161 return g;
1162}
1163
1164
1165/// Make octahedron shape (8) in 2D simulated 3D
1166/// @ret group element of polygon_elements
1167//polyline_element
1168group_element
1169//make_octahedron(const point_2t origin, const style& s, const double radius)
1170make_octahedron(const point_2t, const style&, const double radius,
1171 const string tipstr = "")
1172{
1173 // Draw faces as polygons, group as meta polygon.
1174 // Outer group.
1175 group_element go;
1176 go.start_element("polygon-oct-r-" + std::to_string(radius));
1177 go.add_title(tipstr);
1178 go.finish_element();
1179 return go;
1180}
1181
1182
1183/// Make icosahedron shape (20) in 2D simulated 3D
1184//make_icosahedron_3d(int centerX, int centerY, int size)
1185group_element
1186make_icosahedron(const point_2t origin, const style& s, const double radius,
1187 const string tipstr = "")
1188{
1189 auto [ centerX, centerY ] = origin;
1190
1191 // Icosahedron vertices (12 vertices)
1192 double phi = (1 + sqrt(5)) / 2; // Golden ratio
1193 double vertices[12][3] =
1194 {
1195 {0, 1, phi}, {0, 1, -phi}, {0, -1, phi}, {0, -1, -phi},
1196 {1, phi, 0}, {1, -phi, 0}, {-1, phi, 0}, {-1, -phi, 0},
1197 {phi, 0, 1}, {phi, 0, -1}, {-phi, 0, 1}, {-phi, 0, -1}
1198 };
1199
1200 // Normalize and scale vertices
1201 double normalized[12][3];
1202 for (int i = 0; i < 12; i++)
1203 {
1204 double length = sqrt(vertices[i][0]*vertices[i][0] +
1205 vertices[i][1]*vertices[i][1] +
1206 vertices[i][2]*vertices[i][2]);
1207 normalized[i][0] = vertices[i][0] / length;
1208 normalized[i][1] = vertices[i][1] / length;
1209 normalized[i][2] = vertices[i][2] / length;
1210 }
1211
1212 // Project vertices
1213 int projX[12], projY[12];
1214 for (int i = 0; i < 12; i++)
1215 {
1216 projX[i] = centerX + radius * (normalized[i][0] * 0.707 - normalized[i][2] * 0.707);
1217 projY[i] = centerY + radius * (normalized[i][0] * 0.408 + normalized[i][1] * 0.816 + normalized[i][2] * 0.408);
1218 }
1219
1220 // Icosahedron edges (30 edges)
1221 int edges[30][2] =
1222 {
1223 {0,2}, {0,4}, {0,6}, {0,8}, {0,10}, // From vertex 0
1224 {1,3}, {1,4}, {1,6}, {1,9}, {1,11}, // From vertex 1
1225 {2,5}, {2,7}, {2,8}, {2,10}, // From vertex 2
1226 {3,5}, {3,7}, {3,9}, {3,11}, // From vertex 3
1227 {4,6}, {4,8}, {4,9}, // From vertex 4
1228 {5,7}, {5,8}, {5,9}, // From vertex 5
1229 {6,10}, {6,11}, // From vertex 6
1230 {7,10}, {7,11}, // From vertex 7
1231 {8,9}, {10,11} // Remaining edges
1232 };
1233
1234 group_element g;
1235 g.start_element("icosahedron-" + std::to_string(radius));
1236 for (int i = 0; i < 30; i++)
1237 {
1238 int v1 = edges[i][0];
1239 int v2 = edges[i][1];
1240 point_2t p1 = { projX[v1], projY[v1] };
1241 point_2t p2 = { projX[v2], projY[v2] };
1242 line_element l = make_line(p1, p2, s);
1243 g.add_element(l);
1244 }
1245
1246 if (!tipstr.empty())
1247 g.add_title(tipstr);
1248 g.finish_element();
1249
1250 return g;
1251}
1252
1253
1254/// Make grid palette for display.
1255/// NB @param klrs can be color_qis or array/palette.
1256svg_element
1257display_color_qis(const auto& klrs,
1258 const area<> a, const typography& typobase)
1259{
1260 const auto [ awidth, aheight ] = a;
1261
1262 typography typo = typobase;
1263 typo._M_style = k::w_style;
1264 typo._M_size = 9;
1268
1269 // Draw out colors.
1270 double rwidth = 20;
1271 double rheight = 80;
1272 auto rspace = 4;
1273 auto typsz = 7;
1274
1275 // Turn off RAII.
1276 svg_element obj("color_qis_" + std::to_string(klrs.size()) + "_palette",
1277 a, false);
1278 obj.start();
1279
1280 auto x = rwidth, y = rheight;
1281 auto xoffset = 0;
1282 for (const auto& klr : klrs)
1283 {
1284 // Color block
1285 const style s = { klr, 1.0, klr, 0.0, 2 };
1286 point_2t p = { x + xoffset, y };
1287 rect_element r = make_rect_centered(p, s, { rwidth, rheight});
1288 obj.add_element(r);
1289
1290 // Label.
1291 sized_text_r(obj, typo, typsz, to_string(klr),
1292 x + xoffset, y - rheight / 2 + rspace, 90);
1293
1294 if (xoffset + rwidth + rspace < awidth - rwidth - rspace)
1295 xoffset += rwidth + rspace;
1296 else
1297 {
1298 xoffset = 0;
1299 y += (rheight + rspace + rspace);
1300 }
1301 }
1302
1303 obj.finish_element();
1304 return obj;
1305}
1306
1307} // namespace svg
1308
1309#endif
static constexpr const char * pair_finish_tag
void add_filter(const string id)
void add_data(const data &d)
Either serialize immediately (as below), or create data structure that adds data to data_vec and then...
void add_style(const style &sty)
void add_data(const data &d, const string dasharray="")
void finish_element()
SVG element end boilerplate.
const string offset_percentage(const ssize_type numer, const ssize_type denom)
void add_data(const data &d, string trans="")
void add_transform(const string s)
void start_element()
For groups of elements that have the same name.
static constexpr const char * pair_finish_tag
void add_data(const vrange &points)
void add_title(const string &t)
void add_data(const data &d)
Either serialize immediately (as below), or create data structure that adds data to data_vec and then...
void stop(const string off, const color &klr, const double opacity=1.0)
void add_data(const data &d, const string trans="", const unit utype=svg::unit::point)
Either serialize immediately (as below), or create data structure that adds data to data_vec and then...
static constexpr const char * pair_finish_tag
static constexpr const char * pair_finish_tag
void add_raw(const string &raw)
void add_fill(const string id)
void start(const string &desc="")
void add_element(const element_base &e)
static constexpr string finish_tag_hard
Abstract base class for all SVG Elements.
Circular gradients https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient.
constexpr double pi(3.14159265358979323846)
constexpr char space(' ')
Formatting character constants.
double zero_angle_north_ccw(double angled)
Zero degrees is top, going clockwise (cw).
double scale_proportional_to_area(double radius, double weight)
line_element make_line(const point_2t origin, const point_2t end, style s, const string dasharray="")
Line primitive.
void point_to_ring_halo(svg_element &obj, const point_2t origin, const space_type radius, const double blurspace, const svg::color klr, const svg::color fadeklr=color::none, const double opacity=1)
Draws a ring centered at origin of radius r, with outer and inner radial gradient of blurspace in eac...
circle_element make_circle_marker(const point_2t origin, const style s, const space_type r, const string title, const string xform="", const string imgid="")
Make circle element with title and tooltip information.
rect_element make_rect(const point_2t origin, const style s, const area<> a, const string filterstr="", const string xform="")
Create rect_element at origin.
color
Color enumerated as types.
group_element make_text_honeycomb(const point_2t origin, const double r, const uint hexn, const bool cfillp, const string s, const typography typo, const string xform="")
Center rings of text in a hexagon pattern at this point.
void sized_text_r(element_base &obj, svg::typography typo, const int sz, const string text, const int tx, const int ty, const double deg)
Text at size, with a transformation=rotation.
path_element make_path_center_mark(const point_2t &origin, const style styl, const int len, const int width, const string xform="", const string tipstr="")
Plus or x tilt mark as closed path that can be filled.
void styled_text(element_base &obj, const string text, const point_2t origin, const typography typo, const string xform="")
Text at.
uint text_line_n(svg_element &obj, const point_2t origin, const string text, const svg::typography typo, const int sz, const uint maxlen)
Text of maxlen length, overflow goes on line below.
string make_path_data_from_points(const vrange &lpoints)
Make single path segment.
path_element make_path(const string &pathda, const style &styl, const string id="", const bool selfclosingtagp=true, const string xattr="")
Draw path given serialized path data. Can be used to make pinstripes, ie top and bottom line layers....
const string to_string(const unit e)
group_element make_sunburst(const point_2t origin, const style s, const space_type r=4, const uint nrays=10, const string tipstr="")
Rectangles of various sizes rotated from center point (x,y).
circle_element make_circle(const point_2t origin, const style s, const space_type r, const string xform="")
Make circle element.
rect_element make_rect_centered(const point_2t origin, const style s, const area<> a, const string filterstr="", const string xform="")
Create rect_element centered at origin.
text_element style_text_r(const string text, const point_2t origin, const typography &typo, const double deg, const point_2t rorigin, const k::rrotation rr=k::rrotation::none)
Text element at.
double zero_angle_north_cw(double angled)
Zero degrees is top, going clockwise (cw).
vrange radiate_hexagon_honeycomb(const point_2t origin, const double r, const uint n, const bool centerfilledp)
Compute set of points for a radial fill of hexograms centered at p.
double space_type
Base floating point type.
Definition a60-svg.h:63
polygon_element make_polygon(const vrange &points, const style s)
Make polygon element.
group_element make_hexagon_honeycomb(const point_2t origin, const double r, const uint hexn, const bool cfillp, const style styl, const string xform="")
Center rings of hexagons at this point.
svg_element display_color_qis(const auto &klrs, const area<> a, const typography &typobase)
Make grid palette for display. NB.
rect_element make_rect_marker(const point_2t origin, const style s, const space_type r, const string title, const string filterstr="", const string imgid="")
Create rectangle element with title and tooltip information.
text_element style_text(const string text, const point_2t origin, const typography typo, const string xtransf="")
Text element at.
group_element make_octahedron(const point_2t, const style &, const double radius, const string tipstr="")
Make octahedron shape (8) in 2D simulated 3D @ret group element of polygon_elements.
path_element make_path_ripple(const point_2t origin, const style s, const double length, const double amplitude, const double decay, const int cycles=3, const string tipstr="")
Make waves.
path_element make_path_marker(const point_2t origin, const style s, const double r, const uint pointsn, const string title, const string xattr="")
Make a polygon marker for line graphs.
void styled_text_link(element_base &obj, const string text, const point_2t origin, const typography typo, const string uri)
Text at.
string make_path_arc_closed(const point_2t &origin, const point_2t &start, const point_2t &end, const space_type r, const int arcflag=0, const int sweepflag=0)
Make closed path between two points and the center of a circle of radius r. Points like: get_circumfe...
path_element make_path_blob(const point_2t origin, const style s, const double size, const int numCurves=5+std::rand() % 4, const string tipstr="")
Make blob shape.
std::vector< point_2t > vrange
Definition izzi-points.h:61
path_element make_lauburu(const point_2t origin, const style s, const double size, double swirlFactor=0.5, int pointsPerSwirl=8, const string tipstr="")
Make lauburu.
path_element make_path_polygon(const point_2t origin, const style s, const double r, const uint pointsn, const bool selfclosingtagp=true, const string xattr="")
Center an polygon at this point. radius 4 is pixels to draw out from center point....
point_2t get_circumference_point_d(const double ad, const double r, const point_2t origin)
Angle in degrees.
void sized_text(element_base &obj, svg::typography typo, const int sz, const string text, const int tx, const int ty)
Text at size.
string make_path_arc_circumference(const point_2t &start, const point_2t &end, const space_type r, const int arcflag=0, const int sweepflag=1)
Make path segment between two points on a circumference of radius r. Points like: get_circumference_p...
@ text
metadata, header
polygon_element make_polygon_marker(const vrange &points, const style s, const string title, const string xttr="", const string imgid="")
Make polygon element.
unsigned int uint
Definition a60-svg.h:58
void styled_text_r(element_base &obj, const string text, const point_2t origin, const typography typo, const double deg)
Text at.
group_element make_icosahedron(const point_2t origin, const style &s, const double radius, const string tipstr="")
Make icosahedron shape (20) in 2D simulated 3D.
uint text_line_n_r(svg_element &obj, const point_2t origin, const string text, const svg::typography typo, const uint sz, const uint maxlen, const uint lettingsz=0)
Text of maxlen length rotated, overflow goes on line below.
group_element make_line_rays(const point_2t origin, const style s, const space_type r=4, const uint nrays=10)
Lines radiating from center point (x,y).
polyline_element make_polyline(const vrange &points, const style s, const stroke_style sstyle={ })
Polyline primitive.
double scale_proportional_to_weight(double radius, double weight)
point_2t get_circumference_point_r(const double angler, const double r, const point_2t origin)
Angle in radians.
std::tuple< space_type, space_type > point_2t
Point (x,y) in 2D space, space_type defaults to double.
Definition izzi-points.h:22
vspace get_honeycomb_angles(const point_2t origin, const vrange &hexagons, const bool degreesp=true)
Compute set of angles, given points for a radial fill of hexograms centered at p.
Additional path/line/polyline stroke styles. NB: https://yuanchuan.dev/fun-with-stroke-dasharray.
Datum consolidating style preferences.
color_qi _M_stroke_color
static string translate(int x, int y)
static string rotate(int deg)
@ right
Right part of text block.
@ left
Left-most part of text block.
@ end
End the text block at point.
@ start
Start the text block at point.