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