Graph Temperatures Water Bath

<?php

  $top = 110;
  $bottom = 45;
  $old_hour_min = "";

  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 = 900;
    public $image_height = 400;
    public $low_pixel = 400 - 50;
    public $high_pixel = 70;

    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, $bottom_temp, $begin, $end, $latest, $latest_mean, $target;

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

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

      $this->range_in_degrees_F = $this->hi_temp_degrees_F - $this->lo_temp_degrees_F;
      $this->pixels_per_degree = ($this->low_pixel - $this->high_pixel) / $this->range_in_degrees_F;
      $this->zero = $this->hi_temp_degrees_F * $this->pixels_per_degree + $this->high_pixel;

      $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 );

      $target_y = $this->zero - $target * $this->pixels_per_degree;
      imageline( $this->image, 10, $target_y, $this->image_width - 30, $target_y, $this->violet );

      $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->low_pixel; $tick_y += .1 * $this->pixels_per_degree )
      {
        imageline( $this->image, 10, $tick_y, 20, $tick_y, $this->black );
        if( ($tick_degrees * 100) % 50 == 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,
            number_format($tick_degrees,1) . " \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, 180, $this->image_height-32, $this->blue, $this->font,
        "Min " . number_format($bottom_temp,4) . " \xC2\xB0F"  );
      imagettftext( $this->image, 10, 0, 425, $this->image_height-32, $this->darkgray, $this->font,
        "Mean " . number_format($latest_mean,4) . " \xC2\xB0F"  );
      imagettftext( $this->image, 10, 0, 685, $this->image_height-32, $this->red, $this->font,
        "Max " . number_format($top,4) . " \xC2\xB0F"  );

      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, 425, 50, $this->green, $this->font,
        "Latest " . number_format($latest,4) . " \xC2\xB0F"  );
      imagettftext( $this->image, 10, 0, 425, 70, $this->violet, $this->font,
        "Target " . number_format($target,4) . " \xC2\xB0F"  );
      imagettftext( $this->image, 10, 0, 685, 50, $this->black, $this->font, "End " . $end );
    }

    public function plot_sample( $column, $time, $temperature, $mean, $std_dev, $heater, $integral, $target )
    {
      global $old_hour_min;

      $temperature_y = $this->zero - $temperature * $this->pixels_per_degree;
      $mean_y = $this->zero - $mean * $this->pixels_per_degree;

      $x = $column * 3 + 80;

      //
      // Draw the dot for the mean
      //
      imagefilledellipse( $this->image, $x, $mean_y, 6, 6, $this->gray );

      //
      // Draw the dot for the temperature
      //
      imagefilledellipse( $this->image, $x, $temperature_y, 5, 5, $this->green );

      if( $column % 20 == 0 )
      {
        $hour_min = substr( $time, 11, 5 );
        $hour_min_sec = substr( $time, 11, 8 );
        imageline( $this->image, $x+2, $this->image_height, $x+2, $this->image_height-15, $this->black );
        if( $hour_min != $old_hour_min )
          imagettftext( $this->image, 8, 0, $x+4, $this->image_height-2, $this->black, $this->font, " " . $hour_min );
        $old_hour_min = $hour_min;
      }
    }

    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 = "water_bath";

      //
      // 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" );

      $this->mysqli->query( "SET time_zone='-8:00';" )
        or die( 'Select failed: ' . $this->mysqli->error );

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

      //
      // Yesterday
      //
      // $this->select =
      //   "SELECT id, temperature, mean, std_dev, heater, reg_date FROM temps"
      //     . " WHERE DATE(reg_date) = (CURDATE() - 1)";

      //
      // Last 5 minutes
      //
      $this->select =
        "SELECT id, temperature, mean, std_dev, heater, integral, target, reg_date FROM temps"
          . " WHERE reg_date > date_sub( now(), interval 5 minute )";

      $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, $bottom_temp, $begin, $end, $latest, $latest_mean, $target;
        $columns = array();
        
        $top = -99999;
        $bottom = 100000;
        $bottom_temp = 100000;
        $num = 0;
        $row = 0;

        for( $x = 0; ; $x++ )
        {
          $row = $this->selected->fetch_assoc();
          if( ! $row )
            break;

          $temperature = $row["temperature"];
          $latest = $temperature;
          $mean = $row["mean"];
          $latest_mean = $mean;
          $std_dev = $row["std_dev"];
          $heater = $row["heater"];
          $integral = $row["integral"];
          $target = $row["target"];

          if( $temperature < $bottom_temp )
            $bottom_temp = $temperature;
          if( $temperature < $bottom )
            $bottom = $temperature;
          if( $mean < $bottom )
            $bottom = $mean;

          if( $temperature > $top )
            $top = $temperature;
          if( $mean > $top )
            $top = $mean;

          if( $x == 0 )
            $begin = $row["reg_date"];

          $end = $row["reg_date"];

          $columns[$num] = array( $num, $end, $temperature, $mean, $std_dev, $heater, $integral, $target );
          $num++;

          if( ! $row )
            break;
        }

        $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], $c[6], $c[7] );
        }
      }
    }
  }

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

?>