izzi
SVG SUBSET C++ API
Loading...
Searching...
No Matches
a60-svg-graphs-line.h
Go to the documentation of this file.
1// izzi line graphs -*- mode: C++ -*-
2
3// Copyright (c) 2025, Benjamin De Kosnik <b.dekosnik@gmail.com>
4
5// This file is part of the alpha60 library. This library is free
6// software; you can redistribute it and/or modify it under the terms
7// of the GNU General Public License as published by the Free Software
8// Foundation; either version 3, or (at your option) any later
9// version.
10
11// This library is distributed in the hope that it will be useful, but
12// WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14// General Public License for more details.
15
16#ifndef izzi_SVG_LINE_GRAPHS_H
17#define izzi_SVG_LINE_GRAPHS_H 1
18
19#include <set>
20
21#include "izzi-json-basics.h"
24#include "a60-svg-markers.h"
25
26
27namespace svg {
28
29/// Polyline/line options.
30///
31/// 1: use one line with css dasharray and markers mid, end points
32/// 2: use two lines: one with css dasharray and no marker_form, two
33/// with explicit marker paths and added text tooltips
34/// 3: use two lines and add js + image tooltips: like 2 above
35/// but add image tooltips, with js controlling image visibility.
37 {
38 chart_line_style_1 = 100, ///< One Element, one path plus css dasharray and markers
39 chart_line_style_2 = 200, ///< Two Element, one path, one marker
40 chart_line_style_3 = 300 ///< Three Element, one path, one marker, image
41 };
42
43/**
44 Line Graphs / Line Charts.
45
46 Some Example:
47 https://www.highcharts.com/demo/highcharts/accessible-line
48
49 Outline:
50
51 input has 2 columns: x, y
52 - how many x, what is range, what is delta
53 - how many y, what is range, what is delta
54
55 plot as grid/matrix system given above.
56
57 line: points, linestyle
58
59 x axis: title, tick mark spacing, tick mark style
60 y axis: title, tick mark spacing, tick mark style
61*/
62
63/// Per-graph constants, metadata, configuration, text.
65{
67
68#if 0
69 /// 900 x 600
70 /// Margins/Spaces
71 static constexpr uint xmargin = 100;
72 static constexpr uint ymargin = 100;
73
74 /// Type sizes
75 static constexpr uint ttitlesz = 16; // title large bold
76 static constexpr uint th1sz = 12; // h1
77 static constexpr uint tpsz = 10; // text, paragraph,
78 static constexpr uint tticsz = 7; // tic text
79#else
80 /// 1920 x 1080
81 /// Margins/Spaces
82 static constexpr uint xmargin = 200;
83 static constexpr uint ymargin = 200;
84
85 /// Type sizes
86 static constexpr uint ttitlesz = 26; // title large bold
87 static constexpr uint th1sz = 18; // h1
88 static constexpr uint tpsz = 10; // text, paragraph, marker
89 static constexpr uint tticsz = 14; // tic text
90#endif
91
92 /// Key data: title, area, mode
93 string title; /// graph title
94 area_type graph_area; /// graph area
95 graph_mode mode; /// chart_line_style_n to use
96
97 /// Labels, tic units.
98 static constexpr uint xticdigits = 1; // fp sig digits xaxis
99 static constexpr uint yticdigits = 10; // number y tic labelsf
100 string xlabel; // x axis label
101 string ylabel;
102 string xticu; // x axis tick mark units postfix
103 string yticu;
104
105 /// Line/Outline/Markers/Tooltip styles
106 style lstyle; /// line style
107 stroke_style sstyle; /// marker stroke style, if any.
108
109 /// Image Tooltip
110 area_type tooltip_area; /// chart_line_style_3 tooltip size
111 string tooltip_id; /// chart_line_style_3 toolip id prefix
112 string tooltip_images; /// chart_line_style 3 set of image elements
113};
114
115
116/// Simplify sorted vrange by removing interior duplicates.
117vrange
119{
120 // Transform range to the simplest expression, where multiple points
121 // without significant vertical change are coalesed to starting
122 // control point and ending control point.
123 vrange simplevr;
124 point_2t last = { -1.0, -1.0 };
125 double duprangep(false);
126 for (const point_2t& pt : vr)
127 {
128 auto [ x, y] = pt;
129 if (y != get<1>(last))
130 {
131 if (duprangep == true)
132 {
133 simplevr.push_back(last);
134 duprangep = false;
135 }
136 simplevr.push_back(pt);
137 }
138 else
139 duprangep = true;
140 last = pt;
141 }
142 return simplevr;
143}
144
145
146/// Tramsform change points to points where the y-axis (% visual complete) changes
147/// @param points already simplified change points
148vrange
150{
151 vrange simplest;
152 point_2t last = { -1.0, -1.0 };
153 for (const point_2t& pt : points)
154 {
155 auto [ x, y] = pt;
156 if (y != get<1>(last))
157 simplest.push_back(pt);
158 last = pt;
159 }
160 return simplest;
161}
162
163
164/// Tramsform change points to points where the x-axis (time) matches
165/// a value in onlypoints.
166///
167/// @param points already simplified change points
168vrange
169find_tooltip_points(const vrange& points, const vspace& onlypoints)
170{
171 vrange edited;
172 for (const space_type& matchx : onlypoints)
173 {
174 for (const auto& pt : points)
175 {
176 auto [ x, y ] = pt;
177 if (x == matchx)
178 {
179 edited.push_back(pt);
180 break;
181 }
182 }
183 }
184 return edited;
185}
186
187
188/// Map data points to cartestian points on graph area.
189/// @param data points
190vrange
192 const graph_rstate& gstate,
193 const point_2t xrange, const point_2t yrange)
194{
195 auto [ minx, maxx ] = xrange;
196 auto [ miny, maxy ] = yrange;
197
198 // Locate graph area on plate area.
199 // aplate is total plate area with margins, aka
200 // pwidth = xmargin + gwidth + xmargin
201 // pheight = ymargin + gheight + ymargin
202 auto [ pwidth, pheight ] = gstate.graph_area;
203 double gwidth = pwidth - (2 * gstate.xmargin);
204 double gheight = pheight - (2 * gstate.ymargin);
205 const double chartyo = pheight - gstate.ymargin;
206
207 // Transform data points to scaled cartasian points in graph area.
208 vrange cpoints;
209 for (uint i = 0; i < points.size(); i++)
210 {
211 const point_2t& pt = points[i];
212 auto [ vx, vy ] = pt;
213
214 // At bottom of graph.
215 const double xlen = scale_value_on_range(vx, minx, maxx, 0, gwidth);
216 double x = gstate.xmargin + xlen;
217
218 // Y axis grows up from chartyo.
219 const double ylen = scale_value_on_range(vy, miny, maxy, 0, gheight);
220 double y = chartyo - ylen;
221
222 cpoints.push_back(std::make_tuple(x, y));
223 }
224 return cpoints;
225}
226
227
228/// Return set of images for image tooltips, one for each point.
229/// @param aimg is size of image embedded inside svg element.
230/// @pathprefix is the path to the directory with the store of images
231/// @idimgbase is the root name for what will be document level unique names of
232/// sequentially numbered images (say fximage-, for fximage-101 et al)
233/// Expected, zero filled imageid.
234/// 2025-06-26-android-15-ptablet-talkback-4usted-firefox_13188.webp
235group_element
236make_line_graph_images(const vrange& points, const graph_rstate& gstate,
237 const string imgprefix,
238 const string imgpath = "../filmstrip/",
239 const string imgext = ".webp")
240{
242 g.start_element(gstate.title + "-tooltip-images");
243 for (const point_2t p : points)
244 {
245 std::ostringstream oss;
246 oss << std::setfill('0') << std::setw(5);
247 oss << static_cast<uint>(std::get<0>(p));
248 const string xms = oss.str();
249
250 const string isrc = imgpath + imgprefix + xms + imgext;
251 const string imgid = gstate.tooltip_id + xms;
252 auto [ width, height ] = gstate.tooltip_area;
253
255 image_element::data di = { isrc, 0, 0, width, height };
256 i.start_element(imgid);
257 i.add_data(di, "anonymous", "hidden", "");
258 //i.add_data(di, "anonymous", "", "none");
259 i.finish_element();
260 g.add_element(i);
261 }
262 g.finish_element();
263 return g;
264}
265
266
267/// Make marker or composite markers for one marker location.
268string
270 const style styl, const double radius,
271 const string tipstr = "", const string imgid = "")
272{
273 string pointstr;
274 marker_element mrkr;
275 element_base& mkr = mrkr;
276 switch (form)
277 {
279 mkr = make_circle_marker(cpoint, styl, radius, tipstr, "", imgid);
280 break;
282 mkr = make_path_marker(cpoint, styl, radius, 3, tipstr, imgid);
283 break;
285 mkr = make_rect_marker(cpoint, styl, radius, tipstr, "", imgid);
286 break;
288 mkr = make_path_marker(cpoint, styl, radius, 6, tipstr, imgid);
289 break;
291 mkr = make_octahedron(cpoint, styl, radius, tipstr);
292 break;
294 mkr = make_icosahedron(cpoint, styl, radius, tipstr);
295 break;
297 mkr = make_sunburst(cpoint, styl, radius, 11, tipstr);
298 break;
299 case marker_shape::x:
300 {
301 auto [ cx, cy ] = cpoint;
302 string xform = transform::rotate(45, cx, cy);
303 string xttr = mkr.make_transform_attribute(xform);
304 mkr = make_path_center_mark(cpoint, styl, radius, radius / 3, xttr, tipstr);
305 break;
306 }
308 mkr = make_path_blob(cpoint, styl, radius, 5, tipstr);
309 break;
311 mkr = make_lauburu(cpoint, styl, radius / 3, 4.0, 6, tipstr);
312 break;
314 mkr = make_path_ripple(cpoint, styl, radius * 1.5, radius, 2.5, 2, tipstr);
315 break;
316 default:
317 string m("make_marker_instance:: ");
318 m += "marker_shape (";
319 m += to_string(form);
320 m += ")";
321 m += "is invalid or missing, skipping...";
322 m += k::newline;
323 std::clog << m << std::endl;
324 break;
325 }
326 pointstr = mkr.str();
327 return pointstr;
328}
329
330
331/// Return set of paths of marker shapes with text tooltips.
332/// NB: For graph_mode >= chart_line_style_2
333string
334make_line_graph_markers(const vrange& points, const vrange& cpoints,
335 const graph_rstate& gstate, const double radius,
336 const string imgidbase = "")
337{
338 string ret;
339 for (uint i = 0; i < points.size(); i++)
340 {
341 auto [ vx, vy ] = points[i];
342 auto cpoint = cpoints[i];
343
344 // If there are tooltip images, link in here.
345 string imgid(imgidbase);
346 if (!imgid.empty())
347 {
348 std::ostringstream oss;
349 oss << std::setfill('0') << std::setw(5);
350 oss << static_cast<uint>(vx);
351 const string xms = oss.str();
352 string imgidn(imgid + xms);
353 imgid = script_element::tooltip_attribute(imgidn);
354 }
355
356 // Generate displayed tooltip text....
357 string tipstr(gstate.title);
358 tipstr += k::newline;
359 tipstr += "week: ";
360 tipstr += std::to_string(static_cast<uint>(vx));
361 //tipstr += gstate.yticu;
362 tipstr += k::newline;
363
364 std::ostringstream oss;
365 oss.imbue(std::locale(""));
366 oss << std::fixed << std::setprecision(0) << vy;
367 tipstr += oss.str();
368 //tipstr += std::to_string(static_cast<uint>(vx));
369 //tipstr += gstate.xticu;
370
371 // Markers default to closed paths that are filled with no stroke.
372 // Setting visible to vector | echo induces outline behavior.
373 style styl = gstate.lstyle;
374 styl._M_fill_opacity = 1;
375 if (gstate.is_visible(select::echo))
376 {
378 styl._M_stroke_size = 0.5;
379 }
380 else
381 styl._M_stroke_opacity = 0;
382
383 const auto& form = gstate.sstyle.marker_form;
384 ret += make_marker_instance(form, cpoint, styl, radius, tipstr, imgid);
385
386 // Add additional marker or markers.
387 // dr == distance from p1 for echo/rep marker
388 // shrinkf == how much to reduce the echo/rep from the original
389 const double dr = radius * 2;
390 const double shrinkf(0.75);
391 const ushort rep = gstate.sstyle.marker_reps;
392 if (rep > 0 && i + 1 < points.size())
393 {
394 // Find the next point, and then inch along along the line connecting.
395 auto [ cx1, cy1 ] = cpoint;
396 auto cpointnext = cpoints[i + 1];
397 auto [ cx2, cy2 ] = cpointnext;
398
399 // Calculate the vector from p1 to p2
400 double dx = cx2 - cx1;
401 double dy = cy2 - cy1;
402
403 // Calculate the distance between p1 and p2
404 double d = distance_cartesian(cpoint, cpointnext);
405
406 // Calculate the unit vector from p1 to p2
407 double unit_x = dx / d;
408 double unit_y = dy / d;
409
410 // Calculate by moving distance dr from p1 in the direction of the unit vector
411 double x3 = cx1 + dr * unit_x;
412 double y3 = cy1 + dr * unit_y;
413 point_2t rpoint(x3, y3);
414
415 ret += make_marker_instance(form, rpoint, styl, radius * shrinkf,
416 "", "");
417 }
418
419 }
420 return ret;
421}
422
423
424/// Axis Labels
425/// Axis X/Y Ticmarks
426/// X line increments
427///
428/// @param aplate = total size of graph area
429/// @param points = vector of {x,y} points to graph
430/// @param gstate = graph render state
431/// @param xscale = scale x axis by this ammount (1000 if converting ms to s)
432/// @param yscale = scale y axis by this ammount
433/// @param typo = typography to use for labels
434svg_element
436 const point_2t xrange, const point_2t yrange,
437 const double xscale = 1, const double yscale = 1,
438 const typography typo = k::apercu_typo)
439{
440 using namespace std;
441
442 // Locate graph area on plate area.
443 auto [ pwidth, pheight ] = gstate.graph_area;
444 double gwidth = pwidth - (2 * gstate.xmargin);
445 double gheight = pheight - (2 * gstate.ymargin);
446 const double chartyo = pheight - gstate.ymargin;
447 const double chartxo = gstate.xmargin;
448 const double chartxe = pwidth - gstate.xmargin;
449
450 auto [ xmin, xmax ] = xrange;
451 auto [ ymin, ymax ] = yrange;
452
453 // Separate tic label values for each (x, y) axis, find ranges for each.
454 const vrange cpoints = transform_to_graph_points(points, gstate,
455 xrange, yrange);
456 auto [ maxx, maxy ] = max_vrange(points, gstate.xticdigits, xscale, yscale);
457
458 const double xrangec(maxx - xmin);
459 const double gxscale(gwidth / xrangec);
460 const double yrangec(maxy - ymin);
461 const double gyscale(gheight / yrangec);
462
463 // Y axis is simpler, 0, 10, 20, ..., 80, 90, 100 in percent.
464 const double ydelta = yrangec / gstate.yticdigits;
465
466 // Derive the number of tick marks.
467
468 // Use a multiple of 5 to make natural counting easier.
469 // Start with an assumption of 20 tic marks for the x axis.
470#if MOZ
471 double xtickn(xrangec * 2); // .5 sec
472 if (xtickn < 10)
473 xtickn = 10;
474 if (xtickn > 26)
475 xtickn = xrangec;
476#else
477 double xtickn = 53;
478#endif
479
480 // X axis is seconds, xtickn minimum delta is 0.1 sec.
481#if MOZ
482 double xticmindelta = 0.1;
483 double xdelta = std::max(xrangec / xtickn, xticmindelta);
484 // Round up to significant digits, so if xdelta is 0.18 round to 0.2.
485 xdelta = std::round(xdelta * gstate.xticdigits * 10) / (gstate.xticdigits * 10);
486#else
487 double xticmindelta = 1;
488 double xdelta = std::max(xrangec / xtickn, xticmindelta);
489#endif
490
491 // Base typo for annotations.
492 typography anntypo = typo;
493 anntypo._M_style = k::wcagg_style;
494
495 // Start adding elements.
496 svg_element lanno(gstate.title, "line graph annotations",
497 gstate.graph_area, false);
498 lanno.add_raw(group_element::start_group("annotations"));
499
500 // Chart title.
501 if (gstate.is_visible(select::title))
502 {
505
506 const double lyo = graph_rstate::ymargin / 2;
507 const double loffset = double(anntypo._M_size);
508 point_2t xlabelp = make_tuple(pwidth / 2, lyo + loffset);
509 styled_text(lanno, gstate.title, xlabelp, anntypo);
510 }
511
512 // Axes Lines and Tic Labels
513 if (gstate.is_visible(select::axis))
514 {
515 lanno.add_raw(group_element::start_group("axes-lines"));
516
519
520 // Add axis labels.
521 const double loffset = double(anntypo._M_size) * 1.5;
522
523 const double lyo = pheight - graph_rstate::ymargin / 2;
524 point_2t xlabelp = make_tuple(pwidth / 2, lyo + loffset);
525 string xlabel = gstate.xlabel;
526 std::transform(xlabel.begin(), xlabel.end(), xlabel.begin(),
527 [](unsigned char c){ return std::toupper(c); });
528 styled_text(lanno, xlabel, xlabelp, anntypo);
529
530 const double lxo = graph_rstate::xmargin / 2;
531 point_2t ylabelp = make_tuple(lxo - loffset, pheight / 2);
532 string ylabel = gstate.ylabel;
533 std::transform(ylabel.begin(), ylabel.end(), ylabel.begin(),
534 [](unsigned char c){ return std::toupper(c); });
535 styled_text_r(lanno, ylabel, ylabelp, anntypo, -90, ylabelp);
536
537 // Add axis lines.
538 if (gstate.is_visible(select::vector))
539 {
540 line_element lx = make_line({chartxo, chartyo}, {chartxe, chartyo},
541 gstate.lstyle);
542 line_element ly = make_line({chartxo, chartyo}, {chartxo, gstate.ymargin},
543 gstate.lstyle);
544 lanno.add_element(lx);
545 lanno.add_element(ly);
546 }
547
550 }
551
552 // Horizontal lines linking left and right y-axis tic label value to each other,
553 // perhaps with magnification-ready micro text.
554 if (gstate.is_visible(select::linex))
555 {
556 lanno.add_raw(group_element::start_group("tic-y-label-lines"));
557
558 style hlstyl = gstate.lstyle;
560
561 anntypo._M_size = 3;
563
564 for (double y = ymin + ydelta; y < maxy + ydelta; y += ydelta)
565 {
566 // Base line layer.
567 const double yto = chartyo - (y * gyscale);
568 const double ytol = yto - (hlstyl._M_stroke_size / 2);
569 auto lxe = make_line({chartxo + graph_rstate::th1sz, ytol},
570 {chartxe - graph_rstate::th1sz, ytol}, hlstyl);
571 lanno.add_element(lxe);
572
573 // Add y-axis tic numbers along line for use when magnified.
574 if (gstate.is_visible(select::alt))
575 {
576 // Skip first and last as covered by either Y-axes tic marks.
577 for (double x = xmin + xdelta; x < maxx - xdelta; x += xdelta)
578 {
579 const double xto = chartxo + (x * gxscale);
580 const string syui = std::to_string(static_cast<uint>(y)) + gstate.yticu;
581 styled_text(lanno, syui, {xto, yto}, anntypo);
582 }
583 }
584 }
585
587 }
588
589 // Generate tic marks
590 const double ygo = gstate.ymargin + gheight + graph_rstate::th1sz;
591 if (gstate.is_visible(select::ticks))
592 {
593 // Base typo for tic labels.
594 // NB: Assume pointsx/pointsy are monotonically increasing.
597
598 // X tic labels
599 lanno.add_raw(group_element::start_group("tic-x-labels"));
600 for (uint i = 0; i < points.size(); i++)
601 {
602 const double xto = std::get<0>(cpoints[i]);
603 const double x = std::get<0>(points[i]);
604
605 // Make sure data point range within domain min and max.
606 if (x <= xmax)
607 {
608 ostringstream oss;
609#if MOZ
610 oss << fixed << setprecision(gstate.xticdigits) << x;
611#else
612 oss << std::trunc(x);
613#endif
614 const string sxui = oss.str() + gstate.xticu;
615 styled_text(lanno, sxui, {xto, ygo}, anntypo);
616 }
617 }
619
620 // Y tic labels
621 // Positions for left and right y-axis tic labels.
622 lanno.add_raw(group_element::start_group("tic-y-labels"));
623 const double yticspacer = graph_rstate::th1sz * 2;
624 const double xgol = gstate.xmargin - yticspacer; // left
625 const double xgor = gstate.xmargin + gwidth + yticspacer; // right
626 const double starty = ymin != 0 ? ymin : ymin + ydelta; // skip zero label
627 for (double y = starty; y < maxy + ydelta; y += ydelta)
628 {
629 const double yto = chartyo - (y * gyscale);
630 const string syui = std::to_string(static_cast<uint>(y)) + gstate.yticu;
631 styled_text(lanno, syui, {xgol, yto}, anntypo);
632 styled_text(lanno, syui, {xgor, yto}, anntypo);
633 }
635 }
636
638 return lanno;
639}
640
641
642/// Returns a svg_element with the rendered line graph (char).
643/// Assumptions:
644/// vgrange x axis is monotonically increasing
645///
646/// NB1: Axes and labels drawn in a separate pass (make_line_graph_annotations).
647/// NB2: Output file of x-axis point values for image tooltips if strategy = 3.
648///
649/// @param aplate = total size of graph area
650/// @param points = vector of {x,y} points to graph
651/// @param gstate = graph render state
652/// @param xrange = unified x-axis range for all graphs if multiplot
653/// @param yrange = unified y-axis range for all graphs if multiplot
654/// @param metadata = image filename prefix for tooltips if present
655svg_element
656make_line_graph(const vrange& points, const graph_rstate& gstate,
657 const point_2t xrange, const point_2t yrange,
658 const double marker_radius = 3.0)
659{
660 using namespace std;
661 const vrange cpoints = transform_to_graph_points(points, gstate,
662 xrange, yrange);
663
664 // Plot path of points on cartesian plane.
665 const string gname = gstate.title + "_line_graph";
666 svg_element lgraph(gname, "line graph", gstate.graph_area, false);
667 if (gstate.is_visible(select::vector))
668 {
669 if (gstate.mode == chart_line_style_1)
670 {
671 // Use polyline and CSS-based marker_form all in one line on layer 1.
672 lgraph.add_raw(group_element::start_group("polyline-" + gstate.title));
673 polyline_element pl1 = make_polyline(cpoints, gstate.lstyle, gstate.sstyle);
674 lgraph.add_element(pl1);
676 }
677 if (gstate.mode == chart_line_style_2)
678 {
679 // Use polyline base line on layer 1.
680 // Use set of marker points paths with value as text tooltips on layer 2.
681 lgraph.add_raw(group_element::start_group("polyline-" + gstate.title));
682 stroke_style no_markerstyle = gstate.sstyle;
683 no_markerstyle.marker_defs = "";
684 polyline_element pl1 = make_polyline(cpoints, gstate.lstyle, no_markerstyle);
685 lgraph.add_element(pl1);
687
688 // Markers + text tooltips.
689 lgraph.add_raw(group_element::start_group("markers-" + gstate.title));
690 string markers = make_line_graph_markers(points, cpoints, gstate, marker_radius);
691 lgraph.add_raw(markers);
693 }
694 if (gstate.mode == chart_line_style_3)
695 {
696 string m("make_line_graph:: ");
697 m += "chart_line_style_3 requires use of different overloaded function";
698 m += k::newline;
699 throw std::runtime_error(m);
700 }
701 }
702
703 return lgraph;
704}
705
706
707/// Line graph 3 needs more parameters.
708svg_element
709make_line_graph(const vrange& points, const vrange& tpoints, graph_rstate& gstate,
710 const point_2t xrange, const point_2t yrange,
711 const string metadata, script_element::scope scontext)
712{
713 using namespace std;
714 const vrange cpoints = transform_to_graph_points(points, gstate,
715 xrange, yrange);
716
717 // Plot path of points on cartesian plane.
718 const string gname = gstate.title + "_line_graph";
719 svg_element lgraph(gname, "line graph", gstate.graph_area, false);
720 if (gstate.is_visible(select::vector))
721 {
722 if (gstate.mode == chart_line_style_3)
723 {
724 // Use polyline base line on layer 1 of control points (subset points).
725 // Use set of control points marker paths with value as text tooltips on layer 2.
726 // Use set of image points (subset control points) image elements on layer 3.
727 lgraph.add_raw(group_element::start_group("polyline-" + gstate.title));
728 stroke_style no_markerstyle = gstate.sstyle;
729 no_markerstyle.marker_defs = "";
730 polyline_element pl1 = make_polyline(cpoints, gstate.lstyle, no_markerstyle);
731 lgraph.add_element(pl1);
733
734 // Markers + text tooltips, add image id + js to make image visible.
735 // Use simplified points, aka only the visual change points.
736 const vrange& ctpoints = transform_to_graph_points(tpoints, gstate,
737 xrange, yrange);
738 lgraph.add_raw(group_element::start_group("markers-" + gstate.title));
739 string markers = make_line_graph_markers(tpoints, ctpoints, gstate, 3,
740 gstate.tooltip_id);
741 lgraph.add_raw(markers);
743
744 // Add tool images to graph_rstate.
745 // Add this plus script at the same layer of the DOM, which varies.
746 const string imgprefix = metadata + k::hyphen + gstate.title + "_";
747 group_element ttips = make_line_graph_images(tpoints, gstate, imgprefix);
748 if (scontext == script_element::scope::element)
749 {
750 lgraph.add_element(ttips);
751 lgraph.add_raw(script_element::tooltip_script(scontext).str());
752 }
753 else
754 gstate.tooltip_images = ttips.str();
755 }
756 }
757
758 return lgraph;
759}
760
761} // namepace svg
762
763#endif
static const script_element tooltip_script(const scope context)
void start_element()
For groups of elements that have the same name.
static string finish_group()
static const string tooltip_attribute(const string &id)
static string start_group(const string name)
void add_data(const data &d)
Either serialize immediately (as below), or create data structure that adds data to data_vec and then...
void add_raw(const string &raw)
scope
Where is the script element placed? On/within the element itself, or at the document (global)?...
@ element
Scripts scoped to element.
string str() const
void add_element(const element_base &e)
string make_transform_attribute(const string s)
Common transforms include rotate(180)
void start_element(const string &id)
Abstract base class for all SVG Elements.
line_element make_line(const point_2t origin, const point_2t end, style s, const string dasharray="")
Line primitive.
group_element make_line_graph_images(const vrange &points, const graph_rstate &gstate, const string imgprefix, const string imgpath="../filmstrip/", const string imgext=".webp")
Return set of images for image tooltips, one for each point.
circle_element make_circle_marker(const point_2t origin, const style s, const space_type r, const string title, const string xform="", const string imgid="")
Make circle element with title and tooltip information.
string make_marker_instance(const marker_shape form, const point_2t &cpoint, const style styl, const double radius, const string tipstr="", const string imgid="")
Make marker or composite markers for one marker location.
@ pt
Point where 1 pixel x 1/72 dpi x 96 PPI = .26 mm.
unsigned short ushort
Base integer type: positive and negative, signed integral value.
Definition a60-svg.h:57
path_element make_path_center_mark(const point_2t &origin, const style styl, const int len, const int width, const string xform="", const string tipstr="")
Plus or x tilt mark as closed path that can be filled.
void styled_text(element_base &obj, const string text, const point_2t origin, const typography typo, const string xform="")
Text at.
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 string to_string(const unit e)
group_element make_sunburst(const point_2t origin, const style s, const space_type r=4, const uint nrays=10, const string tipstr="")
Rectangles of various sizes rotated from center point (x,y).
double space_type
Base floating point type.
Definition a60-svg.h:63
vrange find_tooltip_points(const vrange &points, const vspace &onlypoints)
Tramsform change points to points where the x-axis (time) matches a value in onlypoints.
graph_mode
Polyline/line options.
@ chart_line_style_1
One Element, one path plus css dasharray and markers.
@ chart_line_style_3
Three Element, one path, one marker, image.
@ chart_line_style_2
Two Element, one path, one marker.
rect_element make_rect_marker(const point_2t origin, const style s, const space_type r, const string title, const string filterstr="", const string imgid="")
Create rectangle element with title and tooltip information.
group_element make_octahedron(const point_2t, const style &, const double radius, const string tipstr="")
Make octahedron shape (8) in 2D simulated 3D @ret group element of polygon_elements.
marker_shape
Marker shape.
@ lauburu
Lauburu Curve.
@ octahedron
Octahedron (8) 3D.
@ circle
Circle, Round.
@ icosahedron
Icosahedron.
@ blob
Organic Blob form.
path_element make_path_ripple(const point_2t origin, const style s, const double length, const double amplitude, const double decay, const int cycles=3, const string tipstr="")
Make waves.
path_element make_path_marker(const point_2t origin, const style s, const double r, const uint pointsn, const string title, const string xattr="")
Make a polygon marker for line graphs.
path_element make_path_blob(const point_2t origin, const style s, const double size, const int numCurves=5+std::rand() % 4, const string tipstr="")
Make blob shape.
std::vector< point_2t > vrange
Definition izzi-points.h:61
point_2t max_vrange(vspace &xpoints, vspace &ypoints, const uint pown)
For each dimension of vrnage, find min/max and return (xmax, ymax) NB: Assumes zero is min.
Definition izzi-points.h:95
path_element make_lauburu(const point_2t origin, const style s, const double size, double swirlFactor=0.5, int pointsPerSwirl=8, const string tipstr="")
Make lauburu.
svg_element make_line_graph_annotations(const vrange &points, const graph_rstate &gstate, const point_2t xrange, const point_2t yrange, const double xscale=1, const double yscale=1, const typography typo=k::apercu_typo)
Axis Labels Axis X/Y Ticmarks X line increments.
vrange transform_to_graph_points(const vrange &points, const graph_rstate &gstate, const point_2t xrange, const point_2t yrange)
Map data points to cartestian points on graph area.
string make_line_graph_markers(const vrange &points, const vrange &cpoints, const graph_rstate &gstate, const double radius, const string imgidbase="")
Return set of paths of marker shapes with text tooltips. NB: For graph_mode >= chart_line_style_2.
vrange find_change_points(const vrange &vr)
Simplify sorted vrange by removing interior duplicates.
@ alt
alternate use specified in situ
@ ticks
ticks, markers
@ vector
svg path, circle, rectangle, etc.
@ linex
horizontal lines
@ echo
b & w outline version of vector
vrange find_visual_change_points(const vrange &points)
Tramsform change points to points where the y-axis (% visual complete) changes.
svg_element make_line_graph(const vrange &points, const graph_rstate &gstate, const point_2t xrange, const point_2t yrange, const double marker_radius=3.0)
Returns a svg_element with the rendered line graph (char). Assumptions: vgrange x axis is monotonical...
std::vector< space_type > vspace
Split range, so one dimension of (x,y) cartesian plane.
Definition izzi-points.h:56
unsigned int uint
Definition a60-svg.h:58
void styled_text_r(element_base &obj, const string text, const point_2t origin, const typography typo, const double deg)
Text at.
group_element make_icosahedron(const point_2t origin, const style &s, const double radius, const string tipstr="")
Make icosahedron shape (20) in 2D simulated 3D.
space_type distance_cartesian(const point_2t &p1, const point_2t &p2)
Find cartesian distance between two 2D points.
polyline_element make_polyline(const vrange &points, const style s, const stroke_style sstyle={ })
Polyline primitive.
std::tuple< space_type, space_type > point_2t
Point (x,y) in 2D space, space_type defaults to double.
Definition izzi-points.h:22
Per-graph constants, metadata, configuration, text.
string title
Key data: title, area, mode.
static constexpr uint yticdigits
area< space_type > area_type
static constexpr uint xticdigits
chart_line_style_n to use
string tooltip_id
chart_line_style_3 tooltip size
static constexpr uint th1sz
area_type tooltip_area
marker stroke style, if any.
area_type graph_area
graph title
stroke_style sstyle
line style
graph_mode mode
graph area
static constexpr uint tticsz
static constexpr uint ymargin
static constexpr uint xmargin
1920 x 1080 Margins/Spaces
static constexpr uint ttitlesz
Type sizes.
static constexpr uint tpsz
style lstyle
Line/Outline/Markers/Tooltip styles.
string tooltip_images
chart_line_style_3 toolip id prefix
render_state_base(const select m=select::none)
bool is_visible(const select v) const
Additional path/line/polyline stroke styles. NB: https://yuanchuan.dev/fun-with-stroke-dasharray.
ushort marker_reps
Marker repetitions, if any. Default is zero, no repitition, single marker.
marker_shape marker_form
Marker shapes. For graph_mode 2, this means the marker_shape of the mark.
string marker_defs
Marker string pointing to definitions elements. For graph_mode 1, this means the SVG equivalent of CS...
Datum consolidating style preferences.
color_qi _M_fill_color
color_qi _M_stroke_color
static string rotate(int deg)