Graph Temperatures Full Day

<?php

  $top = 110;
  $bottom = 45;

  date_default_timezone_set( "America/Los_Angeles" );

  class Temperature_Graph
  {
    public $image;
    public $black;
    public $white;
    public $red;
    public $blue;
    public $green;
    public $brightgreen;
    public $violet;
    public $gray;
    public $image_width = 950;
    public $image_height = 1200;
    public $hi_temp_degrees_F = 110;
    public $lo_temp_degrees_F = 45;

    public $range_in_degrees_F;
    public $pixels_per_degree;
    public $zero;
    public $font;

    public function __construct()
    {
      global $top, $bottom, $begin, $end;

      $this->font = "..\\php\\extras\\fonts\\ttf\\Vera.ttf";

      $this->hi_temp_degrees_F = intval( $top ) + 2;
      $this->lo_temp_degrees_F = intval( $bottom ) - 2;

      $this->range_in_degrees_F = $this->hi_temp_degrees_F - $this->lo_temp_degrees_F;
      $this->pixels_per_degree = $this->image_height / $this->range_in_degrees_F;
      $this->zero = $this->hi_temp_degrees_F * $this->pixels_per_degree + 10;

      $this->image       = imagecreatetruecolor( $this->image_width, $this->image_height );
      $this->black       = imagecolorallocate( $this->image, 0, 0, 0 );
      $this->gray        = imagecolorallocate( $this->image, 200, 200, 200 );
      $this->darkgray    = imagecolorallocate( $this->image, 80, 80, 80 );
      $this->white       = imagecolorallocate( $this->image, 255, 255, 255 );
      $this->red         = imagecolorallocate( $this->image, 255, 50, 50 );
      $this->blue        = imagecolorallocate( $this->image, 50, 50, 255 );
      $this->green       = imagecolorallocate( $this->image, 0, 150, 0 );
      $this->brightgreen = imagecolorallocate( $this->image, 0, 255, 0 );
      $this->violet      = imagecolorallocate( $this->image, 255, 50, 255 );

      imagefill( $this->image, 0, 0, $this->white );

      $high_tick = $this->zero - $this->hi_temp_degrees_F * $this->pixels_per_degree;

      $tick_degrees = $this->hi_temp_degrees_F;

      for( $tick_y = $high_tick; $tick_y < $this->image_height; $tick_y += 1 * $this->pixels_per_degree )
      {
        imageline( $this->image, 10, $tick_y, 20, $tick_y, $this->black );
        if( $tick_degrees % 10 == 0 )
        {
          imageline( $this->image, 10, $tick_y, 40, $tick_y, $this->black );
          imagettftext( $this->image, 10, 0, 15, $tick_y+12, $this->black, $this->font,
            $tick_degrees . " \xC2\xB0F" );
        }
        $tick_degrees -= 1;
      }

      imagerectangle( $this->image, 10, 10, $this->image_width-1, $this->image_height-1, $this->black );
      imagettftext( $this->image, 10, 0, 70, $this->image_height-32, $this->blue, $this->font,
        "Min " . ($bottom+3) . " \xC2\xB0F"  );
      imagettftext( $this->image, 10, 0, 450, $this->image_height-32, $this->red, $this->font,
        "Max " . ($top-3) . " \xC2\xB0F"  );
      imagettftext( $this->image, 10, 0, 750, $this->image_height-32, $this->darkgray, $this->font,
        "Gray is 95% confidence level " );

      imagettftext( $this->image, 14, 0, 420, 30, $this->black, $this->font, "Temperature" );
      imagettftext( $this->image, 10, 0, 70, 50, $this->black, $this->font, "Begin " . $begin );
      imagettftext( $this->image, 10, 0, 685, 50, $this->black, $this->font, "End " . $end );
    }

    public function plot_sample( $column, $time, $mean, $std_dev, $min_temp, $max_temp )
    {
      $mean_y = $this->zero - $mean * $this->pixels_per_degree;
      $min_temp_y = $this->zero - $min_temp * $this->pixels_per_degree;
      $max_temp_y = $this->zero - $max_temp * $this->pixels_per_degree;

      $x = $column * 12 + 60;
      $y = $mean_y - $std_dev * 1.959964 * $this->pixels_per_degree;
      $x2 = $x + 6;
      $y2 = $mean_y + $std_dev * 1.959964 * $this->pixels_per_degree;

      //
      // Draw the box around the standard deviation
      //
      imagefilledrectangle( $this->image, $x, $y, $x2, $y2, $this->gray );
      imagerectangle( $this->image, $x, $y, $x2, $y2, $this->black );

      //
      // Draw the whiskers for the min_temp and max_temp
      //
      imageline( $this->image, $x+1, $min_temp_y, $x2-1, $min_temp_y, $this->blue );
      imageline( $this->image, $x+1, $max_temp_y, $x2-1, $max_temp_y, $this->red );

      imageline( $this->image, $x+3, $min_temp_y, $x+3, $mean_y, $this->blue );
      imageline( $this->image, $x+3, $mean_y, $x+3, $max_temp_y, $this->red );

      //
      // Draw the line for the mean
      //
      imageline( $this->image, $x-1, $mean_y-1, $x2+1, $mean_y-1, $this->black );
      imageline( $this->image, $x-2, $mean_y, $x2+2, $mean_y, $this->black );
      imageline( $this->image, $x-1, $mean_y+1, $x2+1, $mean_y+1, $this->black );

      if( $column % 3 == 0 )
      {
        $hour = substr( $time, 11, 2 );
        imageline( $this->image, $x+2, $this->image_height, $x+2, $this->image_height-10, $this->black );
        imagettftext( $this->image, 10, 0, $x+4, $this->image_height-2, $this->black,
          $this->font, $hour );
      }
    }

    public function __destruct()
    {
      header( 'Content-Type: image/png' );
      imagepng( $this->image );
      imagedestroy( $this->image );
    }
  }

  class Read_Database_Temperatures
  {
    public $mysqli;
    public $selected;
    public $select;

    public function __construct()
    {
      $db_name = "temperature";

      //
      // This program can be called with optional arguments:
      //   --database=foo         the name of the database to use
      //

      $options = getopt( "", [ "database::" ] );

      if( $options )
      {
        foreach( array_keys( $options ) as $key )
        {
          if( $key == "database" )
          {
            $db_name = $options[$key];
          }
        }
      }

      $this->mysqli = new mysqli( 'localhost', 'root', '', $db_name )
        or die( 'Could not connect: ' . mysqli_error() );

      $this->mysqli->select_db( $db_name )
          or die( "Could not select database $db_name" );

      //
      // Last 24 hours
      //
      // $this->select =
      //   "SELECT id, mean, std_dev, min_temp, max_temp, reg_date FROM temps"
      //     . " WHERE reg_date >= (CURTIME() - INTERVAL 1 DAY)";

      //
      // Yesterday
      //
      $this->mysqli->query( "SET time_zone='-8:00';" )
        or die( 'Select failed: ' . $this->mysqli->error );
      $this->select =
        "SELECT id, mean, std_dev, min_temp, max_temp, reg_date FROM temps"
          . " WHERE DATE(reg_date) = (CURDATE() - 1)";

      $this->selected = $this->mysqli->query( $this->select )
        or die( 'Select failed: ' . $this->mysqli->error );
    }

    public function output()
    {
      if( $this->selected->num_rows > 0 )
      {
        global $top, $bottom, $begin, $end;
        $columns = array();
        $samples = array();
        $samples_per_period = 20 * 60;
        $PACTZ = new DateTimeZone('America/Los_Angeles');
        $yesterday = new DateTime( 'yesterday midnight', $PACTZ );
        
        $period_start = $yesterday->getTimeStamp();

        $top = -99999;
        $bottom = 100000;
        $num = 0;
        $sum = 0;
        $variance = 0;

        for( $y = 0; ; $y++ )
        {
          $period_end = $period_start + (20 * 60);
          $num_samples = 0;
          $min_val = 99999;
          $max_val = -99999;
          $sum = 0;
          $row = 0;
          for( $x = 0; ; $x++ )
          {
            $row = $this->selected->fetch_assoc();
            if( ! $row )
              break;
            $sample_time = strtotime( $row["reg_date"] );
            if( $sample_time > $period_end )
              break;
            $sum += $row["mean"];
            $samples[$x] = $row["mean"];
            if( $y == 0 && $x == 0 )
              $begin = $row["reg_date"];
            $end = $row["reg_date"];
            $num_samples++;
          }

          $mean = $sum / $num_samples;

          for( $x = 0; $x < $num_samples; $x++ )
          {
            $variance += ($samples[$x] - $mean) * ($samples[$x] - $mean);

            if( $samples[$x] < $min_val )
              $min_val = $samples[$x];
            if( $samples[$x] > $max_val )
              $max_val = $samples[$x];

            if( $samples[$x] < $bottom )
              $bottom = $samples[$x];
            if( $samples[$x] > $top )
              $top = $samples[$x];
          }

          $variance /= $num_samples - 1;
          $std_dev = sqrt( $variance );

          $columns[$num] = array( $num, $end, $mean, $std_dev, $min_val, $max_val );
          $num++;

          $period_start = $period_end;
          if( ! $row )
            break;
        }

        //
        // Leave a little room at the top and bottom
        //
        $bottom -= 3;
        $top += 3;

        $graph = new Temperature_Graph();

        for( $x = 0; $x < $num; $x++ )
        {
          $c = $columns[ $x ];
          $graph->plot_sample( $c[0], $c[1], $c[2], $c[3], $c[4], $c[5] );
        }
      }
    }
  }

  $db = new Read_Database_Temperatures();
  $db->output();

?>