[ Index ]

PHP Cross Reference of MantisBT

title

Body

[close]

/library/ezc/Graph/src/axis/ -> date.php (source)

   1  <?php
   2  /**
   3   * File containing the ezcGraphChartElementDateAxis class
   4   *
   5   * @package Graph
   6   * @version 1.5
   7   * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
   8   * @license http://ez.no/licenses/new_bsd New BSD License
   9   */
  10  /**
  11   * Class to represent date axis.
  12   *
  13   * Axis elements represent the axis in a bar, line or radar chart. They are
  14   * chart elements (ezcGraphChartElement) extending from
  15   * ezcGraphChartElementAxis, where additional formatting options can be found.
  16   * You should generally use the axis, which matches your input data best, so
  17   * that the automatic chart layouting works best. Aavailable axis types are:
  18   *
  19   * - ezcGraphChartElementDateAxis
  20   * - ezcGraphChartElementLabeledAxis
  21   * - ezcGraphChartElementLogarithmicalAxis
  22   * - ezcGraphChartElementNumericAxis
  23   *
  24   * Date axis will try to find a "nice" interval based on the values on the x
  25   * axis. If non numeric values are given, ezcGraphChartElementDateAxis will
  26   * convert them to timestamps using PHPs strtotime function.
  27   *
  28   * It is always possible to set start date, end date and the interval manually
  29   * by yourself.
  30   *
  31   * The $dateFormat option provides an additional way of formatting the labels
  32   * used on the axis. The options from the parent class $formatString and
  33   * $labelCallback do still apply.
  34   *
  35   * You may use a date axis like in the following example:
  36   *
  37   * <code>
  38   *  $graph = new ezcGraphLineChart();
  39   *  $graph->options->fillLines = 210;
  40   *  $graph->title = 'Concurrent requests';
  41   *  $graph->legend = false;
  42   *  
  43   *  $graph->xAxis = new ezcGraphChartElementDateAxis();
  44   *  
  45   *  // Add data
  46   *  $graph->data['Machine 1'] = new ezcGraphArrayDataSet( array(
  47   *      '8:00' => 3241,
  48   *      '8:13' => 934,
  49   *      '8:24' => 1201,
  50   *      '8:27' => 1752,
  51   *      '8:51' => 123,
  52   *  ) );
  53   *  $graph->data['Machine 2'] = new ezcGraphArrayDataSet( array(
  54   *      '8:05' => 623,
  55   *      '8:12' => 2103,
  56   *      '8:33' => 543,
  57   *      '8:43' => 2034,
  58   *      '8:59' => 3410,
  59   *  ) );
  60   *  
  61   *  $graph->data['Machine 1']->symbol = ezcGraph::BULLET;
  62   *  $graph->data['Machine 2']->symbol = ezcGraph::BULLET;
  63   *  
  64   *  $graph->render( 400, 150, 'tutorial_axis_datetime.svg' );
  65   * </code>
  66   *
  67   * @property float $startDate
  68   *           Starting date used to display on axis.
  69   * @property float $endDate
  70   *           End date used to display on axis.
  71   * @property float $interval
  72   *           Time interval between steps on axis.
  73   * @property string $dateFormat
  74   *           Format of date string
  75   *           Like http://php.net/date
  76   *
  77   * @version 1.5
  78   * @package Graph
  79   * @mainclass
  80   */
  81  class ezcGraphChartElementDateAxis extends ezcGraphChartElementAxis
  82  {
  83      
  84      const MONTH = 2629800;
  85  
  86      const YEAR = 31536000;
  87  
  88      const DECADE = 315360000;
  89  
  90      /**
  91       * Minimum inserted date
  92       * 
  93       * @var int
  94       */
  95      protected $minValue = false;
  96  
  97      /**
  98       * Maximum inserted date 
  99       * 
 100       * @var int
 101       */
 102      protected $maxValue = false;
 103  
 104      /**
 105       * Nice time intervals to used if there is no user defined interval
 106       * 
 107       * @var array
 108       */
 109      protected $predefinedIntervals = array(
 110          // Second
 111          1           => 'H:i.s',
 112          // Ten seconds
 113          10          => 'H:i.s',
 114          // Thirty seconds
 115          30          => 'H:i.s',
 116          // Minute
 117          60          => 'H:i',
 118          // Ten minutes
 119          600         => 'H:i',
 120          // Half an hour
 121          1800        => 'H:i',
 122          // Hour
 123          3600        => 'H:i',
 124          // Four hours
 125          14400       => 'H:i',
 126          // Six hours
 127          21600       => 'H:i',
 128          // Half a day
 129          43200       => 'd.m a',
 130          // Day
 131          86400       => 'd.m',
 132          // Week
 133          604800      => 'W',
 134          // Month
 135          self::MONTH => 'M y',
 136          // Year
 137          self::YEAR  => 'Y',
 138          // Decade
 139          self::DECADE => 'Y',
 140      );
 141  
 142      /**
 143       * Constant used for calculation of automatic definition of major scaling 
 144       * steps
 145       */
 146      const MAJOR_COUNT = 10;
 147  
 148      /**
 149       * Constructor
 150       * 
 151       * @param array $options Default option array
 152       * @return void
 153       * @ignore
 154       */
 155      public function __construct( array $options = array() )
 156      {
 157          $this->properties['startDate'] = false;
 158          $this->properties['endDate'] = false;
 159          $this->properties['interval'] = false;
 160          $this->properties['dateFormat'] = false;
 161  
 162          parent::__construct( $options );
 163      }
 164  
 165      /**
 166       * __set 
 167       * 
 168       * @param mixed $propertyName 
 169       * @param mixed $propertyValue 
 170       * @throws ezcBaseValueException
 171       *          If a submitted parameter was out of range or type.
 172       * @throws ezcBasePropertyNotFoundException
 173       *          If a the value for the property options is not an instance of
 174       * @return void
 175       * @ignore
 176       */
 177      public function __set( $propertyName, $propertyValue )
 178      {
 179          switch ( $propertyName )
 180          {
 181              case 'startDate':
 182                  $this->properties['startDate'] = (int) $propertyValue;
 183                  break;
 184              case 'endDate':
 185                  $this->properties['endDate'] = (int) $propertyValue;
 186                  break;
 187              case 'interval':
 188                  $this->properties['interval'] = (int) $propertyValue;
 189                  $this->properties['initialized'] = true;
 190                  break;
 191              case 'dateFormat':
 192                  $this->properties['dateFormat'] = (string) $propertyValue;
 193                  break;
 194              default:
 195                  parent::__set( $propertyName, $propertyValue );
 196                  break;
 197          }
 198      }
 199  
 200      /**
 201       * Ensure proper timestamp
 202       *
 203       * Takes a mixed value from datasets, like timestamps, or strings 
 204       * describing some time and converts it to a timestamp.
 205       * 
 206       * @param mixed $value 
 207       * @return int
 208       */
 209      protected static function ensureTimestamp( $value )
 210      {
 211          if ( is_numeric( $value ) )
 212          {
 213              $timestamp = (int) $value;
 214          }
 215          elseif ( ( $timestamp = strtotime( $value ) ) === false )
 216          {
 217              throw new ezcGraphErrorParsingDateException( $value );
 218          }
 219  
 220          return $timestamp;
 221      }
 222  
 223      /**
 224       * Add data for this axis
 225       * 
 226       * @param array $values Value which will be displayed on this axis
 227       * @return void
 228       */
 229      public function addData( array $values )
 230      {
 231          foreach ( $values as $nr => $value )
 232          {
 233              $value = self::ensureTimestamp( $value );
 234  
 235              if ( $this->minValue === false ||
 236                   $value < $this->minValue )
 237              {
 238                  $this->minValue = $value;
 239              }
 240  
 241              if ( $this->maxValue === false ||
 242                   $value > $this->maxValue )
 243              {
 244                  $this->maxValue = $value;
 245              }
 246          }
 247  
 248          $this->properties['initialized'] = true;
 249      }
 250  
 251      /**
 252       * Calculate nice time interval
 253       *
 254       * Use the best fitting time interval defined in class property array
 255       * predefinedIntervals.
 256       * 
 257       * @param int $min Start time
 258       * @param int $max End time
 259       * @return void
 260       */
 261      protected function calculateInterval( $min, $max )
 262      {
 263          $diff = $max - $min;
 264  
 265          foreach ( $this->predefinedIntervals as $interval => $format )
 266          {
 267              if ( ( $diff / $interval ) <= self::MAJOR_COUNT )
 268              {
 269                  break;
 270              }
 271          }
 272  
 273          if ( ( $this->properties['startDate'] !== false ) &&
 274               ( $this->properties['endDate'] !== false ) )
 275          {
 276              // Use interval between defined borders
 277              if ( ( $diff % $interval ) > 0 )
 278              {
 279                  // Stil use predefined date format from old interval if not set
 280                  if ( $this->properties['dateFormat'] === false )
 281                  {
 282                      $this->properties['dateFormat'] = $this->predefinedIntervals[$interval];
 283                  }
 284  
 285                  $count = ceil( $diff / $interval );
 286                  $interval = round( $diff / $count, 0 );
 287              }
 288          }
 289  
 290          $this->properties['interval'] = $interval;
 291      }
 292  
 293      /**
 294       * Calculate lower nice date
 295       *
 296       * Calculates a date which is earlier or equal to the given date, and is
 297       * divisible by the given interval.
 298       * 
 299       * @param int $min Date
 300       * @param int $interval Interval 
 301       * @return int Earlier date
 302       */
 303      protected function calculateLowerNiceDate( $min, $interval )
 304      {
 305          switch ( $interval )
 306          {
 307              case self::MONTH:
 308                  // Special handling for months - not covered by the default 
 309                  // algorithm 
 310                  return mktime(
 311                      1,
 312                      0,
 313                      0,
 314                      (int) date( 'm', $min ),
 315                      1,
 316                      (int) date( 'Y', $min )
 317                  );
 318              default:
 319                  $dateSteps = array( 60, 60, 24, 7, 52 );
 320  
 321                  $date = array(
 322                      (int) date( 's', $min ),
 323                      (int) date( 'i', $min ),
 324                      (int) date( 'H', $min ),
 325                      (int) date( 'd', $min ),
 326                      (int) date( 'm', $min ),
 327                      (int) date( 'Y', $min ),
 328                  );
 329  
 330                  $element = 0;
 331                  while ( ( $step = array_shift( $dateSteps ) ) &&
 332                          ( $interval > $step ) )
 333                  {
 334                      $interval /= $step;
 335                      $date[$element++] = (int) ( $element > 2 );
 336                  }
 337  
 338                  $date[$element] -= $date[$element] % $interval;
 339  
 340                  return mktime(
 341                      $date[2],
 342                      $date[1],
 343                      $date[0],
 344                      $date[4],
 345                      $date[3],
 346                      $date[5]
 347                  );
 348          }
 349      }
 350  
 351      /**
 352       * Calculate start date
 353       *
 354       * Use calculateLowerNiceDate to get a date earlier or equal date then the 
 355       * minimum date to use it as the start date for the axis depending on the
 356       * selected interval.
 357       * 
 358       * @param mixed $min Minimum date
 359       * @param mixed $max Maximum date
 360       * @return void
 361       */
 362      public function calculateMinimum( $min, $max )
 363      {
 364          if ( $this->properties['endDate'] === false )
 365          {
 366              $this->properties['startDate'] = $this->calculateLowerNiceDate( $min, $this->interval );
 367          }
 368          else
 369          {
 370              $this->properties['startDate'] = $this->properties['endDate'];
 371  
 372              while ( $this->properties['startDate'] > $min )
 373              {
 374                  switch ( $this->interval )
 375                  {
 376                      case self::MONTH:
 377                          $this->properties['startDate'] = strtotime( '-1 month', $this->properties['startDate'] );
 378                          break;
 379                      case self::YEAR:
 380                          $this->properties['startDate'] = strtotime( '-1 year', $this->properties['startDate'] );
 381                          break;
 382                      case self::DECADE:
 383                          $this->properties['startDate'] = strtotime( '-10 years', $this->properties['startDate'] );
 384                          break;
 385                      default:
 386                          $this->properties['startDate'] -= $this->interval;
 387                  }
 388              }
 389          }
 390      }
 391  
 392      /**
 393       * Calculate end date
 394       *
 395       * Use calculateLowerNiceDate to get a date later or equal date then the 
 396       * maximum date to use it as the end date for the axis depending on the
 397       * selected interval.
 398       * 
 399       * @param mixed $min Minimum date
 400       * @param mixed $max Maximum date
 401       * @return void
 402       */
 403      public function calculateMaximum( $min, $max )
 404      {
 405          $this->properties['endDate'] = $this->properties['startDate'];
 406  
 407          while ( $this->properties['endDate'] < $max )
 408          {
 409              switch ( $this->interval )
 410              {
 411                  case self::MONTH:
 412                      $this->properties['endDate'] = strtotime( '+1 month', $this->properties['endDate'] );
 413                      break;
 414                  case self::YEAR:
 415                      $this->properties['endDate'] = strtotime( '+1 year', $this->properties['endDate'] );
 416                      break;
 417                  case self::DECADE:
 418                      $this->properties['endDate'] = strtotime( '+10 years', $this->properties['endDate'] );
 419                      break;
 420                  default:
 421                      $this->properties['endDate'] += $this->interval;
 422              }
 423          }
 424      }
 425  
 426      /**
 427       * Calculate axis bounding values on base of the assigned values 
 428       * 
 429       * @return void
 430       */
 431      public function calculateAxisBoundings()
 432      {
 433          // Prevent division by zero, when min == max
 434          if ( $this->minValue == $this->maxValue )
 435          {
 436              if ( $this->minValue == 0 )
 437              {
 438                  $this->maxValue = 1;
 439              }
 440              else
 441              {
 442                  $this->minValue -= ( $this->minValue * .1 );
 443                  $this->maxValue += ( $this->maxValue * .1 );
 444              }
 445          }
 446  
 447          // Use custom minimum and maximum if available
 448          if ( $this->properties['startDate'] !== false )
 449          {
 450              $this->minValue = $this->properties['startDate'];
 451          }
 452  
 453          if ( $this->properties['endDate'] !== false )
 454          {
 455              $this->maxValue = $this->properties['endDate'];
 456          }
 457  
 458          // Calculate "nice" values for scaling parameters
 459          if ( $this->properties['interval'] === false )
 460          {
 461              $this->calculateInterval( $this->minValue, $this->maxValue );
 462          }
 463  
 464          if ( $this->properties['dateFormat'] === false && isset( $this->predefinedIntervals[$this->interval] ) )
 465          {
 466              $this->properties['dateFormat'] = $this->predefinedIntervals[$this->interval];
 467          }
 468  
 469          if ( $this->properties['startDate'] === false )
 470          {
 471              $this->calculateMinimum( $this->minValue, $this->maxValue );
 472          }
 473  
 474          if ( $this->properties['endDate'] === false )
 475          {
 476              $this->calculateMaximum( $this->minValue, $this->maxValue );
 477          }
 478      }
 479  
 480      /**
 481       * Get coordinate for a dedicated value on the chart
 482       * 
 483       * @param float $value Value to determine position for
 484       * @return float Position on chart
 485       */
 486      public function getCoordinate( $value )
 487      {
 488          // Force typecast, because ( false < -100 ) results in (bool) true
 489          $intValue = ( $value === false ? false : self::ensureTimestamp( $value ) );
 490  
 491          if ( ( $value === false ) &&
 492               ( ( $intValue < $this->startDate ) || ( $intValue > $this->endDate ) ) )
 493          {
 494              switch ( $this->position )
 495              {
 496                  case ezcGraph::LEFT:
 497                  case ezcGraph::TOP:
 498                      return 0.;
 499                  case ezcGraph::RIGHT:
 500                  case ezcGraph::BOTTOM:
 501                      return 1.;
 502              }
 503          }
 504          else
 505          {
 506              switch ( $this->position )
 507              {
 508                  case ezcGraph::LEFT:
 509                  case ezcGraph::TOP:
 510                      return ( $intValue - $this->startDate ) / ( $this->endDate - $this->startDate );
 511                  case ezcGraph::RIGHT:
 512                  case ezcGraph::BOTTOM:
 513                      return 1 - ( $intValue - $this->startDate ) / ( $this->endDate - $this->startDate );
 514              }
 515          }
 516      }
 517  
 518      /**
 519       * Return count of minor steps
 520       * 
 521       * @return integer Count of minor steps
 522       */
 523      public function getMinorStepCount()
 524      {
 525          return false;
 526      }
 527  
 528      /**
 529       * Return count of major steps
 530       * 
 531       * @return integer Count of major steps
 532       */
 533      public function getMajorStepCount()
 534      {
 535          return (int) ceil( ( $this->properties['endDate'] - $this->startDate ) / $this->interval );
 536      }
 537  
 538      /**
 539       * Get label for a dedicated step on the axis
 540       * 
 541       * @param integer $step Number of step
 542       * @return string label
 543       */
 544      public function getLabel( $step )
 545      {
 546          return $this->getLabelFromTimestamp( $this->startDate + ( $step * $this->interval ), $step );
 547      }
 548  
 549      /**
 550       * Get label for timestamp
 551       * 
 552       * @param int $time
 553       * @param int $step
 554       * @return string
 555       */
 556      protected function getLabelFromTimestamp( $time, $step )
 557      {
 558          if ( $this->properties['labelCallback'] !== null )
 559          {
 560              return call_user_func_array(
 561                  $this->properties['labelCallback'],
 562                  array(
 563                      date( $this->properties['dateFormat'], $time ),
 564                      $step,
 565                  )
 566              );
 567          }
 568          else
 569          {
 570              return date( $this->properties['dateFormat'], $time );
 571          }
 572      }
 573  
 574      /**
 575       * Return array of steps on this axis
 576       * 
 577       * @return array( ezcGraphAxisStep )
 578       */
 579      public function getSteps()
 580      {
 581          $steps = array();
 582  
 583          $start = $this->properties['startDate'];
 584          $end = $this->properties['endDate'];
 585          $distance = $end - $start;
 586  
 587          $step = 0;
 588          for ( $time = $start; $time <= $end; )
 589          {
 590              $steps[] = new ezcGraphAxisStep(
 591                  ( $time - $start ) / $distance,
 592                  $this->interval / $distance,
 593                  $this->getLabelFromTimestamp( $time, $step++ ),
 594                  array(),
 595                  $step === 1,
 596                  $time >= $end
 597              );
 598  
 599              switch ( $this->interval )
 600              {
 601                  case self::MONTH:
 602                      $time = strtotime( '+1 month', $time );
 603                      break;
 604                  case self::YEAR:
 605                      $time = strtotime( '+1 year', $time );
 606                      break;
 607                  case self::DECADE:
 608                      $time = strtotime( '+10 years', $time );
 609                      break;
 610                  default:
 611                      $time += $this->interval;
 612                      break;
 613              }
 614          }
 615  
 616          return $steps;
 617      }
 618  
 619      /**
 620       * Is zero step
 621       *
 622       * Returns true if the given step is the one on the initial axis position
 623       * 
 624       * @param int $step Number of step
 625       * @return bool Status If given step is initial axis position
 626       */
 627      public function isZeroStep( $step )
 628      {
 629          return ( $step == 0 );
 630      }
 631  }
 632  
 633  ?>


Generated: Thu Jul 28 15:48:31 2011 Cross-referenced by PHPXref 0.7