izzi
SVG SUBSET C++ API
Loading...
Searching...
No Matches
a60-svg-radial-base.h
Go to the documentation of this file.
1// svg circle and radial forms -*- mode: C++ -*-
2
3// alpha60
4// bittorrent x scrape x data + analytics
5
6// Copyright (c) 2020-2021, Benjamin De Kosnik <b.dekosnik@gmail.com>
7
8// This file is part of the alpha60 library. This library is free
9// software; you can redistribute it and/or modify it under the terms
10// of the GNU General Public License as published by the Free Software
11// Foundation; either version 3, or (at your option) any later
12// version.
13
14// This library is distributed in the hope that it will be useful, but
15// WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17// General Public License for more details.
18
19#ifndef MiL_SVG_RADIAL_BASE_H
20#define MiL_SVG_RADIAL_BASE_H 1
21
22#include <set>
23#include <chrono>
24#include <iostream>
25#include <iomanip>
26#include <algorithm>
27
28#include "a60-svg.h"
29
30
31namespace svg::constants {
32
33/// Radial rotation direction.
34enum class rrotation
35 {
36 cw, ///< Clockwise
37 ccw ///< Counter-clockwise
38 };
39
40} // namespace svg::constants
41
42namespace svg {
43
44/**
45 Max number of non-overlapping degrees in radial form,
46 as a tuple of (min, max).
47
48 Total degrees in a circle are 360, but then the beginning and the
49 end of the radial display are in the same place, which may
50 confusing. So, shave a bit off both ends for the optics, so that
51 there is a gap between the beginning and end of the generated
52 radial viz. The default is such that the beginning and the end have
53 a discernable gap.
54*/
57{
58 static point_2t rrange = { 10, 350 };
59 return rrange;
60}
61
62/// Convenience for setting radial range.
64set_radial_range(const space_type rmin, const space_type rmax)
65{
66 point_2t& rrange = get_radial_range();
67 point_2t rold = rrange;
68 auto& min = std::get<0>(rrange);
69 auto& max = std::get<1>(rrange);
70 min = rmin;
71 max = rmax;
72 return rold;
73}
74
75
76/// Transform a value on a range to an angle on the radial range.
77inline double
79{
80 // Normalize [0, pmax] to range [mindeg, maxdeg] and put pvalue in it.
81 const auto [ mindeg, maxdeg ] = get_radial_range();
82 double d = scale_value_on_range(pvalue, 0, pmax, mindeg, maxdeg);
83 return d;
84}
85
86
87/// Adjust angle above to CW/CCW orientation.
88inline double
89adjust_angle_rotation(const double dorig, const k::rrotation rot)
90{
91 double d(0.0);
92 if (rot == k::rrotation::cw)
93 d = zero_angle_north_cw(dorig);
94 if (rot == k::rrotation::ccw)
95 d = zero_angle_north_ccw(dorig);
96 return d;
97}
98
99
100/**
101 Angle adjustment such that two points on the circumference path of
102 radius @param r from origin are a minimum of @param dist apart.
103
104 NB: If it cannot be computed directly, returns @param minadjust, a
105 minimum adjustment angle that defaults to 0.25 degrees.
106*/
107inline double
109 const double minadjust = 0.25)
110{
111 // Angle between sequential id's.
112 //
113 // Given r,
114 // point p on circumference r distance from origin with angle d
115 // point pprime on circumference r from origin with angle dprime
116 // distance is the cartesian distance between p and pprime
117 //
118 // then
119 // distance = 2 * theta, where theta from sin(theta) = (rspace / 2) / r.
120 double angle(minadjust);
121 const double arg((dist / 2) / r);
122 if (arg < 1.0)
123 {
124 double angler = std::asin(arg);
125 angle = angler * (180.0 / k::pi);
126 }
127
128 // Verify and iterate, if it could not be computed above.
129 const point_2t origin(0, 0);
130 const point_2t p1 = get_circumference_point_d(0, r, origin);
131 point_2t p2 = get_circumference_point_d(0 + angle, r, origin);
132 while (distance_cartesian(p1, p2) < dist)
133 {
134 angle += minadjust;
135 p2 = get_circumference_point_d(0 + angle, r, origin);
136 }
137
138 return angle;
139}
140
141
142/// The number of significant digits in @param maxval.
143uint
145{
146 uint sigdigits(1);
147 while (maxval > 1)
148 {
149 maxval /= 10;
150 ++sigdigits;
151 }
152 return sigdigits;
153}
154
155
156/// Get the label space.
157/// Value -> Name, as a string where value has labelspaces of fill
158/// NB: Should be the number of significant digits in pmax plus separators.
159/// So, 10 == 2, 100 == 3, 10k == 5 + 1
162{
163 static ssize_type lspaces;
164 return lspaces;
165}
166
167
168/// Set the number of label spaces.
169void
172
173
174/// Make radial labels.
175string
176make_label_for_value(string pname, ssize_type pvalue,
177 const uint valuewidth = 9)
178{
179 // Consolidate label text to be "VALUE -> NAME"
180 std::ostringstream oss;
181 oss.imbue(std::locale(""));
182 oss << std::setfill(' ') << std::setw(valuewidth)
183 << std::left << pvalue;
184
185 string label = oss.str() + " -> " + pname;
186 return label;
187}
188
189
190/// Sort vectors of strings to largest length string first. (Or use set<>).
191void
193{
194 auto lsizeless = [](const string& s1, const string& s2)
195 { return s1.size() < s2.size(); };
196 sort(ids.begin(), ids.end(), lsizeless);
197}
198
199
200/// Text with typography, arranged cw around points (x,y) on a circle.
201void
202radial_text_cw(svg_element& obj, string text, const typography& typo,
203 const point_2t origin, const double deg)
204{
205 typography typot(typo);
209 styled_text_r(obj, text, origin, typot, 360 - deg);
210}
211
212
213/// Text with typography, arranged ccw around points (x,y) on a circle.
214void
215radial_text_ccw(svg_element& obj, string text, const typography& typo,
216 const point_2t origin, const double deg)
217{
218 typography typot(typo);
222 styled_text_r(obj, text, origin, typot, 360 - deg + 180);
223}
224
225
226/// Text with typography, arranged cw around points (x,y) on a circle.
227void
228radial_text_cw(svg_element& obj, string text, const typography& typo,
229 const point_2t origin, const double deg, const point_2t rorigin)
230{
231 typography typot(typo);
235 styled_text_r(obj, text, origin, typot, 360 - deg, rorigin);
236}
237
238
239/// Text with typography, arranged ccw around points (x,y) on a circle.
240void
241radial_text_ccw(svg_element& obj, string text, const typography& typo,
242 const point_2t origin, const double deg, const point_2t rorigin)
243{
244 typography typot(typo);
248 styled_text_r(obj, text, origin, typot, 360 - deg + 180, rorigin);
249}
250
251
252/**
253 Easier to read radial text that switches orientation at 180
254 degrees, instead of going upside-down as it follows the circle
255 around the circumference, aka mirrored or symmetric radial text.
256
257 Text with typography, arranged around an origin of a circle with radius r.
258 Text is align left CW (1,180)
259 Text is aligh right CCW (181, 359)
260
261 NB: Assume value is un-adjusted, aka from get_angle
262
263 roriginp is a switch to use a render origin, not the text position,
264 as the rotation origin.
265*/
266void
267radial_text_r(svg_element& obj, string text, const typography& typo,
268 const int r, const point_2t origin, const double deg,
269 const bool roriginp = false)
270{
271 if (deg <= 180)
272 {
273 auto dcw = zero_angle_north_cw(deg);
274 const point_2t p = get_circumference_point_d(dcw, r, origin);
275 if (!roriginp)
276 radial_text_cw(obj, text, typo, p, dcw);
277 else
278 radial_text_cw(obj, text, typo, p, dcw, origin);
279 }
280 else
281 {
282 // Mapping some values...
283
284 // d 210 cw -> d 150 ccw
285 // d 240 cw -> d 120 ccw
286 // d 270 cw -> d 90 ccw
287 // d 330 cw -> d 30 ccw
288
289 // 210 - 180 == 30 -> 180 - 30 == 150
290 // 240 - 180 == 60 -> 180 - 60 == 120
291 // 330 - 180 == 150 -> 180 - 150 == 30
292
293 auto dp = deg - 180;
294 auto dccw = zero_angle_north_ccw(180 - dp);
295 const point_2t p = get_circumference_point_d(dccw, r, origin);
296 if (!roriginp)
297 radial_text_ccw(obj, text, typo, p, dccw);
298 else
299 radial_text_ccw(obj, text, typo, p, dccw, origin);
300 }
301}
302
303
304// Radiate clockwise from 0 to 35x degrees about origin, placing each
305// id at a point on the circumference. Duplicate ids splay, stack,
306// or append/concatencate at, after, or around that point.
307/// Spread ids on either side of an origin point, along circumference
308/// path.
309void
311 const strings& ids, const double angled,
312 const point_2t origin, double r, double rspace,
313 const bool satellitep = false)
314{
315 // If rspace is set for labels, and isn't adjusted for this radius,
316 // make sure it is set low.
317 if (rspace > r * 2)
318 rspace = r * 2;
319
320 const double angleprimed = adjust_angle_at_orbit_for_distance(r, rspace);
321 const double maxdeg = angleprimed * (ids.size() - 1);
322 double angled2 = angled - (maxdeg / 2);
323
324 // NB: value label is at r + rspace, so rprime has to be longer.
325 double rlabel = r + rspace;
326 double rprime = rlabel + (rspace * 2);
327 for (const string& s: ids)
328 {
329 if (satellitep)
330 {
331 // Make a ring on point on the kusama circle for the id.
332 auto [ x, y ] = get_circumference_point_d(angled2, r, origin);
333 auto rring = rspace / 2;
334 const id_rstate ridst = get_id_rstate(s);
335 style rstyl = ridst.styl;
336 point_to_circle(obj, { x, y }, rstyl, rring);
337 }
338
339 radial_text_r(obj, s, typo, rprime, origin, angled2);
340 angled2 += angleprimed;
341 }
342}
343
344
345/// Spread ids past the origin point, along circumference path.
346void
348 const strings& ids, const double angledo,
349 const point_2t origin, double r, double rspace)
350{
351 double angledelta = typo._M_size; // XXX
352 double angled(angledo);
353 for (const string& s: ids)
354 {
355 radial_text_r(obj, s, typo, r + rspace, origin, angled);
356 angled -= angledelta;
357 }
358}
359
360
361/// Spread ids after in stepping pattern outward.
362void
364 const strings& ids, const double angled,
365 const point_2t origin, double r, double rspace)
366{
367 if (ids.size() > 1)
368 {
369 auto imiddle = ids.begin() + (ids.size() / 2);
370 strings ids1(ids.begin(), imiddle);
371 strings ids2(imiddle, ids.end());
372 splay_ids_after(obj, typo, ids1, angled, origin, r, rspace);
373 splay_ids_after(obj, typo, ids2, angled, origin, r + 125, rspace);
374 }
375 else
376 splay_ids_after(obj, typo, ids, angled, origin, r, rspace);
377}
378
379
380/// Rotate and stack ids at origin point, extending radius for each
381/// from point of origin.
382void
384 const strings& ids, const double angled,
385 const point_2t origin, double r, const double rdelta = 10)
386{
387 // Rotate 90 CW around origin, and spread out .
388 typography typo(typoo);
389 //typo._M_anchor = svg::typography::anchor::middle;
390 //typo._M_align = svg::typography::align::center;
391
392 for (const string& s: ids)
393 {
394 // XXX used to be angled + 90
395 radial_text_r(obj, s, typo, r, origin, angled);
396 r += rdelta;
397 }
398}
399
400
401/// Concatenate ids onto one line.
402void
404 const strings& ids, const double angled,
405 const point_2t origin, double r)
406{
407 // Concatenate ids to one line.
408 string scat;
409 for (const string& s: ids)
410 {
411 if (!scat.empty())
412 scat += ", ";
413 scat += s;
414 }
415 radial_text_r(obj, scat, typo, r, origin, angled);
416}
417
418} // namespace svg
419
420#endif
rrotation
Radial rotation direction.
double zero_angle_north_ccw(double angled)
Zero degrees is top, going clockwise (cw).
uint significant_digits_in(ssize_type maxval)
The number of significant digits in.
void append_ids_at(svg_element &obj, const typography &typo, const strings &ids, const double angled, const point_2t origin, double r)
Concatenate ids onto one line.
void radial_text_r(svg_element &obj, string text, const typography &typo, const int r, const point_2t origin, const double deg, const bool roriginp=false)
ssize_type & get_label_spaces()
Get the label space. Value -> Name, as a string where value has labelspaces of fill NB: Should be the...
void point_to_circle(svg_element &obj, const point_2t origin, style s, const space_type r=4, const string xform="")
Draws a circle around a point (x,y), of style (s), of radius (r).
double get_angle(ssize_type pvalue, ssize_type pmax)
Transform a value on a range to an angle on the radial range.
point_2t set_radial_range(const space_type rmin, const space_type rmax)
Convenience for setting radial range.
void radial_text_cw(svg_element &obj, string text, const typography &typo, const point_2t origin, const double deg)
Text with typography, arranged cw around points (x,y) on a circle.
void styled_text_r(svg_element &obj, const string text, const point_2t origin, const typography typo, const double deg)
Text at.
double scale_value_on_range(const ssize_type value, const ssize_type min, const ssize_type max, const ssize_type nfloor, const ssize_type nceil)
Scale value from min to max on range (nfloor, nceil).
Definition a60-svg.h:216
int ssize_type
Definition a60-svg.h:59
point_2t & get_radial_range()
double adjust_angle_at_orbit_for_distance(double r, double dist, const double minadjust=0.25)
string make_label_for_value(string pname, ssize_type pvalue, const uint valuewidth=9)
Make radial labels.
double zero_angle_north_cw(double angled)
Zero degrees is top, going clockwise (cw).
double space_type
Base floating point type.
Definition a60-svg.h:62
@ text
metadata, header
void splay_ids_around(svg_element &obj, const typography &typo, const strings &ids, const double angled, const point_2t origin, double r, double rspace, const bool satellitep=false)
Spread ids on either side of an origin point, along circumference path.
double adjust_angle_rotation(const double dorig, const k::rrotation rot)
Adjust angle above to CW/CCW orientation.
void splay_ids_after(svg_element &obj, const typography &typo, const strings &ids, const double angledo, const point_2t origin, double r, double rspace)
Spread ids past the origin point, along circumference path.
void splay_ids_stagger(svg_element &obj, const typography &typo, const strings &ids, const double angled, const point_2t origin, double r, double rspace)
Spread ids after in stepping pattern outward.
point_2t get_circumference_point_d(const double ad, const double r, const point_2t origin)
Angle in degrees.
unsigned int uint
Definition a60-svg.h:57
space_type distance_cartesian(const point_2t &p1, const point_2t &p2)
Find cartesian distance between two 2D points.
Definition a60-svg.h:230
void set_label_spaces(ssize_type spaces)
Set the number of label spaces.
void stack_ids_at(svg_element &obj, const typography &typoo, const strings &ids, const double angled, const point_2t origin, double r, const double rdelta=10)
Rotate and stack ids at origin point, extending radius for each from point of origin.
void radial_text_ccw(svg_element &obj, string text, const typography &typo, const point_2t origin, const double deg)
Text with typography, arranged ccw around points (x,y) on a circle.
std::tuple< space_type, space_type > point_2t
Point (x,y) in 2D space.
Definition a60-svg.h:65
std::vector< string > strings
Definition a60-svg.h:37
const id_rstate get_id_rstate(const string id)
Given identifier/name/id, get corresponding id_rstate from cache.
void sort_strings_by_size(strings &ids)
Sort vectors of strings to largest length string first. (Or use set<>).
Named render state. Datum to take id string and tie it to visual representation.
Datum consolidating style preferences.
@ 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.