diff --git a/glucometer_graphs.pl b/glucometer_graphs.pl index 7f12902..351c78a 100644 --- a/glucometer_graphs.pl +++ b/glucometer_graphs.pl @@ -24,13 +24,13 @@ my @sortedlines; my @data; my @avg_data; my $intervals; -my %seen_days; -my %seen_weeks; +my %seen_days; +my %seen_weeks; my $page_size; my $gnuplot_data; my $total_day_graphs; my $total_week_graphs; -my $count_graphs = 0; +my $count_graphs = 0; my $page_number = 0; my $interval = 15; # The number of minutes to average points for the area range graph @@ -40,6 +40,7 @@ my $output = ''; my $max_glucose = 8; my $min_glucose = 4; my $graph_max = 21; +my $noicons = 0; my $units = ''; my $page = 'a4'; my $graphs_per_page = 2; @@ -50,7 +51,8 @@ GetOptions ("input=s" => \$input, # The name of the CSV file from wh "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" => \$graphs_per_page) # The number of days printed on each page + "noicons" => \$noicons, # include icons (yes or no) + "graphs:i" => \$graphs_per_page)# The number of days printed on each page or die $error; @@ -67,14 +69,14 @@ sub calculate_max_min { my $date = Time::Piece->strptime( $key, $fmt ); my ( $hour, $minute ) = ( $date->hour, $date->min ); my $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 { + } else { $intervals{$time}{min} = $value; } # Override the current maximum values for this interval if it @@ -83,7 +85,7 @@ sub calculate_max_min { if ( $intervals{$time}{max} > $value ) { $intervals{$time}{max} = $value; } - } else { + } else { $intervals{$time}{max} = $value; } } @@ -92,9 +94,9 @@ sub calculate_max_min { -open( my $ifh, '<:encoding(UTF-8)', $input ) +open( my $ifh, '<:encoding(UTF-8)', $input ) or die "Could not open file '$input' $!"; - + while ( my $row = <$ifh> ) { chomp( $row ); # Clean up the comments @@ -106,6 +108,10 @@ while ( my $row = <$ifh> ) { # Parse CSV into whitespace-separated tokens to avoid conflicting separators $row =~ s#^"(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})","([\d\.]+)",.*,"(.*?)"$#$1T$2 $3 "$4"#; + + # Remove icons if not requested + $row =~ s# "[^"]+"## if ( $noicons ); + push @filelines, $row; } @@ -118,12 +124,12 @@ if ( $page =~ /a4/i ) { $page_size = "11in,8.5in"; } elsif ( $page =~ /\d+(cm|in),\d+/ ) { $page_size = $page; -} else { +} else { # A4 size default $page_size = "29.7cm,21.0cm"; } -# Standardise units for gnuplot's A1C calculations +# Standardise units for gnuplot's A1C calculations if ( $units =~ /mg/i ) { $units = 'mg/dL'; } elsif ( $units =~ /mmol/i ) { @@ -134,18 +140,18 @@ if ( $units =~ /mg/i ) { # Get the list of days for which to produce graphs foreach my $row ( @filelines ) { - if ( $row =~ m#^((\d{4})-(\d{2})-(\d{2}))#ms ) { - my ( $date,$year,$month,$day ) = ( $1, $2, $3, $4 ); + if ( $row =~ m#^((\d{4})-(\d{2})-(\d{2}))#ms ) { + my ( $date,$year,$month,$day ) = ( $1, $2, $3, $4 ); my $time = Time::Piece->strptime( $date, "%Y-%m-%d" ); my $week = $time->strftime("%W"); $seen_weeks{$year}{$week}++; - $seen_days{$date}++; + $seen_days{$date}++; } } # Remove weeks for which there is less than a day of results in that week # (In a full day, assuming a reading is taken every 15 minutes, there will be 96 readings) -foreach my $year ( sort keys %seen_weeks ) { - foreach my $week ( sort keys %{$seen_weeks{$year}} ) { +foreach my $year ( sort keys %seen_weeks ) { + foreach my $week ( sort keys %{$seen_weeks{$year}} ) { delete $seen_weeks{$year}{$week} if ( scalar $seen_weeks{$year}{$week} < 96 ); } } @@ -156,12 +162,12 @@ $intervals = calculate_max_min( 'lines' => \@filelines, 'format' => '%Y-%m-%dT%H # Set up basic gnuplot output options push @data, qq( -set terminal pdf size $page_size enhanced font 'Calibri,14' linewidth 1 +set terminal pdf size $page_size enhanced font 'Calibri,14' linewidth 1 #set output '$output' ); # Read each line into a $Data variable for use by gnuplot -foreach my $d ( sort keys %seen_days ) { +foreach my $d ( sort keys %seen_days ) { my $label = "$1$2$3" if ( $d =~ m#(\d{4})-(\d{2})-(\d{2})# ); push @data, qq(\$Data$label << EOD); @sortedlines = (); @@ -199,8 +205,8 @@ push @data, qq(EOD); # Output weekly data averages by hour of the day -foreach my $year ( sort keys %seen_weeks ) { - foreach my $week ( sort keys %{$seen_weeks{$year}} ) { +foreach my $year ( sort keys %seen_weeks ) { + foreach my $week ( sort keys %{$seen_weeks{$year}} ) { my $time = Time::Piece->strptime( $year, "%Y" ); my $mon = $time + ( ONE_WEEK * ( $week - 1 ) ) + ( ONE_DAY ); my $sun = $time + ( ONE_WEEK * ( $week - 1 ) ) + ( ONE_DAY * 7 ); @@ -253,10 +259,10 @@ set datafile separator whitespace set timefmt "%H:%M:%S" # Store in table in seconds, as the value must be a number set format x "%s" timedate -set format y "%.2f" numeric +set format y "%.2f" numeric ); -foreach my $d ( sort keys %seen_days ) { +foreach my $d ( sort keys %seen_days ) { my $label = "$1$2$3" if ( $d =~ m#(\d{4})-(\d{2})-(\d{2})# ); push @data, qq( set samples 10000 @@ -281,7 +287,7 @@ set datafile separator whitespace set timefmt "%H:%M:%S" # Store in table in seconds, as the value must be a number set format x "%s" timedate -set format y "%.2f" numeric +set format y "%.2f" numeric set samples 10000 @@ -320,8 +326,8 @@ unset table # Sample the average $interval values by week into a smoothed plot, and store each in a new table -foreach my $year ( sort keys %seen_weeks ) { - foreach my $week ( sort keys %{$seen_weeks{$year}} ) { +foreach my $year ( sort keys %seen_weeks ) { + foreach my $week ( sort keys %{$seen_weeks{$year}} ) { my $time = Time::Piece->strptime( "$year", "%Y" ); my $mon = $time + ( ONE_WEEK * ( $week - 1 ) ) + ( ONE_DAY ); my $label = $mon->strftime("%Y%m%d"); @@ -330,7 +336,7 @@ set datafile separator whitespace set timefmt "%H:%M:%S" set format x "%s" timedate -set format y "%.2f" numeric +set format y "%.2f" numeric set samples 10000 set xdata stats \$DataWeekAvg$label using 2 @@ -383,7 +389,7 @@ set style data lines set xdata time set timefmt "%H:%M:%S" set format x "%H:%M" timedate -set format y "%.0f" numeric +set format y "%.0f" numeric # If extended to 23:59, the x grid overlaps with the border set xrange ["00:00":"23:58"] set yrange [0:$graph_max] @@ -397,12 +403,12 @@ set rmargin 10 set tmargin 5 set bmargin 5 -set multiplot title layout $graphs_per_page,1 +set multiplot title layout $graphs_per_page,1 ); # For each day, generate a graph with some fancy options -foreach my $d ( sort keys %seen_days ) { +foreach my $d ( sort keys %seen_days ) { my $label = "$1$2$3" if ( $d =~ m#(\d{4})-(\d{2})-(\d{2})# ); my $time = Time::Piece->strptime( $d, "%Y-%m-%d" ); #my $title = $time->strftime("%a %d %b %Y"); @@ -413,7 +419,7 @@ foreach my $d ( sort keys %seen_days ) { push @data, qq( set title "Daily Glucose Summary for $title" font "Calibri,18" set xlabel "Time" offset 0,-0.25 -set ylabel "Blood glucose" +set ylabel "Blood glucose" set xtics left tc rgb "#000000" set ytics 2 tc rgb "#000000" set grid ytics ls 100 front @@ -421,7 +427,7 @@ 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 AVG = Mean$label -AVG_LABEL = gprintf("Median glucose: %.2f", AVG) +AVG_LABEL = gprintf("Median 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 @@ -431,8 +437,8 @@ plot \$SmoothData$label using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose set multiplot previous set title " " set xlabel " " offset 0,-0.25 -set ylabel " " -set xtics tc rgb "#ffffff00" +set ylabel " " +set xtics tc rgb "#ffffff00" set ytics tc rgb "#ffffff00" unset grid unset object 1 @@ -461,7 +467,7 @@ set style data lines set xdata time set timefmt "%H:%M:%S" set format x "%H:%M" timedate -set format y "%.0f" numeric +set format y "%.0f" numeric # If extended to 23:59, the x grid overlaps with the border set xrange ["00:00":"23:58"] set yrange [0:$graph_max] @@ -477,11 +483,11 @@ set rmargin 10 set tmargin 5 set bmargin 5 -set multiplot title layout $graphs_per_page,1 +set multiplot title layout $graphs_per_page,1 set title "Overall Average Daily Glucose" font "Calibri,18" set xlabel "Time" offset 0,-0.25 -set ylabel "Blood glucose" +set ylabel "Blood glucose" set xtics left tc rgb "#000000" set ytics 2 tc rgb "#000000" set grid ytics ls 100 front @@ -489,7 +495,7 @@ 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.05 back AVG = MedianTotal -AVG_LABEL = gprintf("Median glucose: %.2f", AVG) +AVG_LABEL = gprintf("Median 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 @@ -501,26 +507,26 @@ if (A1C == 0 && '$units' eq 'mmol/L') { A1C = (MedianTotal + 2.59) / 1.59 } # mg/dL numbers tend to be higher than 35 -if (A1C == 0 && MedianTotal >= 35) { +if (A1C == 0 && MedianTotal >= 35) { A1C = (MedianTotal + 46.7) / 28.7 } # mmol/L numbers tend to be lower than 35 -if (A1C == 0 && MedianTotal < 35) { +if (A1C == 0 && MedianTotal < 35) { A1C = (MedianTotal + 2.59) / 1.59 } -A1C_LABEL = gprintf("Average A1c: %.1f", A1C) +A1C_LABEL = gprintf("Average A1c: %.1f", A1C) 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 \$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 +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 # Add an x grid set multiplot previous set title " " set xlabel " " offset 0,-0.25 -set ylabel " " -set xtics tc rgb "#ffffff00" +set ylabel " " +set xtics tc rgb "#ffffff00" set ytics tc rgb "#ffffff00" unset grid unset object 1 @@ -540,7 +546,7 @@ set style data lines set xdata time set timefmt "%H:%M:%S" set format x "%H:%M" timedate -set format y "%.0f" numeric +set format y "%.0f" numeric # If extended to 23:59, the x grid overlaps with the border set xrange ["00:00":"23:58"] set yrange [0:$graph_max] @@ -556,10 +562,10 @@ set rmargin 10 set tmargin 5 set bmargin 5 -set multiplot title layout $graphs_per_page,1 +set multiplot title layout $graphs_per_page,1 ); -foreach my $year ( sort keys %seen_weeks ) { - foreach my $week ( sort keys %{$seen_weeks{$year}} ) { +foreach my $year ( sort keys %seen_weeks ) { + foreach my $week ( sort keys %{$seen_weeks{$year}} ) { my $time = Time::Piece->strptime( "$year", "%Y" ); my $mon = $time + ( ONE_WEEK * ( $week - 1 ) ) + ( ONE_DAY ); my $sun = $time + ( ONE_WEEK * ( $week - 1 ) ) + ( ONE_DAY * 7 ); @@ -568,7 +574,7 @@ foreach my $year ( sort keys %seen_weeks ) { push @data, qq( set title "Average Daily Glucose from $title" font "Calibri,18" set xlabel "Time" offset 0,-0.25 -set ylabel "Blood glucose" +set ylabel "Blood glucose" set xtics left tc rgb "#000000" set ytics 2 tc rgb "#000000" set grid ytics ls 100 front @@ -576,7 +582,7 @@ 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.05 back AVG = MedianTotal$label -AVG_LABEL = gprintf("Median glucose: %.2f", AVG) +AVG_LABEL = gprintf("Median 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 @@ -588,26 +594,26 @@ if (A1C == 0 && '$units' eq 'mmol/L') { A1C = (MedianTotal$label + 2.59) / 1.59 } # mg/dL numbers tend to be higher than 35 -if (A1C == 0 && MedianTotal$label >= 35) { +if (A1C == 0 && MedianTotal$label >= 35) { A1C = (MedianTotal$label + 46.7) / 28.7 } # mmol/L numbers tend to be lower than 35 -if (A1C == 0 && MedianTotal$label < 35) { +if (A1C == 0 && MedianTotal$label < 35) { A1C = (MedianTotal$label + 2.59) / 1.59 } -A1C_LABEL = gprintf("Average A1c: %.1f", A1C) +A1C_LABEL = gprintf("Average A1c: %.1f", A1C) 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 \$DataWeekMaxMinTable$label using (strftime("%H:%M:%S", \$1)):2:3 with filledcurves lc 111, \$SmoothDataWeekAvg$label using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable +plot \$DataWeekMaxMinTable$label using (strftime("%H:%M:%S", \$1)):2:3 with filledcurves lc 111, \$SmoothDataWeekAvg$label using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable # Add an x grid set multiplot previous set title " " set xlabel " " offset 0,-0.25 -set ylabel " " -set xtics tc rgb "#ffffff00" +set ylabel " " +set xtics tc rgb "#ffffff00" set ytics tc rgb "#ffffff00" unset grid unset object 1 @@ -629,7 +635,7 @@ test ); # Cleanup stored variables -foreach my $d ( sort keys %seen_days ) { +foreach my $d ( sort keys %seen_days ) { my $label = "$1$2$3" if ( $d =~ m#(\d{4})-(\d{2})-(\d{2})# ); push @data, qq(undefine \$Data$label); push @data, qq(undefine \$SmoothData$label); @@ -648,11 +654,11 @@ undefine \$DataMaxMinTable $gnuplot_data = join "\n", @data; print $gnuplot_data; -open( my $ofh, '>', $output ) +open( my $ofh, '>', $output ) or die "Could not open file '$output' $!"; my ( $pid, $stdin, $stdout, $stderr ); -use Symbol 'gensym'; +use Symbol 'gensym'; $stderr = gensym; $pid = open3( $stdin, $stdout, $stderr, 'gnuplot' ); @@ -679,7 +685,6 @@ close( $ofh ) #open(GNUPLOT, "|gnuplot"); #print GNUPLOT $gnuplot_data; -#close(GNUPLOT); +#close(GNUPLOT); - -# vim : set expandtab shiftwidth=4 softtabstop=4 tw=1000 : +# vim: set expandtab shiftwidth=4 softtabstop=4 tw=1000 :