izzi
SVG SUBSET C++ API
Loading...
Searching...
No Matches
a60-svg-graphs-chord.h
Go to the documentation of this file.
1// alpha60 horizontal chord separations -*- 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 a60_SVG_CHORD_GRAPHS_H
20#define a60_SVG_CHORD_GRAPHS_H 1
21
22#include "a60-metadata.h"
23
24namespace svg {
25
26using namespace a60::metadata;
27
28//// Distance between center and top/bottom block
29const double ctopspace(60);
30
31/// Distance between label and top/bottom of line or text
32const double ltopspace(10);
33
34const double hdenom(a60::k::age_values.size() + 1 + 1 + 1);
35
36// Type sizes.
37const auto lsz = 16; // large bold
38const auto asz = 12; // sub headings
39const auto ssz = 10; // sub sub headings
40
41/// Compute xoffset.
42uint
43get_xoffset(const string& id, const uint xstart, const double hspace)
44{ return xstart + (a60::k::age_value_to_multiple(id, true) * hspace); };
45
46
47/// Grid, titles, labels for h_chord_graph.
48/// @param yscale == how much to vertically scale the bar graph from center.
50h_chord_graph_labels(const string aggname,
52{
53 const string viztitle("chord-graph-" + aggname);
54 svg_element obj(viztitle, a);
55
56 // Starting positions and vertical/horizontal offsets.
57 const auto width = a._M_width;
58 const double hspace((width - (a60::k::metamargin * 2)) / hdenom);
59 const uint xstart(a60::k::metamargin + hspace + hspace);
60
61 // Vertical have rect, vo number, filler, age, filler, vp number, rect.
62 // Middle is age values.
63 const auto height = a._M_height;
64 const double vcenter(height / 2);
65
66 typography typol = get_metadata_typo(lsz, true);
67 sized_text(obj, typol, lsz, "age values", a60::k::metamargin, vcenter);
68
69 typography typoli = get_metadata_typo(asz, false);
71 sized_text(obj, typoli, ssz, "birth age values per decile", a60::k::metamargin,
72 vcenter - ctopspace);
73 string playingll("#" + a60::k::playing + " age values per decile");
74 sized_text(obj, typoli, ssz, playingll, a60::k::metamargin,
75 vcenter + ctopspace + asz);
76
77 // Draw horizontal labels over connecting lines.
78 // Draw h labels, ages with v hair lines up and down at each value.
81 style styll = get_metadata_style(false);
82 styll._M_stroke_size = 0.5;
83 styll._M_stroke_opacity = 1.0;
84 for (const auto& val : a60::k::age_values)
85 {
86 uint xoff = get_xoffset(val, xstart, hspace);
87 sized_text(obj, typol, lsz, val, xoff, vcenter);
88
89 // Top line.
90 point_2t topstart(xoff, vcenter - lsz - ltopspace);
91 point_2t topend(xoff, vcenter - ctopspace + ltopspace);
92 auto l1 = make_line(topstart, topend, styll);
93 obj.add_element(l1);
94
95 // Bottom line.
96 point_2t bstart(xoff, vcenter + lsz + ltopspace);
97 point_2t bend(xoff, vcenter + ctopspace - ltopspace);
98 auto l2 = make_line(bstart, bend, styll);
99 obj.add_element(l2);
100 }
101
102 return obj;
103}
104
105
106/** A chord diagram, flattened into a linear horizontal form.
107
108 Rendering set/subset of metadata with "playing" compound grammars.
109
110 The Vertical (roughly, in thirds):
111 top is actual age graph
112 middle is ages plotted on horizontal axes
113 bottom is playing age graph, inverted.
114
115 And then a connecting line will be drawn from the actual age to
116 the playing age.
117
118 The Horizontal:
119 Use linear scaling for age values, instead of bell shaped scaling for glyph.
120 have 10 values (00-09, ..., 90-99) + 100+ + unknown +ambiguous -> 13.
121*/
123h_chord_graph(const vumids& cumulative, const string aggname,
124 const area<> a = svg::k::v1080p_h, const double yscale = 0.7,
125 const double rwidth = 10)
126{
127 using id_size = a60::id_size;
128
129 // Draw labels, axes, ticks, etc.
130 svg_element obj = h_chord_graph_labels(aggname, a);
131
132 // Starting positions and vertical/horizontal offsets.
133 const auto width = a._M_width;
134 const double hspace((width - (a60::k::metamargin * 2)) / hdenom);
135 const uint xstart(a60::k::metamargin + hspace + hspace);
136
137 // Vertical have rect, vo number, filler, age, filler, vp number, rect.
138 // Middle is age values.
139 const auto height = a._M_height;
140 const double vcenter(height / 2);
141
142 /**
143 Process cumulative age values 1-3.
144
145 1. ages vo map
146 2. ages playing map
147 3. largest count value over all of the age values
148 */
149 const uint agesi = a60::k::find_id_index("ages");
150 const umids& ages = cumulative[agesi];
151 umids agesvo;
152 umids agesvp;
153 id_size maxno(0);
154 for (const auto& val : ages)
155 {
156 auto [ id, count ] = val;
157
158 if (id.find(a60::k::playing) != string::npos)
159 agesvp.insert(val);
160 else
161 agesvo.insert(val);
162 maxno = std::max(maxno, count);
163 }
164 const id_size maxn = maxno * 2;
165 std::cout << "max n is: " << maxn << std::endl;
166
167 /**
168 Process cumulative age values 4.
169
170 4. coalesce/flatten multi-playing values
171 "50-59 playing 50-59, 60-69"
172 "50-59 playing 10-19, 60-69
173
174 to
175
176 "50-59 playing 50-59" x 1
177 "50-59 playing 10-19" x 1
178 "50-59 playing 60-69" x 2
179 */
180 umids agesvpflat;
181 for (const auto& val : agesvp)
182 {
183 auto [ id, count ] = val;
184
185 // Pick out all age_values from playing age_values.
186 strings ageso;
187 strings agesplaying;
188 split_playing_value(id, ageso, agesplaying);
189 for (const string& valso : ageso)
190 {
191 for (const string& valsp : agesplaying)
192 {
193 const string splayings = k::space + a60::k::playing + k::space;
194 const string valpflat = valso + splayings + valsp;
195 if (agesvpflat.count(valpflat))
196 {
197 auto& val = agesvpflat.at(valpflat);
198 val += count;
199 }
200 else
201 agesvpflat.insert(make_pair(valpflat, count));
202 }
203 }
204 }
205
206 // Setup.
207 typography typol = get_metadata_typo(lsz, true);
208 sized_text(obj, typol, lsz, "age values", a60::k::metamargin, vcenter);
209
210 typography typov = get_metadata_typo(lsz, false, true);
211
212 const uint ytotalgraphspace(vcenter - ctopspace - ltopspace - a60::k::metamargin);
213 const uint ygraphdelta = std::round(yscale * ytotalgraphspace);
214
215 // Size per two of largest values in one v space.
216 const uint ydelta(ygraphdelta / 2);
217
218 // Draw ages.
219 // Starting point for above-the-age-values line, above summary count
220 uint voffstart = vcenter - ctopspace - ltopspace - asz;
221
222 // Vector of total age_value counts per decile for birth ages.
223 using vul = a60::vul;
224 vul vsum(a60::k::age_values.size(), 0);
225 for (const auto& val : agesvo)
226 {
227 auto [ id, count ] = val;
228 uint xoff = get_xoffset(id, xstart, hspace);
229
230 // rect
231 double rheight = scale_value_on_range(count, 1, maxn, 10, ydelta);
232 double rystart = voffstart - (rheight / 2);
233 rect_element r = make_rect_centered({xoff, rystart},
234 get_metadata_style(),
235 { rwidth, rheight });
236 obj.add_element(r);
237
238 // text
239 sized_text(obj, typov, asz, std::to_string(count), xoff,
240 voffstart - rheight - ltopspace);
241
242 const auto indx = a60::k::age_value_to_multiple(id, true);
243 vsum[indx] += count;
244 }
245
246 // Draw ages playing.
247 // Starting point for below-the-age-values line, below summary count.
248 uint vpoffstart = vcenter + ctopspace + ltopspace + asz;
249
250 style pstyl = { color::gray40, 1.0, color::gray40, 0.0, 0 };
251 style pstyll = { color::gray40, 0.0, color::red, 1, 2 };
252
253 // Vector of total age_value counts per decile for playing.
254 vul vpsum(a60::k::age_values.size(), 0);
255
256 // Vector of previous vertical offsets, how many "playing" slots already filled.
257 vul vpyoffsets(a60::k::age_values.size(), 0);
258 for (const auto& val : agesvpflat)
259 {
260 auto [ idp, count ] = val;
261
262 // Pick out LHS age from RHS ages playing.
263 strings ageso;
264 strings agesplaying;
265 split_playing_value(idp, ageso, agesplaying);
266
267 // For each age playing value, keep track of the total number
268 // from each actual age value.
269 // NB: 40-49 playing 20-29, 30-39, 40-49 is counted as 1 actual 2 playing.
270 // And no 40-49 to 40-49 line
271 for (const string& agepv : agesplaying)
272 {
273 const auto indxp = a60::k::age_value_to_multiple(agepv, true);
274
275 // Check to see if ages playing value is the same as any age value.
276 const auto aend = ageso.end();
277 const bool agesamep = std::find(ageso.begin(), aend, agepv) != aend;
278 if (agesamep)
279 {
280 // Only add to birth age sum.
281 // NB: indxp and indx are the same for this value.
282 vsum[indxp] += count;
283 }
284 else
285 {
286 // Calculate vertical offset, including previous playing values.
287 ulong& yoffset = vpyoffsets[indxp];
288 uint xoff = get_xoffset(agepv, xstart, hspace);
289 uint yoff = vpoffstart + yoffset;
290
291 // rect
292 auto normalize_v_o_r = scale_value_on_range;
293 double rheight = normalize_v_o_r(count, 1, maxn, 10, ydelta);
294 auto rectcp = std::make_tuple(xoff, yoff + (rheight / 2));
295 rect_element r = make_rect_centered(rectcp, pstyl,
296 { rwidth, rheight });
297 obj.add_element(r);
298
299 // text
300 sized_text(obj, typov, ssz, std::to_string(count), xoff,
301 yoff + rheight + ltopspace + ssz);
302
303 // Add rectangle, space, text tile to offsets.
304 yoffset += rheight + ltopspace + ssz + ltopspace;
305
306 // Draw connnectors.
307 for (const string& agev : ageso)
308 {
309 // Style from agepv.
310 style pstylage = pstyll;
311 auto klr = age_value_to_color_tint_shade(agepv);
312 pstylage._M_stroke_color = klr;
313
314 // Dash style if agepv == "ambiguous".
315 string dashstr;
316 const bool vback = agepv == a60::k::age_values.back();
317 const bool vfront = agepv == a60::k::age_values.front();
318 if (vback || vfront)
319 dashstr = "4 1";
320
321 const bool strokewidenp = false;
322 auto strokesz(0);
323 if (strokewidenp)
324 {
325 strokesz = pstylage._M_stroke_size * count;
326 pstylage._M_stroke_size = strokesz;
327 }
328
329 uint xoffvo = get_xoffset(agev, xstart, hspace);
330 point_2t bstart(xoffvo, vcenter + ltopspace);
331 point_2t bend(xoff, yoff + (pstylage._M_stroke_size / 2));
332
333 auto ln = make_line(bstart, bend, pstylage, dashstr);
334 obj.add_element(ln);
335
336 // Add to sum of playing and birth ages.
337 vpsum[indxp] += count;
338
339 const auto indx = a60::k::age_value_to_multiple(agev, true);
340 vsum[indx] += count;
341 }
342 }
343 }
344 }
345
346 // Print out sum of all ages per value, and sum total grey notch.
347 for (const auto& val : a60::k::age_values)
348 {
349 auto indx = a60::k::age_value_to_multiple(val, true);
350 uint xoff = get_xoffset(val, xstart, hspace);
351 uint yoff = vcenter - ctopspace;
352 uint total = vsum[indx];
353 if (total > 0)
354 {
355 sized_text(obj, typov, asz, std::to_string(total), xoff, yoff);
356
357 // Notch.
358 double rheight = scale_value_on_range(total, 1, maxn, 10, ydelta);
359 double ynotch = voffstart - rheight - ltopspace - asz;
360
361 // Top line.
362 point_2t topstart(xoff - (rwidth / 2), ynotch);
363 point_2t topend(xoff + (rwidth / 2), ynotch);
364
365 style nstyl = svg::k::w_style;
366 nstyl._M_stroke_size = 3;
367 nstyl._M_stroke_opacity = 1.0;
368 auto ln = make_line(topstart, topend, nstyl);
369 obj.add_element(ln);
370 }
371 }
372
373 // Print out sum of all playing ages per value.
374 for (const auto& val : a60::k::age_values)
375 {
376 auto indx = a60::k::age_value_to_multiple(val, true);
377 uint xoff = get_xoffset(val, xstart, hspace);
378 uint yoff = vpoffstart - ltopspace;
379 uint total = vpsum[indx];
380 if (total > 0)
381 sized_text(obj, typov, asz, std::to_string(total), xoff, yoff);
382 }
383
384 // Total counts of birth ages and playing ages and "playing %"
385 uint avocount = std::accumulate(vsum.begin(), vsum.end(), uint(0));
386 uint avpcount = std::accumulate(vpsum.begin(), vpsum.end(), uint(0));
387 double playingper = double(avpcount) / (avpcount + avocount);
388 uint playingperi = static_cast<uint>(playingper * 100);
389 string playingperstr = std::to_string(playingperi) + "% playing";
390
391 const uint xsumoff = width - a60::k::metamargin;
394 sized_text(obj, typol, lsz, "total values found", xsumoff, vcenter);
395
397 sized_text(obj, typol, asz, std::to_string(avocount), xsumoff,
398 vcenter - ctopspace);
399 sized_text(obj, typol, asz, std::to_string(avpcount), xsumoff,
400 vcenter + ctopspace + asz);
401 sized_text(obj, typol, asz, playingperstr, xsumoff,
402 vcenter + ctopspace + asz * 3);
403 return obj;
404}
405
406
407/// Analyze metadata directory or subset of it and generate summary
408/// output for all in directory.
409void
410render_metadata_aggregate_chord(const area<> a, const vumids& cumulative,
411 const strings& namedkeyso, const string aggname)
412{
413 // Rank ids.
414 vsids idsranked(a60::k::id_dimensions.size());
415 for (uint index(0); index < a60::k::id_dimensions.size(); ++index)
416 {
417 sids& csids = idsranked[index];
418 const umids& cumuids = cumulative[index];
419 for (const auto& idp : cumuids)
420 {
421 auto [ name, count ] = idp;
422 csids.insert(make_tuple(count, name));
423 }
424 }
425
426 // Serialize.
427 serialize_metadata_aggregate(idsranked, namedkeyso, aggname);
428
429 // Render graph/chord element.
430 const string outname = mangle_aggregate(aggname);
431 svg_element obj = h_chord_graph(cumulative, outname, a);
432
433 // Titles.
434 auto tsz = 16;
435 typography typot = get_metadata_typo(tsz, true, false);
436
437 auto [ width, height ] = obj._M_area;
438 auto xpos = width - a60::k::metamargin;
439 auto ypos = height - a60::k::metamargin;
440
441 // Ages / partition titles.
442 string title("ages");
443 if (!aggname.empty())
444 title += string(a60::k::space + aggname);
445 sized_text_r(obj, typot, tsz, title, xpos, ypos, -90);
446
447 // Subset.
448 render_metadata_aggregate_namekeys(obj, namedkeyso, xpos);
449}
450
451
452/// Analyze metadata directory or subset of it, and generate summary
453/// output for all in directory. Iff field is non zero, then subset
454/// it to data files with field and match.
455void
457 const string field = "", const string match = "",
458 const string wfield = "", const string wvalue = "")
459{
460 // Set of files used for summary.
461 const string idir = a60::io::get_run_time_resources().metadata;
462 const strings files = field.empty() ?
463 a60::io::populate_files(idir, ".json") :
464 files_with_field_matching(field, match);
465
466 vumids cumulative = cache_metadata(files, wfield, wvalue);
467
468 // Get set of named keys.
469 strings namedkeys;
470 if (wfield.empty())
471 namedkeys = files_with_field_matching(field, match, true);
472 else
473 namedkeys = files_with_fields_matching(files, wfield, wvalue, true);
474
475 string outname;
476 const string outname1 = mangle_metadata_aggregate(outname, field, match);
477 const string outname2 = mangle_metadata_aggregate(outname1, wfield, wvalue);
478 render_metadata_aggregate_chord(a, cumulative, namedkeys, outname2);
479}
480
481
482/// Analyze metadata directory or subset of it, and generate summary
483/// output for all in directory.
484void
486 const string aggname,
487 const string wfield = "", const string wvalue = "")
488{
489 string idir = a60::io::get_run_time_resources().metadata;
490 idir = a60::io::end_path(idir);
491
492 // Vector with cumulative maps of name to count for each dimension
493 // in a60::k::id_dimensions.
494 strings files;
495 for (const string& s: namedkeys)
496 files.push_back(idir + s + ".json");
497
498 vumids cumulative = cache_metadata(files, wfield, wvalue);
499
500 // Match name_keys against wfield, if any.
501 strings namedkeysedit;
502 if (wfield.empty())
503 namedkeysedit = namedkeys;
504 else
505 {
506 strings namedkeys2 = files_with_fields_matching(files, wfield, wvalue, true);
507 namedkeysedit = namedkeys2;
508 }
509
510 const string outname = mangle_metadata_aggregate(aggname, wfield, wvalue);
511 render_metadata_aggregate_chord(a, cumulative, namedkeysedit, outname);
512}
513
514} // namepace svg
515
516#endif
void add_element(const element_base &e)
const style w_style
constexpr area v1080p_h
line_element make_line(const point_2t origin, const point_2t end, style s, const string dasharray="")
Line primitive.
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.
svg::svg_element h_chord_graph(const vumids &cumulative, const string aggname, const area<> a=svg::k::v1080p_h, const double yscale=0.7, const double rwidth=10)
const auto asz
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:68
const auto lsz
const double ltopspace(10)
Distance between label and top/bottom of line or text.
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.
const double hdenom(a60::k::age_values.size()+1+1+1)
unsigned long ulong
Definition a60-svg.h:59
const double ctopspace(60)
void render_metadata_aggregate_chord(const area<> a, const vumids &cumulative, const strings &namedkeyso, const string aggname)
Analyze metadata directory or subset of it and generate summary output for all in directory.
void sized_text(element_base &obj, svg::typography typo, const int sz, const string text, const int tx, const int ty)
Text at size.
svg::svg_element h_chord_graph_labels(const string aggname, const svg::area<> a=svg::k::v1080p_h)
Grid, titles, labels for h_chord_graph.
uint get_xoffset(const string &id, const uint xstart, const double hspace)
Compute xoffset.
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
const auto ssz
std::vector< string > strings
Definition a60-svg.h:38
void analyze_metadata_aggregate_chord(const area<> a, const string field="", const string match="", const string wfield="", const string wvalue="")
Analyze metadata directory or subset of it, and generate summary output for all in directory....
Datum consolidating style preferences.
color_qi _M_stroke_color
@ right
Right part of text block.
@ center
Center part of text block.
@ middle
Center the middle of the text block at point.
@ end
End the text block at point.