Cleanup and refactoring for cleanliness, in preparation for average weekly graphs.
This commit is contained in:
parent
b743174a31
commit
5119550036
@ -13,19 +13,23 @@ use warnings;
|
|||||||
use Getopt::Long;
|
use Getopt::Long;
|
||||||
use IPC::Open3;
|
use IPC::Open3;
|
||||||
use Time::Piece;
|
use Time::Piece;
|
||||||
|
use Time::Seconds;
|
||||||
use Data::Dumper;
|
use Data::Dumper;
|
||||||
|
|
||||||
|
$Data::Dumper::Sortkeys = 1;
|
||||||
|
|
||||||
my $error = "Usage: $0 --input <CSV file> --output <output PDF> [--max <n.n>] [--low <n.n>] [--high <n.n>]\n";
|
my $error = "Usage: $0 --input <CSV file> --output <output PDF> [--max <n.n>] [--low <n.n>] [--high <n.n>]\n";
|
||||||
my @lines;
|
my @filelines;
|
||||||
my @sorted_lines;
|
my @sortedlines;
|
||||||
my @data;
|
my @data;
|
||||||
my @avg_data;
|
my @avg_data;
|
||||||
my %intervals;
|
my $intervals;
|
||||||
my %seen;
|
my %seen_days;
|
||||||
my $a1c_calc;
|
my %seen_weeks;
|
||||||
my $page_size;
|
my $page_size;
|
||||||
my $gnuplot_data;
|
my $gnuplot_data;
|
||||||
my $total_graphs;
|
my $total_day_graphs;
|
||||||
|
my $total_week_graphs;
|
||||||
my $count_graphs = 0;
|
my $count_graphs = 0;
|
||||||
my $page_number = 0;
|
my $page_number = 0;
|
||||||
my $interval = 15; # The number of minutes to average points for the area range graph
|
my $interval = 15; # The number of minutes to average points for the area range graph
|
||||||
@ -38,7 +42,7 @@ my $min_glucose = 4;
|
|||||||
my $graph_max = 21;
|
my $graph_max = 21;
|
||||||
my $units = '';
|
my $units = '';
|
||||||
my $page = 'a4';
|
my $page = 'a4';
|
||||||
my $days_per_page = 2;
|
my $graphs_per_page = 2;
|
||||||
GetOptions ("input=s" => \$input, # The name of the CSV file from which to read values
|
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
|
"output=s" => \$output, # The name of the PDF file to output
|
||||||
"high:f" => \$max_glucose, # The high end of your target blood glucose level
|
"high:f" => \$max_glucose, # The high end of your target blood glucose level
|
||||||
@ -46,15 +50,55 @@ GetOptions ("input=s" => \$input, # The name of the CSV file from wh
|
|||||||
"max:i" => \$graph_max, # The highest displayed glucose level on each graph
|
"max:i" => \$graph_max, # The highest displayed glucose level on each graph
|
||||||
"units:s" => \$units, # mmol/L or mg/dL
|
"units:s" => \$units, # mmol/L or mg/dL
|
||||||
"pagesize:s" => \$page, # size of page to print
|
"pagesize:s" => \$page, # size of page to print
|
||||||
"graphs:i" => \$days_per_page) # The number of days printed on each page
|
"graphs:i" => \$graphs_per_page) # The number of days printed on each page
|
||||||
or die $error;
|
or die $error;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Calculate the max and min glucose values for each time interval
|
||||||
|
# Takes an array ref of lines; returns a hash of intervals by time and min/max
|
||||||
|
sub calculate_max_min {
|
||||||
|
my %opts = @_;
|
||||||
|
my $lines = $opts{lines};
|
||||||
|
my $fmt = $opts{format} || qq("%Y-%m-%d %H:%M:%S");
|
||||||
|
my %intervals;
|
||||||
|
foreach my $row ( @{$lines} ) {
|
||||||
|
my ( $key, $value ) = split /,/, $row;
|
||||||
|
$value =~ s#"(.*)"#$1#;
|
||||||
|
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 {
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return \%intervals;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
open( my $ifh, '<:encoding(UTF-8)', $input )
|
open( my $ifh, '<:encoding(UTF-8)', $input )
|
||||||
or die "Could not open file '$input' $!";
|
or die "Could not open file '$input' $!";
|
||||||
|
|
||||||
while ( my $row = <$ifh> ) {
|
while ( my $row = <$ifh> ) {
|
||||||
chomp( $row );
|
chomp( $row );
|
||||||
push @lines, $row;
|
push @filelines, $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
close( $ifh )
|
close( $ifh )
|
||||||
@ -71,64 +115,230 @@ if ( $page =~ /a4/i ) {
|
|||||||
$page_size = "29.7cm,21.0cm";
|
$page_size = "29.7cm,21.0cm";
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set up basic gnuplot options for reading the CSV data
|
# Standardise units for gnuplot's A1C calculations
|
||||||
|
if ( $units =~ /mg/i ) {
|
||||||
|
$units = 'mg/dL';
|
||||||
|
} elsif ( $units =~ /mmol/i ) {
|
||||||
|
$units = 'mmol/L';
|
||||||
|
} else {
|
||||||
|
$units = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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 );
|
||||||
|
my $time = Time::Piece->strptime( $date, "%Y-%m-%d" );
|
||||||
|
my $week = $time->strftime("%W");
|
||||||
|
$seen_weeks{$year}{$week}++;
|
||||||
|
$seen_days{$date}++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$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"' );
|
||||||
|
|
||||||
|
# Set up basic gnuplot output options
|
||||||
push @data, qq(
|
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'
|
#set output '$output'
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
# Get the list of days for which to produce graphs
|
|
||||||
foreach my $row ( @lines ) {
|
|
||||||
if ( $row =~ m#^"(\d{4}-\d{2}-\d{2})#ms ) {
|
|
||||||
my $day = $1;
|
|
||||||
$seen{$day}++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$total_graphs = scalar keys %seen;
|
|
||||||
|
|
||||||
# Read each line into a $Data variable for use by gnuplot
|
# Read each line into a $Data variable for use by gnuplot
|
||||||
# Then sample into a smoothed plot for each day, and store each smoothed line in a new $SmoothData$date variable
|
foreach my $d ( sort keys %seen_days ) {
|
||||||
foreach my $d ( sort keys %seen ) {
|
|
||||||
my $label = "$1$2$3" if ( $d =~ m#(\d{4})-(\d{2})-(\d{2})# );
|
my $label = "$1$2$3" if ( $d =~ m#(\d{4})-(\d{2})-(\d{2})# );
|
||||||
push @data, qq(
|
push @data, qq(
|
||||||
\$Data$label << EOD
|
\$Data$label << EOD
|
||||||
"timestamp","blood glucose","meal","method","comment");
|
"timestamp","blood glucose","meal","method","comment");
|
||||||
foreach my $row (@lines) {
|
@sortedlines = ();
|
||||||
if ( $row =~ s#^"($d )#"$1#ms ) {
|
foreach my $row (@filelines) {
|
||||||
push @data, $row;
|
if ( $row =~ m#^"$d .*$# ) {
|
||||||
|
push @sortedlines, qq($row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# @sortedlines = map { " $_" } @sortedlines; # indent data structure
|
||||||
|
push @data, join "\n", sort @sortedlines;
|
||||||
push @data, qq(EOD);
|
push @data, qq(EOD);
|
||||||
|
}
|
||||||
|
|
||||||
push @data, qq(
|
# Output data averages by hour of the day
|
||||||
|
push @data, qq(
|
||||||
|
\$DataAvg << EOD
|
||||||
|
"timestamp","blood glucose","meal","method","comment");
|
||||||
|
@sortedlines = ();
|
||||||
|
foreach my $row (@filelines) {
|
||||||
|
if ( $row =~ m#^(")\d{4}-\d{2}-\d{2} (.*)$# ) {
|
||||||
|
push @sortedlines, qq($1$2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#@sortedlines = map { " $_" } @sortedlines; # indent data structure
|
||||||
|
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");
|
||||||
|
@sortedlines = ();
|
||||||
|
foreach my $time ( sort keys %$intervals ) {
|
||||||
|
push @sortedlines, qq("$time","$intervals->{$time}->{max}","$intervals->{$time}->{min}");
|
||||||
|
}
|
||||||
|
#@sortedlines = map { " $_" } @sortedlines; # indent data structure
|
||||||
|
push @data, join "\n", sort @sortedlines;
|
||||||
|
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}} ) {
|
||||||
|
my $time = Time::Piece->strptime( $year, "%Y" );
|
||||||
|
my $mon = $time + ( ONE_WEEK * ( $week - 1 ) );
|
||||||
|
my $sun = $time + ( ONE_WEEK * ( $week - 1 ) ) + ( ONE_DAY * 6 );
|
||||||
|
my $label = $mon->strftime("%Y%m%d");
|
||||||
|
|
||||||
|
# Select data from the week in question
|
||||||
|
my @weeklines;
|
||||||
|
foreach my $row (@filelines) {
|
||||||
|
foreach my $dow ( 0 .. 7 ) {
|
||||||
|
my $day = $mon + ( ONE_DAY * $dow );
|
||||||
|
my $d = $day->strftime("%Y-%m-%d");
|
||||||
|
if ( $row =~ m#^"$d .*$#ms ) {
|
||||||
|
push @weeklines, $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
push @data, qq(
|
||||||
|
\$DataWeek$label << EOD
|
||||||
|
"timestamp","blood glucose","meal","method","comment");
|
||||||
|
@sortedlines = ();
|
||||||
|
foreach my $row (@weeklines) {
|
||||||
|
if ($row =~ m#^(")\d{4}-\d{2}-\d{2} (.*)$# ) {
|
||||||
|
push @sortedlines, qq($1$2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# @sortedlines = map { " $_" } @sortedlines; # indent data structure
|
||||||
|
push @data, join "\n", sort @sortedlines;
|
||||||
|
push @data, qq(EOD);
|
||||||
|
|
||||||
|
|
||||||
|
push @data, qq(
|
||||||
|
\$DataWeekAvg$label << EOD
|
||||||
|
"timestamp","blood glucose","meal","method","comment");
|
||||||
|
@sortedlines = ();
|
||||||
|
foreach my $row (@weeklines) {
|
||||||
|
if ($row =~ m#^(")\d{4}-\d{2}-\d{2} (.*)$# ) {
|
||||||
|
push @sortedlines, qq($1$2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(
|
||||||
|
\$DataMaxMin$label << EOD
|
||||||
|
"timestamp","max","min");
|
||||||
|
@sortedlines = ();
|
||||||
|
foreach my $time ( sort keys %{$week_intervals} ) {
|
||||||
|
push @sortedlines, qq("$time","$intervals->{$time}->{max}","$intervals->{$time}->{min}");
|
||||||
|
}
|
||||||
|
# @sortedlines = map { " $_" } @sortedlines; # indent data structure
|
||||||
|
push @data, sort @sortedlines;
|
||||||
|
push @data, qq(EOD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# 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 ","
|
||||||
|
|
||||||
|
# Read the CSV time format
|
||||||
set timefmt "%Y-%m-%d %H:%M:%S"
|
set timefmt "%Y-%m-%d %H:%M:%S"
|
||||||
|
# Store in table in seconds, as the value must be a number
|
||||||
set format x "%s" timedate
|
set format x "%s" timedate
|
||||||
set format y "%.2f" numeric
|
set format y "%.2f" numeric
|
||||||
|
|
||||||
|
);
|
||||||
|
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
|
set samples 10000
|
||||||
|
|
||||||
set xdata
|
set xdata
|
||||||
stats \$Data$label using 2
|
stats \$Data$label using 2
|
||||||
Mean$label = STATS_mean
|
Mean$label = STATS_mean
|
||||||
|
|
||||||
set xdata time
|
set xdata time
|
||||||
set table \$SmoothData$label
|
set table \$SmoothData$label
|
||||||
#plot \$Data$label using "timestamp":"blood glucose"
|
plot \$Data$label using 1:2 smooth mcsplines
|
||||||
#plot \$Data$label using "timestamp":"blood glucose" smooth frequency
|
unset table
|
||||||
plot \$Data$label using "timestamp":"blood glucose" smooth mcsplines
|
set table 'SmoothData$label'
|
||||||
#plot \$Data$label using "timestamp":"blood glucose" smooth bezier
|
plot \$Data$label using 1:2 smooth mcsplines
|
||||||
unset table
|
unset table
|
||||||
undefine \$Data$label
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Sample the average $interval values into a smoothed plot, and store in a new table
|
||||||
|
push @data, qq(
|
||||||
|
set datafile separator ","
|
||||||
|
|
||||||
|
# Read the CSV time format
|
||||||
|
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 samples 10000
|
||||||
|
|
||||||
|
set xdata
|
||||||
|
stats \$DataAvg using 2
|
||||||
|
MeanTotal = STATS_mean
|
||||||
|
|
||||||
|
set xdata time
|
||||||
|
|
||||||
|
set table \$DataAvgTable
|
||||||
|
plot \$DataAvg using 1:2 smooth mcsplines
|
||||||
|
unset table
|
||||||
|
|
||||||
|
set table \$SmoothDataAvg
|
||||||
|
plot \$DataAvg using 1:2 smooth bezier
|
||||||
|
unset table
|
||||||
|
|
||||||
|
|
||||||
|
# Convert DataMaxMin from CSV to table
|
||||||
|
set table \$DataMaxMinTable
|
||||||
|
plot \$DataMaxMin using 1:2:3 with table
|
||||||
|
unset table
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Set up output options for gnuplot.
|
# Set up output options for gnuplot.
|
||||||
# We don't bother to do this at the start, since the CSV needs a comma separator
|
# We don't bother to do this at the start, since the CSV needs a comma separator
|
||||||
# and the new $SmoothData, which contains a table, needs a whitespace separator
|
# and the new $SmoothData, which contains a table, needs a whitespace separator
|
||||||
push @data, qq(
|
push @data, qq(
|
||||||
# change separator from CSV to table
|
# ensure separator handles tables
|
||||||
reset
|
#reset
|
||||||
set datafile separator whitespace
|
set datafile separator whitespace
|
||||||
|
|
||||||
set key off
|
set key off
|
||||||
@ -137,10 +347,9 @@ set xdata time
|
|||||||
set timefmt "%H:%M:%S"
|
set timefmt "%H:%M:%S"
|
||||||
set format x "%H:%M" timedate
|
set format x "%H:%M" timedate
|
||||||
set format y "%.0f" numeric
|
set format y "%.0f" numeric
|
||||||
|
|
||||||
set yrange [0:$graph_max]
|
|
||||||
# If extended to 23:59, the x grid overlaps with the border
|
# If extended to 23:59, the x grid overlaps with the border
|
||||||
set xrange ["00:00":"23:58"]
|
set xrange ["00:00":"23:58"]
|
||||||
|
set yrange [0:$graph_max]
|
||||||
|
|
||||||
set style line 100 dt 3 lw 1 lc rgb "#202020"
|
set style line 100 dt 3 lw 1 lc rgb "#202020"
|
||||||
set style line 101 dt 1 lw 1 lc rgb "#202020"
|
set style line 101 dt 1 lw 1 lc rgb "#202020"
|
||||||
@ -151,14 +360,14 @@ set rmargin 10
|
|||||||
set tmargin 5
|
set tmargin 5
|
||||||
set bmargin 5
|
set bmargin 5
|
||||||
|
|
||||||
set multiplot title layout $days_per_page,1
|
set multiplot title layout $graphs_per_page,1
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
# For each day, generate a graph with some fancy options
|
# For each day, generate a graph with some fancy options
|
||||||
foreach my $d ( sort keys %seen ) {
|
foreach my $d ( sort keys %seen_days ) {
|
||||||
my $label = "$1$2$3" if ( $d =~ m#(\d{4})-(\d{2})-(\d{2})# );
|
my $label = "$1$2$3" if ( $d =~ m#(\d{4})-(\d{2})-(\d{2})# );
|
||||||
my $time = Time::Piece->strptime ( $d, "%Y-%m-%d" );
|
my $time = Time::Piece->strptime( $d, "%Y-%m-%d" );
|
||||||
#my $title = $time->strftime("%a %d %b %Y");
|
#my $title = $time->strftime("%a %d %b %Y");
|
||||||
my $title = $time->strftime("%A, %d %B %Y");
|
my $title = $time->strftime("%A, %d %B %Y");
|
||||||
|
|
||||||
@ -172,8 +381,6 @@ set xtics left tc rgb "#000000"
|
|||||||
set ytics 2 tc rgb "#000000"
|
set ytics 2 tc rgb "#000000"
|
||||||
set grid ytics ls 100 front
|
set grid ytics ls 100 front
|
||||||
|
|
||||||
#set arrow from graph 0,first $min_glucose to graph 1,first $min_glucose ls 6 lw 2 nohead
|
|
||||||
#set arrow from graph 0,first $max_glucose to graph 1,first $max_glucose ls 6 lw 2 nohead
|
|
||||||
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.2 back
|
||||||
|
|
||||||
AVG = Mean$label
|
AVG = Mean$label
|
||||||
@ -181,11 +388,8 @@ 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 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
|
set label 2 AVG_LABEL at graph 0.9, 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
|
plot \$SmoothData$label using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable
|
||||||
|
|
||||||
undefine \$SmoothData$label
|
|
||||||
|
|
||||||
# Add an x grid
|
# Add an x grid
|
||||||
set multiplot previous
|
set multiplot previous
|
||||||
set title " "
|
set title " "
|
||||||
@ -199,124 +403,20 @@ set grid xtics ls 101
|
|||||||
plot 1/0
|
plot 1/0
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( $count_graphs % $days_per_page == 0 && $count_graphs < $total_graphs ) {
|
if ( $count_graphs % $graphs_per_page == 0 && $count_graphs < $total_day_graphs ) {
|
||||||
push @data, qq(unset multiplot);
|
push @data, qq(unset multiplot);
|
||||||
push @data, qq(set multiplot layout $days_per_page,1);
|
push @data, qq(set multiplot layout $graphs_per_page,1);
|
||||||
$page_number++;
|
$page_number++;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
# End daily graph plot
|
||||||
|
|
||||||
# Output data averages by hour of the day
|
# Plot and display a graph with the average glucose values for every $interval for all recorded days
|
||||||
@sorted_lines = ();
|
|
||||||
push @data, qq(
|
push @data, qq(
|
||||||
\$DataAvg << EOD
|
unset multiplot
|
||||||
"timestamp","blood glucose","meal","method","comment");
|
|
||||||
foreach my $row (@lines) {
|
|
||||||
if ( $row =~ s#^"\d{4}-\d{2}-\d{2} #"#ms ) {
|
|
||||||
push @sorted_lines, $row;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
push @data, sort @sorted_lines;
|
|
||||||
push @data, qq(EOD);
|
|
||||||
|
|
||||||
# Output min/max for each time interval
|
# ensure separator handles tables
|
||||||
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 ","
|
|
||||||
|
|
||||||
set timefmt "%H:%M:%S"
|
|
||||||
set format x "%s" timedate
|
|
||||||
set format y "%.2f" numeric
|
|
||||||
set samples 10000
|
|
||||||
set xdata
|
|
||||||
stats \$DataAvg using 2
|
|
||||||
MeanTotal = STATS_mean
|
|
||||||
|
|
||||||
set xdata time
|
|
||||||
|
|
||||||
set table \$DataAvgTable
|
|
||||||
|
|
||||||
#avg(x) = g
|
|
||||||
#min(x) = x<g
|
|
||||||
#max(x) = x>g
|
|
||||||
#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<MeanTotal?\$2:'') every $count_graphs/2 lc 1
|
|
||||||
|
|
||||||
#samples(x) = \$0 > 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
|
|
||||||
|
|
||||||
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
|
set datafile separator whitespace
|
||||||
|
|
||||||
set key off
|
set key off
|
||||||
@ -325,10 +425,9 @@ set xdata time
|
|||||||
set timefmt "%H:%M:%S"
|
set timefmt "%H:%M:%S"
|
||||||
set format x "%H:%M" timedate
|
set format x "%H:%M" timedate
|
||||||
set format y "%.0f" numeric
|
set format y "%.0f" numeric
|
||||||
|
|
||||||
set yrange [0:$graph_max]
|
|
||||||
# If extended to 23:59, the x grid overlaps with the border
|
# If extended to 23:59, the x grid overlaps with the border
|
||||||
set xrange ["00:00":"23:58"]
|
set xrange ["00:00":"23:58"]
|
||||||
|
set yrange [0:$graph_max]
|
||||||
|
|
||||||
set style line 100 dt 3 lw 1 lc rgb "#202020"
|
set style line 100 dt 3 lw 1 lc rgb "#202020"
|
||||||
set style line 101 dt 1 lw 1 lc rgb "#202020"
|
set style line 101 dt 1 lw 1 lc rgb "#202020"
|
||||||
@ -341,9 +440,9 @@ set rmargin 10
|
|||||||
set tmargin 5
|
set tmargin 5
|
||||||
set bmargin 5
|
set bmargin 5
|
||||||
|
|
||||||
set multiplot title layout $days_per_page,1
|
set multiplot title layout $graphs_per_page,1
|
||||||
|
|
||||||
set title "Average Daily Glucose" font "Calibri,18"
|
set title "Overall Average Daily Glucose" font "Calibri,18"
|
||||||
set xlabel "Time" offset 0,-0.25
|
set xlabel "Time" offset 0,-0.25
|
||||||
set ylabel "Blood glucose"
|
set ylabel "Blood glucose"
|
||||||
set xtics left tc rgb "#000000"
|
set xtics left tc rgb "#000000"
|
||||||
@ -377,16 +476,123 @@ 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 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
|
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 \$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
|
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
|
# Add an x grid
|
||||||
undefine \$DataMaxMin
|
set multiplot previous
|
||||||
undefine \$DataMaxMinTable
|
set title " "
|
||||||
undefine \$SmoothDataAvg
|
set xlabel " " offset 0,-0.25
|
||||||
undefine \$DataAvgTable
|
set ylabel " "
|
||||||
|
set xtics tc rgb "#ffffff00"
|
||||||
|
set ytics tc rgb "#ffffff00"
|
||||||
|
unset grid
|
||||||
|
unset object 1
|
||||||
|
set grid xtics ls 101
|
||||||
|
plot 1/0
|
||||||
|
);
|
||||||
|
# End overall average plot
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
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 ) );
|
||||||
|
my $sun = $time + ( ONE_WEEK * ( $week - 1 ) ) + ( ONE_DAY * 6 );
|
||||||
|
my $label = $mon->strftime("%Y-%m-%d");
|
||||||
|
my $title = $mon->strftime("%Y-%m-%d") . " to " . $sun->strftime("%Y-%m-%d");
|
||||||
|
push @data, qq(
|
||||||
|
#reset
|
||||||
|
set datafile separator ","
|
||||||
|
|
||||||
|
set timefmt "%H:%M:%S"
|
||||||
|
set format x "%s" timedate
|
||||||
|
set format y "%.2f" numeric
|
||||||
|
set samples 10000
|
||||||
|
set xdata
|
||||||
|
stats \$DataAvg using 2
|
||||||
|
MeanTotal = STATS_mean
|
||||||
|
|
||||||
|
set xdata time
|
||||||
|
|
||||||
|
set table \$DataAvgTable
|
||||||
|
plot \$DataAvg using 1:2 smooth mcsplines
|
||||||
|
unset table
|
||||||
|
|
||||||
|
set table \$SmoothDataAvg
|
||||||
|
plot \$DataAvg using 1:2 smooth bezier
|
||||||
|
unset table
|
||||||
|
|
||||||
|
# Convert DataMaxMin from CSV to table
|
||||||
|
set table \$DataMaxMinTable
|
||||||
|
plot \$DataMaxMin using 1:2:3 with table
|
||||||
|
unset table
|
||||||
|
|
||||||
|
reset
|
||||||
|
set datafile separator whitespace
|
||||||
|
|
||||||
|
set key off
|
||||||
|
set style data lines
|
||||||
|
set xdata time
|
||||||
|
set timefmt "%H:%M:%S"
|
||||||
|
set format x "%H:%M" timedate
|
||||||
|
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]
|
||||||
|
|
||||||
|
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
|
||||||
|
set tmargin 5
|
||||||
|
set bmargin 5
|
||||||
|
|
||||||
|
set multiplot title layout $graphs_per_page,1
|
||||||
|
|
||||||
|
set title "Average Daily Glucose from $title" font "Calibri,18"
|
||||||
|
set xlabel "Time" offset 0,-0.25
|
||||||
|
set ylabel "Blood glucose"
|
||||||
|
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.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 = 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 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
|
||||||
|
|
||||||
# Add an x grid
|
# Add an x grid
|
||||||
set multiplot previous
|
set multiplot previous
|
||||||
@ -401,12 +607,36 @@ set grid xtics ls 101
|
|||||||
plot 1/0
|
plot 1/0
|
||||||
|
|
||||||
);
|
);
|
||||||
|
if ( $count_graphs % $graphs_per_page == 0 && $count_graphs < $total_day_graphs ) {
|
||||||
|
push @data, qq(unset multiplot);
|
||||||
|
push @data, qq(set multiplot layout $graphs_per_page,1);
|
||||||
|
$page_number++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
=cut
|
||||||
|
|
||||||
push @data, qq(
|
push @data, qq(
|
||||||
unset multiplot
|
unset multiplot
|
||||||
test
|
test
|
||||||
);
|
);
|
||||||
|
|
||||||
|
# Cleanup stored variables
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
push @data, qq(
|
||||||
|
undefine \$DataAvg
|
||||||
|
undefine \$DataAvgTable
|
||||||
|
undefine \$SmoothDataAvg
|
||||||
|
undefine \$DataMaxMin
|
||||||
|
undefine \$DataMaxMinTable
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
# run the data through gnuplot
|
# run the data through gnuplot
|
||||||
$gnuplot_data = join "\n", @data;
|
$gnuplot_data = join "\n", @data;
|
||||||
print $gnuplot_data;
|
print $gnuplot_data;
|
||||||
@ -444,4 +674,5 @@ close( $ofh )
|
|||||||
#print GNUPLOT $gnuplot_data;
|
#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 :
|
||||||
|
Loading…
Reference in New Issue
Block a user