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 {
32
33/**
34 Max number of non-overlapping degrees in radial form,
35 as a tuple of (min, max).
36
37 Total degrees in a circle are 360, but then the beginning and the
38 end of the radial display are in the same place, which may
39 confusing. So, shave a bit off both ends for the optics, so that
40 there is a gap between the beginning and end of the generated
41 radial viz. The default is such that the beginning and the end have
42 a discernable gap.
43*/
46{
47 static point_2t rrange = { 10, 350 };
48 return rrange;
49}
50
51/// Convenience for setting radial range.
53set_radial_range(const space_type rmin, const space_type rmax)
54{
55 point_2t& rrange = get_radial_range();
56 point_2t rold = rrange;
57 auto& min = std::get<0>(rrange);
58 auto& max = std::get<1>(rrange);
59 min = rmin;
60 max = rmax;
61 return rold;
62}
63
64
65/// Transform a value on a range to an angle on the radial range.
66inline double
68{
69 // Normalize [0, pmax] to range [mindeg, maxdeg] and put pvalue in it.
70 const auto [ mindeg, maxdeg ] = get_radial_range();
71 double d = scale_value_on_range(pvalue, 0, pmax, mindeg, maxdeg);
72 return d;
73}
74
75
76/// Adjust angle above to CW/CCW orientation.
77inline double
78adjust_angle_rotation(const double dorig, const k::rrotation rot)
79{
80 double d(0.0);
81 if (rot == k::rrotation::cw)
82 d = zero_angle_north_cw(dorig);
83 if (rot == k::rrotation::ccw)
84 d = zero_angle_north_ccw(dorig);
85 return d;
86}
87
88
89/**
90 Angle adjustment such that two points on the circumference path of
91 radius @param r from origin are a minimum of @param dist apart.
92
93 NB: If it cannot be computed directly, returns @param minadjust, a
94 minimum adjustment angle that defaults to 0.25 degrees.
95*/
96inline double
98 const double minadjust = 0.25)
99{
100 // Angle between sequential id's.
101 //
102 // Given r,
103 // point p on circumference r distance from origin with angle d
104 // point pprime on circumference r from origin with angle dprime
105 // distance is the cartesian distance between p and pprime
106 //
107 // then
108 // distance = 2 * theta, where theta from sin(theta) = (rspace / 2) / r.
109 double angle(minadjust);
110 const double arg((dist / 2) / r);
111 if (arg < 1.0)
112 {
113 double angler = std::asin(arg);
114 angle = angler * (180.0 / k::pi);
115 }
116
117 // Verify and iterate, if it could not be computed above.
118 const point_2t origin(0, 0);
119 const point_2t p1 = get_circumference_point_d(0, r, origin);
120 point_2t p2 = get_circumference_point_d(0 + angle, r, origin);
121 while (distance_cartesian(p1, p2) < dist)
122 {
123 angle += minadjust;
124 p2 = get_circumference_point_d(0 + angle, r, origin);
125 }
126
127 return angle;
128}
129
130
131/// The number of significant digits in @param maxval.
132uint
134{
135 uint sigdigits(1);
136 while (maxval > 1)
137 {
138 maxval /= 10;
139 ++sigdigits;
140 }
141 return sigdigits;
142}
143
144
145/// Get the label space.
146/// Value -> Name, as a string where value has labelspaces of fill
147/// NB: Should be the number of significant digits in pmax plus separators.
148/// So, 10 == 2, 100 == 3, 10k == 5 + 1
151{
152 static ssize_type lspaces;
153 return lspaces;
154}
155
156
157/// Set the number of label spaces.
158void
161
162
163/// Make radial labels.
164string
165make_label_for_value(string pname, ssize_type pvalue,
166 const uint valuewidth = 9)
167{
168 // Consolidate label text to be "VALUE -> NAME"
169 std::ostringstream oss;
170 oss.imbue(std::locale(""));
171 oss << std::setfill(' ') << std::setw(valuewidth)
172 << std::left << pvalue;
173
174 string label = oss.str() + " -> " + pname;
175 return label;
176}
177
178
179/// Sort vectors of strings to largest length string first. (Or use set<>).
180void
182{
183 auto lsizeless = [](const string& s1, const string& s2)
184 { return s1.size() < s2.size(); };
185 sort(ids.begin(), ids.end(), lsizeless);
186}
187
188
189/// Text with typography, arranged cw around points (x,y) on a circle.
190void
191radial_text_cw(svg_element& obj, string text, const typography& typo,
192 const point_2t origin, const double deg)
193{
194 typography typot(typo);
198 styled_text_r(obj, text, origin, typot, 360 - deg);
199}
200
201
202/// Text with typography, arranged ccw around points (x,y) on a circle.
203void
204radial_text_ccw(svg_element& obj, string text, const typography& typo,
205 const point_2t origin, const double deg)
206{
207 typography typot(typo);
211 styled_text_r(obj, text, origin, typot, 360 - deg + 180);
212}
213
214
215/// Text with typography, arranged cw around points (x,y) on a circle.
216void
217radial_text_cw(svg_element& obj, string text, const typography& typo,
218 const point_2t origin, const double deg, const point_2t rorigin)
219{
220 typography typot(typo);
224 styled_text_r(obj, text, origin, typot, 360 - deg, rorigin);
225}
226
227
228/// Text with typography, arranged ccw around points (x,y) on a circle.
229void
230radial_text_ccw(svg_element& obj, string text, const typography& typo,
231 const point_2t origin, const double deg, const point_2t rorigin)
232{
233 typography typot(typo);
237 styled_text_r(obj, text, origin, typot, 360 - deg + 180, rorigin);
238}
239
240
241/**
242 Easier to read radial text that switches orientation at 180
243 degrees, instead of going upside-down as it follows the circle
244 around the circumference, aka mirrored or symmetric radial text.
245
246 Text with typography, arranged around an origin of a circle with radius r.
247 Text is align left CW (1,180)
248 Text is aligh right CCW (181, 359)
249
250 NB: Assume value is un-adjusted, aka from get_angle
251
252 roriginp is a switch to use a render origin, not the text position,
253 as the rotation origin.
254*/
255void
256radial_text_r(svg_element& obj, string text, const typography& typo,
257 const int r, const point_2t origin, const double deg,
258 const bool roriginp = false)
259{
260 if (deg <= 180)
261 {
262 auto dcw = zero_angle_north_cw(deg);
263 const point_2t p = get_circumference_point_d(dcw, r, origin);
264 if (!roriginp)
265 radial_text_cw(obj, text, typo, p, dcw);
266 else
267 radial_text_cw(obj, text, typo, p, dcw, origin);
268 }
269 else
270 {
271 // Mapping some values...
272
273 // d 210 cw -> d 150 ccw
274 // d 240 cw -> d 120 ccw
275 // d 270 cw -> d 90 ccw
276 // d 330 cw -> d 30 ccw
277
278 // 210 - 180 == 30 -> 180 - 30 == 150
279 // 240 - 180 == 60 -> 180 - 60 == 120
280 // 330 - 180 == 150 -> 180 - 150 == 30
281
282 auto dp = deg - 180;
283 auto dccw = zero_angle_north_ccw(180 - dp);
284 const point_2t p = get_circumference_point_d(dccw, r, origin);
285 if (!roriginp)
286 radial_text_ccw(obj, text, typo, p, dccw);
287 else
288 radial_text_ccw(obj, text, typo, p, dccw, origin);
289 }
290}
291
292
293// Radiate clockwise from 0 to 35x degrees about origin, placing each
294// id at a point on the circumference. Duplicate ids splay, stack,
295// or append/concatencate at, after, or around that point.
296/// Spread ids on either side of an origin point, along circumference
297/// path.
298void
300 const strings& ids, const double angled,
301 const point_2t origin, double r, double rspace,
302 const bool satellitep = false)
303{
304 // If rspace is set for labels, and isn't adjusted for this radius,
305 // make sure it is set low.
306 if (rspace > r * 2)
307 rspace = r * 2;
308
309 const double angleprimed = adjust_angle_at_orbit_for_distance(r, rspace);
310 const double maxdeg = angleprimed * (ids.size() - 1);
311 double angled2 = angled - (maxdeg / 2);
312
313 // NB: value label is at r + rspace, so rprime has to be longer.
314 double rlabel = r + rspace;
315 double rprime = rlabel + (rspace * 2);
316 for (const string& s: ids)
317 {
318 if (satellitep)
319 {
320 // Make a ring on point on the kusama circle for the id.
321 auto [ x, y ] = get_circumference_point_d(angled2, r, origin);
322 auto rring = rspace / 2;
323 const id_rstate ridst = get_id_rstate(s);
324 style rstyl = ridst.styl;
325 point_to_circle(obj, { x, y }, rstyl, rring);
326 }
327
328 radial_text_r(obj, s, typo, rprime, origin, angled2);
329 angled2 += angleprimed;
330 }
331}
332
333
334/// Spread ids past the origin point, along circumference path.
335void
337 const strings& ids, const double angledo,
338 const point_2t origin, double r, double rspace)
339{
340 double angledelta = typo._M_size; // XXX
341 double angled(angledo);
342 for (const string& s: ids)
343 {
344 radial_text_r(obj, s, typo, r + rspace, origin, angled);
345 angled -= angledelta;
346 }
347}
348
349
350/// Spread ids after in stepping pattern outward.
351void
353 const strings& ids, const double angled,
354 const point_2t origin, double r, double rspace)
355{
356 if (ids.size() > 1)
357 {
358 auto imiddle = ids.begin() + (ids.size() / 2);
359 strings ids1(ids.begin(), imiddle);
360 strings ids2(imiddle, ids.end());
361 splay_ids_after(obj, typo, ids1, angled, origin, r, rspace);
362 splay_ids_after(obj, typo, ids2, angled, origin, r + 125, rspace);
363 }
364 else
365 splay_ids_after(obj, typo, ids, angled, origin, r, rspace);
366}
367
368
369/// Rotate and stack ids at origin point, extending radius for each
370/// from point of origin.
371void
373 const strings& ids, const double angled,
374 const point_2t origin, double r, const double rdelta = 10)
375{
376 // Rotate 90 CW around origin, and spread out .
377 typography typo(typoo);
378 //typo._M_anchor = svg::typography::anchor::middle;
379 //typo._M_align = svg::typography::align::center;
380
381 for (const string& s: ids)
382 {
383 // XXX used to be angled + 90
384 radial_text_r(obj, s, typo, r, origin, angled);
385 r += rdelta;
386 }
387}
388
389
390/// Concatenate ids onto one line.
391void
393 const strings& ids, const double angled,
394 const point_2t origin, double r)
395{
396 // Concatenate ids to one line.
397 string scat;
398 for (const string& s: ids)
399 {
400 if (!scat.empty())
401 scat += ", ";
402 scat += s;
403 }
404 radial_text_r(obj, scat, typo, r, origin, angled);
405}
406
407} // namespace svg
408
409#endif
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.
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:67
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
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.
@ text
metadata, header
unsigned int uint
Definition a60-svg.h:57
void styled_text_r(element_base &obj, const string text, const point_2t origin, const typography typo, const double deg)
Text at.
space_type distance_cartesian(const point_2t &p1, const point_2t &p2)
Find cartesian distance between two 2D points.
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, space_type defaults to double.
Definition izzi-points.h:22
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.