From c997fc23410dbe51bec8a7837945020748611b67 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 2 Apr 2024 23:59:59 +0200 Subject: [PATCH] Library: develop Gnuplot code for flexible scatter-regression The idea is to build the Layout-branching into the generated Gnuplot script, based on the number of data columns detected. If there is at least one further data column, then the "mulitplot" layout will be used to feature this additional data in a secondary diagram below with aligned axis; if more than one additional data column is present, all further visualisation will draw points, using the secondary Y-axis Moreover, Gnuplot can calculate the linear regresssion line itself, and the drawing will then be done using an `arrow` command, defining a function regLine(x) based on the linear model. --- src/lib/gnuplot-gen.cpp | 88 +++++++++++++++++++++------- src/lib/gnuplot-gen.hpp | 27 ++++++++- tests/library/gnuplot-gen-test.cpp | 57 +++++++++++++----- wiki/thinkPad.ichthyo.mm | 92 ++++++++++++++++++++++++++++-- 4 files changed, 221 insertions(+), 43 deletions(-) diff --git a/src/lib/gnuplot-gen.cpp b/src/lib/gnuplot-gen.cpp index 53f0d3866..ce88e5ce8 100644 --- a/src/lib/gnuplot-gen.cpp +++ b/src/lib/gnuplot-gen.cpp @@ -106,8 +106,11 @@ ${else set xlabel abscissaName ${end if XLabel }${if YLabel -}set ylabel '${YLabel}' -${end if YLabel +}set ylabel '${YLabel}' ${end if YLabel +} +${if Yrange} +set yrange [${Yrange}] +${endif } set key autotitle columnheader tmargin @@ -122,34 +125,64 @@ 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 -set arrow 1 from graph 0, first 1 to graph 1, first 30 nohead ls 9 - -set multiplot layout 2,1 -set lmargin at screen 0.12 -set rmargin at screen 0.88 +# draw regression line as arrow +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 +plots = STATS_columns - 1 +# Adjust layout based on number of data sequences; +# additional sequences placed into secondary diagram +# +if (plots > 1) { + set multiplot layout 2,1 # 2 rows 1 column + 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 -unset arrow 1 -unset arrow 10 -unset arrow 11 -set border 2+8 -set yrange [0:8] -set y2range [500:2000] - -unset x2label -set format x "" -set ylabel "Y1 axis" -set y2label "Y2 axis" offset -2 -set y2tics -plot $RunData using 1:3 with impulses linestyle 3, \ - $RunData using 1:4 with points linestyle 5 axes x1y2 +if (plots > 1) { + # switch off decorations for secondary diagram + unset arrow 1 + unset arrow 10 + unset arrow 11 + set border 2+8 +${if Y2range} + set yrange [${Y2range}] +${endif +} unset x2label + set format x "" +${if Y2label +} set ylabel '${Y2label}' ${endif +} + if (plots <= 2) { + ####--------------------------------- + plot $RunData using 1:3 with impulses linestyle 3 + } else { + # more than one additional data sequence + # +${if Y3range +} set y2range [${Y3range}] ${endif +} set y2tics +${if Y3label +} set y2label '${Y3label}' offset -1 ${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 + } +} )~"; + }//(End)template and defaults definitions @@ -173,4 +206,17 @@ plot $RunData using 1:3 with impulses linestyle 3, \ return plot.render (params.genNode()); } + + string + scatterRegression (ParamRecord params) + { + TextTemplate plot{GNUPLOT_BASIC_PLOT_DEF + +GNUPLOT_SCATTER_REGRESSION}; + + params.set ("CommonStyleDef", GNUPLOT_CommonStyleDef) + .set ("AxisGridSetup", GNUPLOT_AxisGridSetup) + ; + return plot.render (params.genNode()); + } + }} // namespace lib::gnuplot_gen diff --git a/src/lib/gnuplot-gen.hpp b/src/lib/gnuplot-gen.hpp index ad29cb874..2c8055f65 100644 --- a/src/lib/gnuplot-gen.hpp +++ b/src/lib/gnuplot-gen.hpp @@ -67,13 +67,38 @@ namespace gnuplot_gen { ///< preconfigured setup for Gnuplot data visualisation const string KEY_CSVData = "CSVData"; const string KEY_DiagramKind = "DiagramKind"; + 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"; + /** * Generate a Gnuplot diagram to visualise the given data points. */ string dataPlot (ParamRecord); - string dataPlot (string csvData) { return dataPlot (ParamRecord().set (KEY_CSVData, csvData)); } + + inline string + dataPlot (string csvData) + { + return dataPlot (ParamRecord().set (KEY_CSVData, csvData)); + } + + + /** + * Generate a (X,Y)-scatter plot with regression line + */ + string scatterRegression (ParamRecord); + + inline string + scatterRegression (string csvData) + { + return scatterRegression (ParamRecord().set (KEY_CSVData, csvData)); + } }} // namespace lib::gnuplot_gen diff --git a/tests/library/gnuplot-gen-test.cpp b/tests/library/gnuplot-gen-test.cpp index 3015e9132..b48f8c9fc 100644 --- a/tests/library/gnuplot-gen-test.cpp +++ b/tests/library/gnuplot-gen-test.cpp @@ -55,7 +55,7 @@ namespace test{ run (Arg) { simpeUsage(); - verify_instantiation(); + plot_scatter_regression(); verify_keySubstituton(); verify_conditional(); verify_iteration(); @@ -71,17 +71,19 @@ namespace test{ void simpeUsage() { - string gnuplot = gnuplot_gen::dataPlot (CSVData{{"step","fib"} - ,{{0,1} - ,{1,1} - ,{2,2} - ,{3,3} - ,{4,5} - ,{5,8} - ,{6,13} - ,{7,21.55} - }}); - cout << gnuplot < CHECK (contains (gnuplot, "set datafile separator \",;\"")); CHECK (contains (gnuplot, "\"step\",\"fib\"")); @@ -92,12 +94,37 @@ namespace test{ - /** @test TODO - * @todo WIP 4/24 🔁 define ⟶ implement + /** @test Create a (x,y) scatter plot with regression line + * - in the simple case, there is only one diagram + * - use the `stats` command to let Gnuplot calculate the linear regression + * - draw a regrsssion line using the `arrow` command + * and a function representing the linear regression model + * @todo WIP 4/24 🔁 define ⟶ ✔ implement */ void - verify_instantiation() + plot_scatter_regression() { + string gnuplot = gnuplot_gen::scatterRegression( + CSVData{{"step","fib"} + ,{{0,1} + ,{1,1} + ,{2,2} + ,{3,3} + ,{4,5} + ,{5,8} + ,{6,13} + ,{7,21.55} + }}); + cout << gnuplot < no multiplot layout + CHECK (not contains (gnuplot, "set multiplot")); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index efc01e3c6..d8afb7f25 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -112419,9 +112419,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + + @@ -114497,11 +114498,72 @@ std::cout << tmpl.render({"what", "World"}) << s - + + + + + + + + + + + + + + +

+ nicht Header-Zeile, also Zeile 2. +

+

+ Einzelne Datenpunkte können problemlos fehlen.... +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + @@ -123747,6 +123809,24 @@ unsigned int ThreadIdAsInt = *static_cast<unsigned int*>(static_cast<vo + + + + + + + + + + + + + + + + + +