diff --git a/glucometer_graphs.pl b/glucometer_graphs.pl index 6cc155e..7f12902 100644 --- a/glucometer_graphs.pl +++ b/glucometer_graphs.pl @@ -60,11 +60,10 @@ GetOptions ("input=s" => \$input, # The name of the CSV file from wh sub calculate_max_min { my %opts = @_; my $lines = $opts{lines}; - my $fmt = $opts{format} || qq("%Y-%m-%d %H:%M:%S"); + my $fmt = $opts{format} || qq(%Y-%m-%dT%H:%M:%S); my %intervals; foreach my $row ( @{$lines} ) { - my ( $key, $value ) = split /,/, $row; - $value =~ s#"(.*)"#$1#; + my ( $key, $value ) = split / /, $row; 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 ); @@ -98,6 +97,15 @@ open( my $ifh, '<:encoding(UTF-8)', $input ) while ( my $row = <$ifh> ) { chomp( $row ); + # Clean up the comments + $row =~ s#\((Scan|Sensor)\)(; )?##i; + $row =~ s#\(Blood\)(; )?##i; + $row =~ s#Food \(.*?\)(; )?#{/: 🍎}#i; + $row =~ s#Rapid-acting insulin \(.*?\)(; )?#~{/: 💉}{-1{/:=10 Rapid}}#i; + $row =~ s#Long-acting insulin \(.*?\)(; )?#~{/: 💉}{-1{/:=10 Long}}#i; + + # 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"#; push @filelines, $row; } @@ -126,8 +134,8 @@ 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, split /-/, $1 ); + 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}++; @@ -144,7 +152,7 @@ foreach my $year ( sort keys %seen_weeks ) { $total_day_graphs = scalar keys %seen_days; $total_week_graphs = scalar keys %seen_weeks; -$intervals = calculate_max_min( 'lines' => \@filelines, 'format' => '"%Y-%m-%d %H:%M:%S"' ); +$intervals = calculate_max_min( 'lines' => \@filelines, 'format' => '%Y-%m-%dT%H:%M:%S' ); # Set up basic gnuplot output options push @data, qq( @@ -155,13 +163,11 @@ set terminal pdf size $page_size enhanced font 'Calibri,14' linewidth 1 # Read each line into a $Data variable for use by gnuplot 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 -"timestamp","blood glucose","meal","method","comment"); + push @data, qq(\$Data$label << EOD); @sortedlines = (); foreach my $row (@filelines) { - if ( $row =~ m#^"$d .*$# ) { - push @sortedlines, qq($row); + if ( $row =~ m#^${d}T(.*)$# ) { + push @sortedlines, qq($1); } } @sortedlines = map { " $_" } @sortedlines; # indent data structure @@ -170,13 +176,11 @@ foreach my $d ( sort keys %seen_days ) { } # Output data averages by hour of the day -push @data, qq( -\$DataAvg << EOD -"timestamp","blood glucose","meal","method","comment"); +push @data, qq(\$DataAvg << EOD); @sortedlines = (); foreach my $row (@filelines) { - if ( $row =~ m#^(")\d{4}-\d{2}-\d{2} (.*)$# ) { - push @sortedlines, qq($1$2); + if ( $row =~ m#^\S+?T(\S+) (\S+)# ) { + push @sortedlines, qq($1 $2); } } @sortedlines = map { " $_" } @sortedlines; # indent data structure @@ -184,12 +188,10 @@ push @data, join "\n", sort @sortedlines; push @data, qq(EOD); # Output the max and min glucose values for each $interval time period -push @data, qq( -\$DataMaxMin << EOD -"timestamp","max","min"); +push @data, qq(\$DataMaxMin << EOD); @sortedlines = (); foreach my $time ( sort keys %$intervals ) { - push @sortedlines, qq("$time","$intervals->{$time}->{max}","$intervals->{$time}->{min}"); + push @sortedlines, qq($time $intervals->{$time}->{max} $intervals->{$time}->{min}); } @sortedlines = map { " $_" } @sortedlines; # indent data structure push @data, join "\n", sort @sortedlines; @@ -206,37 +208,34 @@ foreach my $year ( sort keys %seen_weeks ) { # Select data from the week in question my @weeklines; + foreach my $row (@filelines) { - foreach my $dow ( 1 .. 7 ) { + foreach my $dow ( 0 .. 6 ) { my $day = $mon + ( ONE_DAY * $dow ); my $d = $day->strftime("%Y-%m-%d"); - if ( $row =~ m#^"$d .*$#ms ) { + if ( $row =~ m#^$d#ms ) { push @weeklines, $row; } } } - push @data, qq( -\$DataWeekAvg$label << EOD - "timestamp","blood glucose","meal","method","comment"); + push @data, qq(\$DataWeekAvg$label << EOD); @sortedlines = (); foreach my $row (@weeklines) { - if ($row =~ m#^(")\d{4}-\d{2}-\d{2} (.*)$# ) { - push @sortedlines, qq($1$2); + if ($row =~ m#^\d{4}-\d{2}-\d{2}T(\S+) (\S+) # ) { + push @sortedlines, qq($1 $2); } } - my $week_intervals = calculate_max_min( 'lines' => \@sortedlines, 'format' => '"%H:%M:%S"' ); + my $week_intervals = calculate_max_min( 'lines' => \@sortedlines, 'format' => '%H:%M:%S' ); @sortedlines = map { " $_" } @sortedlines; # indent data structure push @data, join "\n", sort @sortedlines; push @data, qq(EOD); - push @data, qq( -\$DataWeekMaxMin$label << EOD - "timestamp","max","min"); + push @data, qq(\$DataWeekMaxMin$label << EOD); @sortedlines = (); foreach my $time ( sort keys %{$week_intervals} ) { - push @sortedlines, qq("$time","$intervals->{$time}->{max}","$intervals->{$time}->{min}"); + push @sortedlines, qq($time $intervals->{$time}->{max} $intervals->{$time}->{min}); } @sortedlines = map { " $_" } @sortedlines; # indent data structure push @data, sort @sortedlines; @@ -247,10 +246,11 @@ foreach my $year ( sort keys %seen_weeks ) { # Sample each day's values into a smoothed plot, and store each plot in a new table push @data, qq( -set datafile separator "," +set datafile separator whitespace # Read the CSV time format -set timefmt "%Y-%m-%d %H:%M:%S" +#set timefmt "%Y-%m-%dT%H:%M:%S" +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 @@ -269,15 +269,13 @@ set xdata time set table \$SmoothData$label plot \$Data$label using 1:2 smooth mcsplines unset table -set table 'SmoothData$label' -plot \$Data$label using 1:2 smooth mcsplines -unset table + ); } # Sample the average $interval values into a smoothed plot, and store in a new table push @data, qq( -set datafile separator "," +set datafile separator whitespace # Read the CSV time format set timefmt "%H:%M:%S" @@ -328,14 +326,13 @@ foreach my $year ( sort keys %seen_weeks ) { my $mon = $time + ( ONE_WEEK * ( $week - 1 ) ) + ( ONE_DAY ); my $label = $mon->strftime("%Y%m%d"); push @data, qq( -set datafile separator "," +set datafile separator whitespace set timefmt "%H:%M:%S" set format x "%s" timedate set format y "%.2f" numeric set samples 10000 set xdata -print "DataWeekAvg$label" stats \$DataWeekAvg$label using 2 MedianTotal$label = STATS_mean MeanTotal$label = STATS_mean @@ -428,7 +425,7 @@ 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 -plot \$SmoothData$label using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 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 , \$Data$label using 1:($graph_max-6):3 with labels font "Calibri,18" enhanced # Add an x grid set multiplot previous