diff --git a/src/lib/gnuplot-gen.cpp b/src/lib/gnuplot-gen.cpp index cc0f6e642..9a470e1b5 100644 --- a/src/lib/gnuplot-gen.cpp +++ b/src/lib/gnuplot-gen.cpp @@ -30,7 +30,42 @@ ** without much structure or preconceptions and written in a way to encourage adding ** more use-cases by copy-and-paste. Following this pragmatic approach, generalisations ** and common schemes will emerge eventually. - ** @todo WIP-WIP 4/2024 first usage as part of Scheduler stress testing. + ** + ** # Script generation + ** The resulting Gnuplot script is combined from several building blocks, and + ** passed through the lib::TextTemplate engine to substitute the data and further + ** configuration parameters at designated places into the code. Notably, the script + ** performs further evaluations at run time, especially in reaction to the number + ** of given data rows (relying on the `stats` command of Gnuplot). Data input is + ** configured to CSV format and data is pasted as »here document« into a + ** _data block variable_ `$RunData` + ** + ** \par simple plot + ** By default, the `points` plotting style is used; this can be overridden + ** through the placeholder #KEY_DiagramKind. All given data rows are combined + ** into a single plot, using a common x and y axis, and assigning consecutive + ** line drawing styles to each data row (from blue to green, yellow, red). + ** A maximum of 9 line styles is prepared for drawing. The x/y-ranges and + ** custom labels can be added. + ** + ** \par scatter plot with regression line + ** Typically this is used for measurement data, assuming linear relation. + ** The data is visualised as (x,y) points, overlaying a linear regression line + ** plotted in red. Possibly further data rows can be given, which are then + ** plotted into a secondary diagram, arranged below the main measurement data + ** and using the same x-axis layout. This additional display is featured only + ** when more than 2 columns are present in the data; the number of columns is + ** picked up from the _second data row_ (since the first row provides the + ** key names used for the legend). In further rows, some data points can be + ** omitted. The 3rd data column will be shown as »impulse diagram«, while all + ** further data columns will be displayed as points, using the _secondary_ + ** Y-axis. All axis ranges and labels can be customised. By default, the + ** **linear regression model** is calculated by Gnuplot, based on the + ** data in columns 1 and 2 (the main measurement data), yet an + ** alternative regression line can be defined by parameters. + ** + ** @todo WIP 4/2024 first usage as part of Scheduler stress testing. + ** @see GnuplotGen_test */ @@ -83,9 +118,12 @@ set arrow 11 from graph 0,0 to graph 0,1.08 size screen 0.025,15,60 filled ls 10 # GNUPLOT - data plot from Lumiera # -${if Term}set term ${Term} ${ -if TermSizeSpec}size ${TermSizeSpec}${endif}${ -endif Term} +${if Term +}set term ${Term} ${ +if TermSize}size ${TermSize}${endif} +${else}${if TermSize +}set term wxt size ${TermSize} +${endif}${endif Term} set datafile separator ",;" @@ -98,19 +136,20 @@ _End_of_Data_ ${CommonStyleDef} ${AxisGridSetup} -${if XLabel -}set xlabel '${XLabel}' +${if Xlabel +}set xlabel '${Xlabel}' ${else }stats $RunData using (abscissaName=strcol(1)) every ::0::0 nooutput set xlabel abscissaName -${end if XLabel -}${if YLabel -}set ylabel '${YLabel}' ${end if YLabel +${end if Xlabel +}${if Ylabel +}set ylabel '${Ylabel}' ${end if Ylabel } -${if Yrange} -set yrange [${Yrange}] -${endif +${if Xrange} +set xrange [${Xrange}] ${endif +}${if Yrange} +set yrange [${Yrange}] ${endif } set key autotitle columnheader tmargin @@ -124,17 +163,23 @@ plot for [i=2:*] $RunData using 1:i with ${DiagramKind} linestyle i-1 const string GNUPLOT_SCATTER_REGRESSION = R"~(# -# -####---------Scatter-Regression-Plot------------- -# stats $RunData using 1:2 nooutput -# draw regression line as arrow +${if RegrSlope +}# regression line function (given as parameter) +regLine(x) = ${RegrSlope} * x + ${RegrSocket} +${else +}# regression line function derived from data regLine(x) = STATS_slope * x + STATS_intercept -set arrow 1 from graph 0, first regLine(STATS_min_x) \ - to graph 1, first regLine(STATS_max_x) \ - nohead linestyle 9 - +${end if +} +${if Xtics +}set xtics ${Xtics} +${else}${if Xrange}${else +}set xrange [0:*] +set xtics 1 +${end if}${end if Xtics +} plots = STATS_columns - 1 # Adjust layout based on number of data sequences; # additional sequences placed into secondary diagram @@ -144,21 +189,22 @@ if (plots > 1) { set lmargin at screen 0.12 # fixed margins to align diagrams set rmargin at screen 0.88 } -####------------------------------- -plot $RunData using 1:2 with points linestyle 1 +# +# +####---------Scatter-Regression-Plot------------- +plot $RunData using 1:2 with points linestyle 1, \ + regLine(x) with line linestyle 9 if (plots > 1) { # switch off decorations for secondary diagram - unset arrow 1 unset arrow 10 unset arrow 11 set border 2+8 set key bmargin - ${if Y2range} set yrange [${Y2range}] -${endif -} unset xlabel +${endif} + unset xlabel set format x "" ${if Y2label } set ylabel '${Y2label}' ${endif @@ -171,11 +217,13 @@ ${if Y2label # more than one additional data sequence # ${if Y3range -} set y2range [${Y3range}] ${endif +} set y2range [${Y3range}] + +${endif } set y2tics ${if Y3label -} set y2label '${Y3label}' offset -1 ${endif -} +} set y2label '${Y3label}' offset -1.5 +${endif} ####--------------------------------------------- plot $RunData using 1:3 with impulses linestyle 3, \ for [i=4:*] $RunData using 1:i with points linestyle 5+(i-4) axes x1y2 @@ -188,6 +236,8 @@ ${if Y3label + + /** * @remark each column of the given data is featured as sequence * over the first column interpreted as common abscissa. The name @@ -208,6 +258,16 @@ ${if Y3label } + /** + * @remark the layout of this diagram differs, based on the given + * number of data columns. The main measurement data is expected in + * columns `[1:2]` and showed in the primary display, adding a + * regression line. When further columns are given, a `multiplot` + * layout is established, showing those additional related series + * in a second diagram below. It may be necessary to define a larger + * canvas with different aspect ratio, which is possibly using the + * placeholder #KEY_TermSize + */ string scatterRegression (ParamRecord params) { diff --git a/src/lib/gnuplot-gen.hpp b/src/lib/gnuplot-gen.hpp index 2c8055f65..a30019473 100644 --- a/src/lib/gnuplot-gen.hpp +++ b/src/lib/gnuplot-gen.hpp @@ -25,17 +25,19 @@ ** The visualisation tool [gnuplot] allows for simple data visualisation ** in various formats, integrated into a *NIX commandline work environment. ** - ** The namespace lib::gnuplot_gen provides .... + ** The namespace lib::gnuplot_gen allows to generate diagrams relying on + ** some common layout schemes, which can be customised. Data is passed in + ** as CSV string; the generated Gnuplot script adapts dynamically to the + ** number of data columns given, where the first column always holds + ** the common x-axis values. Additional parameters can be added to + ** the _data binding_ used for script generation; this binding + ** is comprised of key = value settings in a `Rec` + ** (Lumiera's »ETD« format for structural data) ** ** @todo 3/2024 this is an initial draft, shaped by the immediate need to visualise ** [measurement data](\ref vault::gear::test::SchedulerStress_test) collected ** while testing the new [Scheduler](\ref scheduler.hpp) implementation. ** - ** ## Usage - ** TBW - ** - blah - ** - blubb - ** ** @see GnuplotGen_test ** @see SchedulerStress_test ** @see text-template.hpp @@ -67,13 +69,21 @@ namespace gnuplot_gen { ///< preconfigured setup for Gnuplot data visualisation const string KEY_CSVData = "CSVData"; const string KEY_DiagramKind = "DiagramKind"; + const string KEY_Term = "Term"; + const string KEY_TermSize = "TermSize"; + + const string KEY_Xtics = "Xtics"; + const string KEY_Xrange = "Xrange"; const string KEY_Yrange = "Yrange"; const string KEY_Y2range = "Y2range"; const string KEY_Y3range = "Y3range"; const string KEY_Xlabel = "Xlabel"; const string KEY_Ylabel = "Ylabel"; const string KEY_Y2label = "Y2label"; - const string KEY_Y3label = "Y2label"; + const string KEY_Y3label = "Y3label"; + + const string KEY_RegrSocket = "RegrSocket"; + const string KEY_RegrSlope = "RegrSlope"; diff --git a/tests/15library.tests b/tests/15library.tests index afdeba960..f0c471116 100644 --- a/tests/15library.tests +++ b/tests/15library.tests @@ -414,7 +414,7 @@ return: 0 END -PLANNED "generate a Gnuplot diagram" GnuplotGen_test < using multiplot layout CHECK (contains (gnuplot, "set multiplot")); @@ -144,49 +133,54 @@ namespace test{ } - /** @test TODO - * @todo WIP 4/24 🔁 define ⟶ implement + /** @test various customisations through additional parameters + * - a custom defined regression line + * - use a specific output »term« and specify canvas size + * - define the common horizontal data range and x-tic spacing + * - define display ranges for 3 different Y-axis + * - define custom labels for all axes + * @note when using additional parameters, csv data + * must also be given explicitly as `KEY_CSVData` */ void - verify_keySubstituton() - { - UNIMPLEMENTED ("nebbich"); - } - - - /** @test TODO - * @todo WIP 4/24 🔁 define ⟶ implement - */ - void - verify_conditional() - { - } - - - /** @test TODO - * @todo WIP 4/24 🔁 define ⟶ implement - */ - void - verify_iteration() - { - } - - - /** @test TODO - * @todo WIP 4/24 🔁 define ⟶ implement - */ - void - verify_Map_binding() - { - } - - - /** @test TODO - * @todo WIP 4/24 🔁 define ⟶ implement - */ - void - verify_ETD_binding() + verify_customisation() { + string csv = + CSVData{{"abscissa","points","e1","e2","e3"} + ,{{1,1 , 1.1,"" ,210} + ,{2,2 , 1.2,150,220} + ,{3,5 , 5.5,140} + }}; + using namespace gnuplot_gen; + string gnuplot = scatterRegression( + ParamRecord() + .set(KEY_CSVData , csv) + .set(KEY_RegrSocket, 3) + .set(KEY_RegrSlope, -1.5) + .set(KEY_Xtics , 2) + .set(KEY_Xrange , "-1:5.5") + .set(KEY_Yrange , "0:6") + .set(KEY_Y2range, "1.1:1.5") + .set(KEY_Y3range, "100:*") + .set(KEY_Xlabel , "common axis") + .set(KEY_Ylabel , "measurement") + .set(KEY_Y2label, "auxiliary-1") + .set(KEY_Y3label, "auxiliary-2") + .set(KEY_TermSize, "500,800") + ); +// cout << gnuplot < - - - + + + - + @@ -112406,8 +112406,25 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + + + + + + + + + +

+ ...was nicht möglich ist, sofern man Gnuplot irgendwie den xrange automatisch bestimmen läßt (dann ist der xrange nämlich erst nach  dem Plotten bekannt) +

+ +
+
+
@@ -114334,8 +114351,8 @@ std::cout << tmpl.render({"what", "World"}) << s
- - + + @@ -114352,7 +114369,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -114377,10 +114394,10 @@ std::cout << tmpl.render({"what", "World"}) << s - - - - + + + + @@ -114449,8 +114466,8 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + @@ -114550,8 +114567,8 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + @@ -114562,9 +114579,15 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + + + + + + + @@ -114590,9 +114613,9 @@ std::cout << tmpl.render({"what", "World"}) << s - - - + + + @@ -114610,23 +114633,20 @@ std::cout << tmpl.render({"what", "World"}) << s - + - - - +

man kann hier eigentlich nur stichprobenartig verifizieren, daß das jeweilige Template zum Einsatz kam, und daß einige markante Werte per Text-Templating eingebaut wurden. Also z.B. die Datenheader, oder eine Achsenbeschriftung.

- -
+
@@ -114639,7 +114659,26 @@ std::cout << tmpl.render({"what", "World"}) << s
+ + + + + + + + + + + + + + + + + + +