From b743174a319038b372602510d22bba459363dcbd Mon Sep 17 00:00:00 2001 From: tim Date: Mon, 18 Dec 2017 20:15:15 +0200 Subject: [PATCH] Cleanups. --- glucometer_graphs.pl | 180 +++++++++++++++++++++++++++++++++---------- 1 file changed, 141 insertions(+), 39 deletions(-) diff --git a/glucometer_graphs.pl b/glucometer_graphs.pl index 821eff1..505a26c 100644 --- a/glucometer_graphs.pl +++ b/glucometer_graphs.pl @@ -5,39 +5,48 @@ # Author: Timothy Allen # License: MIT # +# TODO Convert to python (see # https://www.physicsforums.com/threads/gnuplot-how-to-find-the-area-under-a-curve-integrate.382070/ +# ) use strict; use warnings; use Getopt::Long; use IPC::Open3; use Time::Piece; +use Data::Dumper; my $error = "Usage: $0 --input --output [--max ] [--low ] [--high ]\n"; my @lines; -my %seen; +my @sorted_lines; my @data; my @avg_data; +my %intervals; +my %seen; my $a1c_calc; +my $page_size; my $gnuplot_data; my $total_graphs; my $count_graphs = 0; my $page_number = 0; +my $interval = 15; # The number of minutes to average points for the area range graph -my $input = ""; -my $output = ""; +my $input = ''; +my $output = ''; # set these values either in mmol/L or mg/dL (don't mix them) my $max_glucose = 8; my $min_glucose = 4; my $graph_max = 21; -my $units = 'mmol/L'; +my $units = ''; +my $page = 'a4'; my $days_per_page = 2; -GetOptions ("input=s" => \$input, # The name of the CSV file from which to read values - "output=s" => \$output, # The name of the PDF file to output - "high:f" => \$max_glucose, # The high end of your target blood glucose level - "low:f" => \$min_glucose, # The low end of your target blood glucose level - "max:i" => \$graph_max, # The highest displayed glucose level on each graph - "units:s" => \$units, # mmol/L or mg/dL - "graphs:i" => \$days_per_page) # The number of days printed on each page +GetOptions ("input=s" => \$input, # The name of the CSV file from which to read values + "output=s" => \$output, # The name of the PDF file to output + "high:f" => \$max_glucose, # The high end of your target blood glucose level + "low:f" => \$min_glucose, # The low end of your target blood glucose level + "max:i" => \$graph_max, # The highest displayed glucose level on each graph + "units:s" => \$units, # mmol/L or mg/dL + "pagesize:s" => \$page, # size of page to print + "graphs:i" => \$days_per_page) # The number of days printed on each page or die $error; open( my $ifh, '<:encoding(UTF-8)', $input ) @@ -51,9 +60,20 @@ while ( my $row = <$ifh> ) { close( $ifh ) or warn "close failed: $!"; +if ( $page =~ /a4/i ) { + $page_size = "29.7cm,21.0cm"; +} elsif ( $page =~ /letter/i ) { + $page_size = "11in,8.5in"; +} elsif ( $page =~ /\d+(cm|in),\d+/ ) { + $page_size = $page; +} else { + # A4 size default + $page_size = "29.7cm,21.0cm"; +} + # Set up basic gnuplot options for reading the CSV data push @data, qq( -set terminal pdf size 29.7cm,21.0cm enhanced font 'Calibri,14' linewidth 1 +set terminal pdf size $page_size enhanced font 'Calibri,14' linewidth 1 #set output '$output' ); @@ -144,13 +164,6 @@ foreach my $d ( sort keys %seen ) { $count_graphs++; - # Algorithms for calculating HbA1c - if ( $units =~ /mg/i ) { - $a1c_calc = "(AVG + 46.7) / 28.7"; - } else { # Assume mmol/L - $a1c_calc = "(AVG + 2.59) / 1.59"; - } - push @data, qq( set title "Daily Glucose Summary for $title" font "Calibri,18" set xlabel "Time" offset 0,-0.25 @@ -168,11 +181,6 @@ AVG_LABEL = gprintf("Average glucose: %.2f", AVG) set object 2 rect at graph 0.9, graph 0.9 fc ls 2 fs transparent solid 0.5 front size char strlen(AVG_LABEL), char 3 set label 2 AVG_LABEL at graph 0.9, graph 0.9 front center -#A1C = $a1c_calc -#A1C_LABEL = gprintf("Average A1c: %.1f", A1C) -#set object 3 rect at graph 0.07, graph 0.9 fc ls 5 fs transparent solid 0.5 front size char strlen(A1C_LABEL), char 3 -#set label 3 A1C_LABEL at graph 0.07, graph 0.9 front center - #plot \$SmoothData$label using 1:2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : $count_graphs ) with linespoints ls 120 lc variable plot \$SmoothData$label using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable @@ -200,18 +208,66 @@ plot 1/0 } # Output data averages by hour of the day +@sorted_lines = (); push @data, qq( \$DataAvg << EOD "timestamp","blood glucose","meal","method","comment"); -my @sorted_rows; foreach my $row (@lines) { if ( $row =~ s#^"\d{4}-\d{2}-\d{2} #"#ms ) { - push @sorted_rows, $row; + push @sorted_lines, $row; } } -push @data, sort @sorted_rows; +push @data, sort @sorted_lines; push @data, qq(EOD); +# Output min/max for each time interval +foreach my $row ( @sorted_lines ) { + $row =~ s/"//g; + my ( $time, $value ) = split /,/, $row; + my ( $hour, $minute, $second ) = split /:/, $time; + $time = sprintf( "%02d:%02d:00", $hour, int($minute/$interval)*$interval ); + + # Override the current minimum values for this interval if it + # exists; otherwise, set it + if ( exists ( $intervals{$time}{min} ) ) { + if ( $intervals{$time}{min} < $value ) { + $intervals{$time}{min} = $value; + } + } else { + $intervals{$time}{min} = $value; + } + # Override the current maximum values for this interval if it + # exists; otherwise, set it + if ( exists ( $intervals{$time}{max} ) ) { + if ( $intervals{$time}{max} > $value ) { + $intervals{$time}{max} = $value; + } + } else { + $intervals{$time}{max} = $value; + } + +} +$Data::Dumper::Sortkeys = 1; +#die Dumper(\%intervals); + +push @data, qq( +\$DataMaxMin << EOD +"timestamp","max","min"); +foreach my $time ( sort keys %intervals ) { + warn $time; + push @data, qq("$time","$intervals{$time}{max}","$intervals{$time}{min}"); +} +push @data, qq(EOD); + +# Standardise units for gnuplot's A1C calculations +if ( $units =~ /mg/i ) { + $units = 'mg/dL'; +} elsif ( $units =~ /mmol/i ) { + $units = 'mmol/L'; +} else { + $units = ''; +} + push @data, qq( reset set datafile separator "," @@ -225,16 +281,41 @@ stats \$DataAvg using 2 MeanTotal = STATS_mean set xdata time + +set table \$DataAvgTable + +#avg(x) = g +#min(x) = xg +#f(x) = g +#fit f(x) \$DataAvg using 1:(\$2>MeanTotal?\$2:'') via t, g +#plot f(x) smooth mcsplines + +#plot \$DataAvg using 1:(\$2>MeanTotal?\$2:'') every $count_graphs/2 lc 2, \$DataAvg using 1:(\$2 4 ? 5 : (\$0+1) +#avg5(x) = (shift5(x), (back1+back2+back3+back4+back5)/samples(\$0)) +#shift5(x) = (back5 = back4, back4 = back3, back3 = back2, back2 = back1, back1 = x) +## Initialize a running sum +#init(x) = (back1 = back2 = back3 = back4 = back5 = sum = 0) + +#plot sum = init(0), \$DataAvg using 1:2 title 'data' lw 2 lc rgb 'forest-green', '' using 1:(avg5(\$2)) pt 7 ps 0.5 lw 1 lc rgb "blue" + +plot \$DataAvg using 1:2 smooth mcsplines + +unset table + set table \$SmoothDataAvg plot \$DataAvg using 1:2 smooth bezier unset table -set table \$TableDataAvg -plot \$DataAvg using 1:2 smooth mcsplines -unset table - undefine \$DataAvg +# Convert DataMaxMin from CSV to table +set table \$DataMaxMinTable +plot \$DataMaxMin using 1:2:3 with table +unset table + reset set datafile separator whitespace @@ -252,6 +333,8 @@ set xrange ["00:00":"23:58"] set style line 100 dt 3 lw 1 lc rgb "#202020" set style line 101 dt 1 lw 1 lc rgb "#202020" set linetype 110 lc rgb "red" +set linetype 111 lc rgb "#B0B0B0" +set style fill transparent solid 0.5 noborder set lmargin 12 set rmargin 10 @@ -267,24 +350,43 @@ set xtics left tc rgb "#000000" set ytics 2 tc rgb "#000000" set grid ytics ls 100 front -set object 1 rect from graph 0, first $min_glucose to graph 1,first $max_glucose fc ls 6 fs solid 0.2 back +set object 1 rect from graph 0, first $min_glucose to graph 1,first $max_glucose fc ls 6 fs solid 0.05 back AVG = MeanTotal AVG_LABEL = gprintf("Average glucose: %.2f", AVG) set object 2 rect at graph 0.9, graph 0.9 fc ls 2 fs transparent solid 0.5 front size char strlen(AVG_LABEL), char 3 set label 2 AVG_LABEL at graph 0.9, graph 0.9 front center -A1C = $a1c_calc +A1C = 0 +if (A1C == 0 && '$units' eq 'mg/dL') { + A1C = (MeanTotal + 46.7) / 28.7 +} +if (A1C == 0 && '$units' eq 'mmol/L') { + A1C = (MeanTotal + 2.59) / 1.59 +} +# mg/dL numbers tend to be higher than 35 +if (A1C == 0 && MeanTotal >= 35) { + A1C = (MeanTotal + 46.7) / 28.7 +} +# mmol/L numbers tend to be lower than 35 +if (A1C == 0 && MeanTotal < 35) { + A1C = (MeanTotal + 2.59) / 1.59 +} + A1C_LABEL = gprintf("Average A1c: %.1f", A1C) -set object 3 rect at graph 0.07, graph 0.9 fc ls 5 fs transparent solid 0.5 front size char strlen(A1C_LABEL), char 3 +set object 3 rect at graph 0.07, graph 0.9 fc ls 4 fs transparent solid 0.5 front size char strlen(A1C_LABEL), char 3 set label 3 A1C_LABEL at graph 0.07, graph 0.9 front center -plot \$SmoothDataAvg using ( strftime("%H:%M:%S", \$1) ):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable -#plot \$TableDataAvg using (strftime("%H:%M:%S", \$1)):2 with lines lw 1 lc 5, \$SmoothDataAvg using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable +#plot \$SmoothDataAvg using ( strftime("%H:%M:%S", \$1) ):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable +#plot \$DataAvgTable using (strftime("%H:%M:%S", \$1)):2 with points lc 5 ps 0.5 pt 37, \$SmoothDataAvg using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable + +plot \$DataMaxMinTable using (strftime("%H:%M:%S", \$1)):2:3 with filledcurves lc 111, \$SmoothDataAvg using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable undefine \$DataAvg +undefine \$DataMaxMin +undefine \$DataMaxMinTable undefine \$SmoothDataAvg -undefine \$TableDataAvg +undefine \$DataAvgTable # Add an x grid set multiplot previous @@ -302,7 +404,7 @@ plot 1/0 push @data, qq( unset multiplot -#test +test ); # run the data through gnuplot @@ -342,4 +444,4 @@ close( $ofh ) #print GNUPLOT $gnuplot_data; #close(GNUPLOT); -# vim: set expandtab shiftwidth=4 softtabstop=4 : +# vim : set expandtab shiftwidth=4 softtabstop=4 tw=1000 :