izzi
SVG SUBSET C++ API
Loading...
Searching...
No Matches
a60-svg-radial-kusama.h
Go to the documentation of this file.
1// svg radial, sunburst / RAIL forms -*- mode: C++ -*-
2
3// alpha60
4// bittorrent x scrape x data + analytics
5
6// Copyright (c) 2018-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_KUSAMA_H
20#define MiL_SVG_RADIAL_KUSAMA_H 1
21
22
23namespace svg {
24
25/// The smallest (sattelite) radius size allowed in a kusama orbit.
26int&
28{
29 static int rsz(1);
30 return rsz;
31}
32
33/// By observation, type size 12pt = 5, 6pt = 2
34int
35set_min_ring_size(const int sz)
36{
37 int& rsz = get_min_ring_size();
38 int rszold(rsz);
39 rsz = sz;
40 return rszold;
41}
42
43
44/// The minimum distance between satellites in high orbit.
45double&
47{
48 static double k(4);
49 return k;
50}
51
52/// By observation, 7pt = 5 minimum
53/// NB: Make sure distance is at least text height away for lowest values.
54double
56{
57 double& k = get_min_satellite_distance();
58 double kold(k);
59 k = kuse;
60 return kold;
61}
62
63
64/**
65 Draw line and value on ray from origin as part of kusama.
66 skip (origin to rstart) + rspace + line of length lineline + rspace + value
67
68 Returns the length along the arc of the generated glyph minus text part.
69*/
70int
72 const double angled, const ssize_type v,
73 const int rspace, const int rstart, const int linelen,
74 const typography& typo,
75 const style styl = {color::black, 1, color::black, .25, 1})
76{
77 const double angleda = adjust_angle_rotation(angled, k::rrotation::cw);
78 const int rbase(rstart + rspace);
79 point_2t pl1 = get_circumference_point_d(angleda, rbase, origin);
80 point_2t pl2 = get_circumference_point_d(angleda, rbase + linelen, origin);
81 points_to_line(obj, styl, pl1, pl2);
82
83 // Length used of glyphs along radiated ray from origin, if any.
84 int glyphr = rbase + linelen + rspace;
85 typography typob(typo);
86 typob._M_w = typography::weight::bold;
87 radial_text_r(obj, std::to_string(v), typob, glyphr, origin, angled);
88 return glyphr - rstart;
89}
90
91
92/**
93 Draw glyph on ray from origin as part of kusama.
94*/
95int
96radiate_glyph(svg_element& obj, const point_2t origin, const double angled,
97 const id_rstate idst,
98 const int kr, const int rspace, const int rstart)
99{
100 // Kusama circle radius, enforce miniumum size.
101 const int kra = std::max(kr, get_min_ring_size());
102
103 // Assumed to scale per value/value_max ratio.
104 const double angleda = adjust_angle_rotation(angled, k::rrotation::cw);
105
106 const string glyphtext = idst.name;
107 const double glyphscale = idst.multiple;
108 const double glyphrotate = idst.rotate;
109
110 int glyphr(0);
112 {
113 // SVG to be inserted is
114 // - square canvas width/height of 100 pixels
115 // - glyph centered in this canvas
116 // - glyph circle diameter = glyphscale value in pixels
117 // Implies:
118 // kr * 2 is desired width of circle in glyph.
119 const double scale(kra * 2 / glyphscale);
120 const double scaledsize = scale * 100;
121 const double scaledglyph = scale * glyphscale;
122 const double svgr = rstart + rspace + (scaledglyph / 2);
123 point_2t p = get_circumference_point_d(angleda, svgr, origin);
124
125 string isvg = file_to_svg_insert(glyphtext);
126 insert_svg_at(obj, isvg, p, 100, scaledsize, angleda + glyphrotate,
127 idst.styl);
128 glyphr += scaledsize;
129 }
130
132 {
133 const int vr = rstart + rspace + kra;
134 point_2t p = get_circumference_point_d(angleda, vr, origin);
135 point_to_circle(obj, p, idst.styl, kra);
136 glyphr += (2 * kra);
137 }
138
139 glyphr += get_label_spaces();
140 return glyphr;
141}
142
143
144/**
145 Draw glyph and id on ray from origin as part of kusama.
146 skip (origin to rstart) + rspace + glyph of radius + rspace + id
147
148 @param origin is center of the primary/base circle for kusama renderings.
149 @param kr is of the kusama satellite circle on the ray.
150 @param rspace is the distance between text/vector/glyph elements
151 @param rstart is the length from origin along the ray to begin rendering
152
153
154 Returns the length along the arc of the generated glyph.
155*/
156int
158 const double angled, const double kr, const int rspace,
159 const int rstart, const string id, const typography& typo)
160{
161 const id_rstate idst = get_id_rstate(id);
162
163 // Switch based on id_rstate settings.
164 // Length used of glyphs along radiated ray from origin, if any.
165 int glyphr(0);
167 glyphr += radiate_glyph(obj, origin, angled, idst, kr, rspace, rstart);
168
169 // Id name.
170 if (idst.is_visible(svg::select::text) && !id.empty())
171 {
172 const int idr = rstart + glyphr + rspace;
173
174#if 1
175 // XXX playing.
176 // Override style if id string contains matching string.
177 typography typoz(typo);
178 const string playingabv(" -> ");
179 if (id.find(playingabv) != string::npos)
180 {
181 const id_rstate idstp = get_id_rstate(playingabv);
182 typoz._M_style = idstp.styl;
183 }
184 radial_text_r(obj, id, typoz, idr, origin, angled);
185#endif
186
187 // NB: This is only an estimate of the text block size.
188 // Should be getComputedTextLength
189 glyphr += id.size() * char_width_to_px(typo._M_size);
190 }
191
192 return glyphr;
193}
194
195
196/// Convenience function for above.
197int
199 const ssize_type v, const ssize_type value_max,
200 const int radius, const int rspace, const int rstart,
201 const string id, const typography& typo)
202{
203 const double angled = get_angle(v, value_max);
204 const double kr((double(v) / value_max) * radius);
205 return radiate_glyph_and_id(obj, origin, angled, kr, rspace, rstart, id, typo);
206}
207
208
209/**
210 Draw these ids around a kusama low orbit circle on arcs from the origin circle.
211
212 Simplest version, make satellite circle on circumference and splay
213 or append id's around it.
214
215 @param radius Length of line on a ray from origin to value.
216
217 @param satdistance Multiple used to compute distance between two
218 satellite values. Useful defaults: 3.3 for 12pt, 2 for 7pt.
219*/
220int
221kusama_ids_orbit_high(svg_element& obj, const point_2t origin, const strings& ids,
222 const ssize_type v, const ssize_type value_max,
223 const int radius, const int rspace, const int rstart,
224 const int linelen,
225 const typography& typo,
226 const bool wbyvaluep, const bool satellitep = false)
227{
228 // Center of glyph, a point on origin circle circumference.
229 const double angled = get_angle(v, value_max);
230
231 int glyphr = radiate_line_and_value(obj, origin, angled, v,
232 rspace, rstart, linelen, typo);
233
234 // Add number of characters of value as string * size of each character.
235 glyphr += significant_digits_in(value_max) * char_width_to_px(typo._M_size);
236
237 // If satellitep, draw "leading" circle in the default style in low orbit to
238 // hang the rest of the glyphs off of, in high orbit...
239 if (satellitep)
240 {
241 glyphr += radiate_glyph_and_id(obj, origin, v, value_max, radius, rspace,
242 rstart + glyphr, "", typo);
243 glyphr += rspace;
244 }
245
246 // Radius of satellite orbit.
247 const double ar = rstart + glyphr;
248
249 // Find the satellite radius(kr).
250 // Variation based on splay_ids_around center point,
251 // where center is point on arc from origin...
252 double kr(0);
253 if (wbyvaluep)
254 kr = ((double(v) / value_max) * radius);
255 else
256 kr = get_min_ring_size();
257
258 // Find distance betwen two satellites spheres on high-orbit kusama.
259 // Want equivalent distances between satellites of different radius (values),
260 // so this cannot be strictly a multiple of various radius from v0 to vmax.
261 // NB: For lowest values, make sure distance is at least text height away.
262 const double k = get_min_satellite_distance();
263 const double distnce = k + (kr * 2);
264 const double sr = ar + rspace + kr;
265 const double anglea = adjust_angle_at_orbit_for_distance(sr, distnce);
266
267 // Given distance and anglea, find start and end angles.
268 const double maxdeg = anglea * (ids.size() - 1);
269 double angled2 = angled - (maxdeg / 2);
270 int maxglyphr2(0);
271 for (const string& id : ids)
272 {
273 int glyphr2 = radiate_glyph_and_id(obj, origin, angled2,
274 kr, rspace, ar, id, typo);
275
276 constexpr bool debugp = false;
277 if (debugp)
278 {
279 const int r4 = 200;
280 const style rstyl = { color::red, 1.0, color::red, 1.0, 0.5 };
281 const double delta = adjust_angle_rotation(angled2, k::rrotation::cw);
282 const point_2t pdbg = get_circumference_point_d(delta, r4, origin);
283 points_to_line(obj, rstyl, origin, pdbg);
284 }
285
286 // Advance and bookkeeping for the next round.
287 angled2 += anglea;
288 maxglyphr2 = std::max(maxglyphr2, glyphr2);
289 }
290 glyphr += maxglyphr2;
291
292 return glyphr;
293}
294
295
296/**
297 Draw these ids as a glyph on the circumference of origin circle.
298
299 Simplest version.
300*/
301void
302kusama_ids_orbit_low(svg_element& obj, const point_2t origin, const strings& ids,
303 const ssize_type v, const ssize_type value_max,
304 const int radius, const int rspace, const int rstart,
305 const int linelen,
306 const typography& typo, const bool wbyvaluep)
307{
308 // Center of glyph, a point on origin circle circumference.
309 const double angled = get_angle(v, value_max);
310
311 int glyphr = 0;
312 if (ids.size() == 1)
313 {
314 // Low orbit.
315 glyphr = radiate_line_and_value(obj, origin, angled, v,
316 rspace, rstart, linelen, typo);
317
318 // Add number of characters of value as string * size of each character.
319 glyphr += significant_digits_in(value_max) * char_width_to_px(typo._M_size);
320
321 glyphr += radiate_glyph_and_id(obj, origin, v, value_max, radius, rspace,
322 rstart + glyphr, ids.front(), typo);
323
324 constexpr bool debugp = false;
325 if (debugp)
326 {
327 const int r4 = 200;
328 const style rstyl = { color::blue, 1.0, color::blue, 1.0, 0.5 };
329 const double delta = adjust_angle_rotation(angled, k::rrotation::cw);
330 const point_2t pdbg = get_circumference_point_d(delta, r4, origin);
331 points_to_line(obj, rstyl, origin, pdbg);
332 }
333 }
334 else
335 {
336 // High orbit.
337 // Do what's left (non-specialized ids) as per usual.
338 kusama_ids_orbit_high(obj, origin, ids, v, value_max,
339 radius, rspace, rstart, linelen, typo, wbyvaluep,
340 false);
341 }
342}
343
344
345/// Layer one value's glyphs and ids.
346void
347kusama_ids_at_uvalue(svg_element& obj, const point_2t origin, const strings& ids,
348 const ssize_type v, const ssize_type value_max,
349 const int radius, const int rspace, const int rstart,
350 const int linelen,
351 const typography& typo, const bool weighbyvaluep)
352{
353 // Draw this id's kusama circle on the circumference of origin
354 // circle.
355 kusama_ids_orbit_low(obj, origin, ids, v, value_max, radius, rspace, rstart,
356 linelen, typo, weighbyvaluep);
357
358 // Iff overlay rays to check text and glyph alignment.
359 constexpr bool overlayrayp = false;
360 if (overlayrayp)
361 {
362 const style rstyl = { color::red, 1.0, color::red, 1.0, 2 };
363 const double angled = get_angle(v, value_max);
364 radiate_line_and_value(obj, origin, angled, v, rspace, rstart,
365 radius * 4, typo, rstyl);
366 }
367
368 // Put in max value.
369 constexpr bool showmaxvp = true;
370 if (showmaxvp)
371 {
372 const double anglemax = get_angle(value_max, value_max);
373 radiate_line_and_value(obj, origin, anglemax, value_max, rspace, rstart,
374 linelen, typo);
375 }
376}
377
378
379/**
380 Split values into
381
382 Go through values, if there are two values that are adjacent,
383 - move the first value out to a larger radius, rprime.
384 - render the first value at rprime, remove it from the list.
385 - save the second value and continue
386
387 A point cluster is a circle whos radius is proportionate to the
388 number of duplicate ids at that point. Duplicate ids splay, stack,
389 or append/concatencate at, after, or around that point cluster.
390
391 NB: invariant that @vuvalues > 1
392
393 Threshold is the range such that a value is considered adjacent for
394 collisions. If v > previous value + threshold, then the points are
395 not considered neighbors.
396
397 startlen is the point at which high-orbit is drawn after transform.
398 @param rstart is the multiple of radius that is startlen.
399*/
400void
402 std::vector<ssize_type>& vuvalues, vvstrings& vids,
403 const ssize_type value_max,
404 const int radius, const int rspace, const int rstart,
405 const typography& typo, const bool weighbyvaluep,
406 const ssize_type threshold = 1,
407 const int startlenm = 5)
408{
409 // Stash of values/ids for near pass, but after transforms are done.
410 vvstrings vidsnear;
411 std::vector<ssize_type> vuvaluesnear;
412
413 const int startlen = radius * startlenm;
414 const int linelen = rspace * 2;
415
416 // Process far values/ids in order.
417 // Draw furthest points, ids, values if no high extension previously and:
418 // 1. distance(v, vnext) >= threshold.
419 // 2. distance(v, vprevious) >= threshold.
420 bool skip = false;
421 for (uint i = 0; i < vuvalues.size(); ++i)
422 {
423 const strings& ids = vids[i];
424 const ssize_type v = vuvalues[i];
425 const bool finalp = i + 1 == vuvalues.size();
426 const bool firstp = i == 0;
427
428 // Check for expired skip.
429 // If last was high and now skip, re-evalue to make sure that
430 // the last value was close enough to matter.
431 // Continue as skipped only if < value_max / 4 ago, else reset.
432 if (skip)
433 {
434 const ssize_type vold = vuvalues[i - 1];
435 if (std::abs(v - vold) > (value_max / 4))
436 skip = false;
437 }
438
439 // Criteria for boosting into high orbit:
440 // not previously boosted
441 // not first, and last is within threshold
442 // not last, and next is within threshold
443 // first and last is close (within threshold with values wrapping)
444 const bool lastyesp = !firstp && (v - threshold <= vuvalues[i - 1]);
445 const bool nextyesp = !finalp && (v + threshold >= vuvalues[i + 1]);
446 const bool firstyesp = firstp && \
447 (v + std::abs(value_max - vuvalues.back()) <= threshold);
448 if (!skip && (nextyesp || lastyesp || firstyesp))
449 {
450 kusama_ids_at_uvalue(obj, origin, ids, v, value_max, radius, rspace,
451 rstart + startlen - linelen, linelen, typo,
452 weighbyvaluep);
453 skip = true;
454 }
455 else
456 {
457 vidsnear.push_back(ids);
458 vuvaluesnear.push_back(v);
459 skip = false;
460 }
461 }
462
463 // Return remaining in arguments.
464 vids = vidsnear;
465 vuvalues = vuvaluesnear;
466}
467
468
469/**
470 Radiate as *_per_uvalue_on_arc function, but group similar
471 values such that they are globbed into a satellite circle, ids
472 splayed around the satellite, and not written on top of each other
473 on the same arc/angle.
474
475 Framing circle radius is the result of dividing page dimensions by (rdenom).
476
477 Satellite circle radius is the product of the number of ids with
478 the same value times a base multipler (rbase).
479
480 When overlap is detected, move outward on radius if true, otherwise
481 move in.
482*/
483svg_element
485 const typography& typo, const id_value_umap& ivm,
486 const ssize_type value_max, const int radius,
487 const int rspace, const bool weighbyvaluep = true,
488 const bool collisionp = false,
489 const bool sortstringsbysizep = false)
490{
491 // Convert from string id-keys to int value-keys, plus an ordered
492 // set of all the unique values.
493 value_set uvalues;
494 value_id_ummap uvaluemm = to_value_id_mmap(ivm, uvalues);
495
496 // Map out preliminary data points. For each unique value in vuvalues
497 //
498 // - VIDS
499 // - construct a vector of strings that have that value, sorted
500 // - short to long
501 //
502 // - VPOINTNS
503 // - constuct a vector of points on the circumference with weight
504 std::vector<ssize_type> vuvalues(uvalues.begin(), uvalues.end());
505 std::vector<strings> vids;
506 for (const auto& v : vuvalues)
507 {
508 // Extract all the ids for a given value.
509 auto irange = uvaluemm.equal_range(v);
510 auto ibegin = irange.first;
511 auto iend = irange.second;
512 strings ids;
513 for (auto i = ibegin; i != iend; ++i)
514 ids.push_back(i->second);
515 if (sortstringsbysizep)
517 else
518 sort(ids.begin(), ids.end());
519 vids.push_back(ids);
520 }
521
522 // Remove zero value ids.
523 if (vuvalues.front() == 0)
524 {
525 auto& ids = vids[0];
526 std::clog << "eliding ids with value zero: " << std::endl;
527 for (const auto& s: ids)
528 std::clog << s << std::endl;
529 vuvalues.erase(vuvalues.begin());
530 vids.erase(vids.begin());
531 }
532
533 // First pass, collision-avoidance.
534 // Make sure at least one dimension has values before proceeding.
535 // For media objects, with value_max < 8, collision avoidance is minimal.
536 // With aggregates with ivm.size() > 350, threshold 2-4 is best.
537 if (collisionp && vuvalues.size() > 1)
538 {
539 // Approximate scaling given default typograhics.
540 ssize_type threshold(1);
541 if (value_max >= 20 && value_max < 190)
542 threshold = 2;
543 if (value_max >= 190 && value_max < 290)
544 threshold = 3;
545 if (value_max >= 290)
546 threshold = 4;
547
548 kusama_collision_transforms(obj, origin, vuvalues, vids, value_max,
549 radius, rspace, radius, typo, weighbyvaluep,
550 threshold);
551 }
552
553 // Draw remaining points, ids, values.
554 for (uint i = 0; i < vuvalues.size(); ++i)
555 {
556 // NB: vpointns valued from smallest to largest, so reverse so that
557 // smaller is drawn last and is more visible.
558 int j = vuvalues.size() - 1 - i;
559 auto& ids = vids[j];
560 auto v = vuvalues[j];
561
562 kusama_ids_at_uvalue(obj, origin, ids, v, value_max,
563 radius, rspace, radius, rspace, typo, weighbyvaluep);
564 }
565
566 return obj;
567}
568
569} // namespace svg
570
571#endif
uint significant_digits_in(ssize_type maxval)
The number of significant digits in.
std::unordered_multimap< value_type, string > value_id_ummap
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)
svg_element insert_svg_at(svg_element &obj, const string isvg, const point_2t origin, const double origsize, const double isize, const double angled=0, const style &styl=k::no_style)
Embed svg in group element.
std::unordered_map< string, value_type > id_value_umap
Hash multimap of unique value to (perhaps multiple) unique ids. Use this form for sorting by value.
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).
int radiate_glyph_and_id(svg_element &obj, const point_2t origin, const double angled, const double kr, const int rspace, const int rstart, const string id, const typography &typo)
double get_angle(ssize_type pvalue, ssize_type pmax)
Transform a value on a range to an angle on the radial range.
int ssize_type
Definition a60-svg.h:59
double adjust_angle_at_orbit_for_distance(double r, double dist, const double minadjust=0.25)
@ text
metadata, header
@ vector
svg path, circle, rectangle, etc.
@ svg
svg element, perhaps nested
void points_to_line(svg_element &obj, const style s, const point_2t origin, const point_2t end, const string dasharray="")
Line between two points.
std::vector< strings > vvstrings
Definition a60-svg.h:39
double adjust_angle_rotation(const double dorig, const k::rrotation rot)
Adjust angle above to CW/CCW orientation.
std::set< value_type > value_set
string file_to_svg_insert(const string ifile)
Import svg file, convert it to svg_element for insertion. ifile is a plain SVG file with a 1:1 aspect...
double & get_min_satellite_distance()
The minimum distance between satellites in high orbit.
int radiate_glyph(svg_element &obj, const point_2t origin, const double angled, const id_rstate idst, const int kr, const int rspace, const int rstart)
constexpr double char_width_to_px(const uint sz)
Approximate pixel height of type of point size @sz.
Definition a60-svg.h:282
int kusama_ids_orbit_high(svg_element &obj, const point_2t origin, const strings &ids, const ssize_type v, const ssize_type value_max, const int radius, const int rspace, const int rstart, const int linelen, const typography &typo, const bool wbyvaluep, const bool satellitep=false)
void kusama_collision_transforms(svg_element &obj, const point_2t origin, std::vector< ssize_type > &vuvalues, vvstrings &vids, const ssize_type value_max, const int radius, const int rspace, const int rstart, const typography &typo, const bool weighbyvaluep, const ssize_type threshold=1, const int startlenm=5)
point_2t get_circumference_point_d(const double ad, const double r, const point_2t origin)
Angle in degrees.
void kusama_ids_at_uvalue(svg_element &obj, const point_2t origin, const strings &ids, const ssize_type v, const ssize_type value_max, const int radius, const int rspace, const int rstart, const int linelen, const typography &typo, const bool weighbyvaluep)
Layer one value's glyphs and ids.
svg_element kusama_ids_per_uvalue_on_arc(svg_element &obj, const point_2t origin, const typography &typo, const id_value_umap &ivm, const ssize_type value_max, const int radius, const int rspace, const bool weighbyvaluep=true, const bool collisionp=false, const bool sortstringsbysizep=false)
int radiate_line_and_value(svg_element &obj, const point_2t origin, const double angled, const ssize_type v, const int rspace, const int rstart, const int linelen, const typography &typo, const style styl={color::black, 1, color::black,.25, 1})
void kusama_ids_orbit_low(svg_element &obj, const point_2t origin, const strings &ids, const ssize_type v, const ssize_type value_max, const int radius, const int rspace, const int rstart, const int linelen, const typography &typo, const bool wbyvaluep)
double set_min_satellite_distance(const double kuse)
By observation, 7pt = 5 minimum NB: Make sure distance is at least text height away for lowest values...
int & get_min_ring_size()
The smallest (sattelite) radius size allowed in a kusama orbit.
unsigned int uint
Definition a60-svg.h:57
std::tuple< space_type, space_type > point_2t
Point (x,y) in 2D space.
Definition a60-svg.h:65
int set_min_ring_size(const int sz)
By observation, type size 12pt = 5, 6pt = 2.
value_id_ummap to_value_id_mmap(const id_value_umap &ivm, value_set &uniquev)
Convert id_value_umap to value_id_mmap + set of unique values.
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.
bool is_visible(const select v) const
Datum consolidating style preferences.