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