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