src/util/DateScaleUtils.php
<?php
/**
* JPGraph v4.0.3
*/
namespace Amenadiel\JpGraph\Util;
/*
* @class DateScaleUtils
* // Description: Help to create a manual date scale
*/
define('DSUTILS_MONTH', 1); // Major and minor ticks on a monthly basis
define('DSUTILS_MONTH1', 1); // Major and minor ticks on a monthly basis
define('DSUTILS_MONTH2', 2); // Major ticks on a bi-monthly basis
define('DSUTILS_MONTH3', 3); // Major icks on a tri-monthly basis
define('DSUTILS_MONTH6', 4); // Major on a six-monthly basis
define('DSUTILS_WEEK1', 5); // Major ticks on a weekly basis
define('DSUTILS_WEEK2', 6); // Major ticks on a bi-weekly basis
define('DSUTILS_WEEK4', 7); // Major ticks on a quod-weekly basis
define('DSUTILS_DAY1', 8); // Major ticks on a daily basis
define('DSUTILS_DAY2', 9); // Major ticks on a bi-daily basis
define('DSUTILS_DAY4', 10); // Major ticks on a qoud-daily basis
define('DSUTILS_YEAR1', 11); // Major ticks on a yearly basis
define('DSUTILS_YEAR2', 12); // Major ticks on a bi-yearly basis
define('DSUTILS_YEAR5', 13); // Major ticks on a five-yearly basis
class DateScaleUtils
{
public static $iMin = 0;
public static $iMax = 0;
private static $starthour;
private static $startmonth;
private static $startday;
private static $startyear;
private static $endmonth;
private static $endyear;
private static $endday;
private static $tickPositions = [];
private static $minTickPositions = [];
private static $iUseWeeks = true;
public static function UseWeekFormat($aFlg)
{
self::$iUseWeeks = $aFlg;
}
public static function doYearly($aType, $aMinor = false)
{
$i = 0;
$j = 0;
$m = self::$startmonth;
$y = self::$startyear;
if (self::$startday == 1) {
self::$tickPositions[$i++] = mktime(0, 0, 0, $m, 1, $y);
}
++$m;
switch ($aType) {
case DSUTILS_YEAR1:
for ($y = self::$startyear; $y <= self::$endyear; ++$y) {
if ($aMinor) {
while ($m <= 12) {
if (!($y == self::$endyear && $m > self::$endmonth)) {
self::$minTickPositions[$j++] = mktime(0, 0, 0, $m, 1, $y);
}
++$m;
}
$m = 1;
}
self::$tickPositions[$i++] = mktime(0, 0, 0, 1, 1, $y);
}
break;
case DSUTILS_YEAR2:
$y = self::$startyear;
while ($y <= self::$endyear) {
self::$tickPositions[$i++] = mktime(0, 0, 0, 1, 1, $y);
for ($k = 0; $k < 1; ++$k) {
++$y;
if ($aMinor) {
self::$minTickPositions[$j++] = mktime(0, 0, 0, 1, 1, $y);
}
}
++$y;
}
break;
case DSUTILS_YEAR5:
$y = self::$startyear;
while ($y <= self::$endyear) {
self::$tickPositions[$i++] = mktime(0, 0, 0, 1, 1, $y);
for ($k = 0; $k < 4; ++$k) {
++$y;
if ($aMinor) {
self::$minTickPositions[$j++] = mktime(0, 0, 0, 1, 1, $y);
}
}
++$y;
}
break;
}
}
public static function doDaily($aType, $aMinor = false)
{
$m = self::$startmonth;
$y = self::$startyear;
$d = self::$startday;
$h = self::$starthour;
$i = 0;
$j = 0;
if ($h == 0) {
self::$tickPositions[$i++] = mktime(0, 0, 0, $m, $d, $y);
}
$t = mktime(0, 0, 0, $m, $d, $y);
switch ($aType) {
case DSUTILS_DAY1:
while ($t <= self::$iMax) {
$t = strtotime('+1 day', $t);
self::$tickPositions[$i++] = $t;
if ($aMinor) {
self::$minTickPositions[$j++] = strtotime('+12 hours', $t);
}
}
break;
case DSUTILS_DAY2:
while ($t <= self::$iMax) {
$t = strtotime('+1 day', $t);
if ($aMinor) {
self::$minTickPositions[$j++] = $t;
}
$t = strtotime('+1 day', $t);
self::$tickPositions[$i++] = $t;
}
break;
case DSUTILS_DAY4:
while ($t <= self::$iMax) {
for ($k = 0; $k < 3; ++$k) {
$t = strtotime('+1 day', $t);
if ($aMinor) {
self::$minTickPositions[$j++] = $t;
}
}
$t = strtotime('+1 day', $t);
self::$tickPositions[$i++] = $t;
}
break;
}
}
public static function doWeekly($aType, $aMinor = false)
{
$hpd = 3600 * 24;
$hpw = 3600 * 24 * 7;
// Find out week number of min date
$thursday = self::$iMin + $hpd * (3 - (date('w', self::$iMin) + 6) % 7);
$week = 1 + (date('z', $thursday) - (11 - date('w', mktime(0, 0, 0, 1, 1, date('Y', $thursday)))) % 7) / 7;
$daynumber = date('w', self::$iMin);
if ($daynumber == 0) {
$daynumber = 7;
}
$m = self::$startmonth;
$y = self::$startyear;
$d = self::$startday;
$i = 0;
$j = 0;
// The assumption is that the weeks start on Monday. If the first day
// is later in the week then the first week tick has to be on the following
// week.
if ($daynumber == 1) {
self::$tickPositions[$i++] = mktime(0, 0, 0, $m, $d, $y);
$t = mktime(0, 0, 0, $m, $d, $y) + $hpw;
} else {
$t = mktime(0, 0, 0, $m, $d, $y) + $hpd * (8 - $daynumber);
}
switch ($aType) {
case DSUTILS_WEEK1:
$cnt = 0;
break;
case DSUTILS_WEEK2:
$cnt = 1;
break;
case DSUTILS_WEEK4:
$cnt = 3;
break;
}
while ($t <= self::$iMax) {
self::$tickPositions[$i++] = $t;
for ($k = 0; $k < $cnt; ++$k) {
$t += $hpw;
if ($aMinor) {
self::$minTickPositions[$j++] = $t;
}
}
$t += $hpw;
}
}
public static function doMonthly($aType, $aMinor = false)
{
$monthcount = 0;
$m = self::$startmonth;
$y = self::$startyear;
$i = 0;
$j = 0;
// Skip the first month label if it is before the startdate
if (self::$startday == 1) {
self::$tickPositions[$i++] = mktime(0, 0, 0, $m, 1, $y);
$monthcount = 1;
}
if ($aType == 1) {
if (self::$startday < 15) {
self::$minTickPositions[$j++] = mktime(0, 0, 0, $m, 15, $y);
}
}
++$m;
// Loop through all the years included in the scale
for ($y = self::$startyear; $y <= self::$endyear; ++$y) {
// Loop through all the months. There are three cases to consider:
// 1. We are in the first year and must start with the startmonth
// 2. We are in the end year and we must stop at last month of the scale
// 3. A year in between where we run through all the 12 months
$stopmonth = $y == self::$endyear ? self::$endmonth : 12;
while ($m <= $stopmonth) {
switch ($aType) {
case DSUTILS_MONTH1:
// Set minor tick at the middle of the month
if ($aMinor) {
if ($m <= $stopmonth) {
if (!($y == self::$endyear && $m == $stopmonth && self::$endday < 15)) {
self::$minTickPositions[$j++] = mktime(0, 0, 0, $m, 15, $y);
}
}
}
// Major at month
// Get timestamp of first hour of first day in each month
self::$tickPositions[$i++] = mktime(0, 0, 0, $m, 1, $y);
break;
case DSUTILS_MONTH2:
if ($aMinor) {
// Set minor tick at start of each month
self::$minTickPositions[$j++] = mktime(0, 0, 0, $m, 1, $y);
}
// Major at every second month
// Get timestamp of first hour of first day in each month
if ($monthcount % 2 == 0) {
self::$tickPositions[$i++] = mktime(0, 0, 0, $m, 1, $y);
}
break;
case DSUTILS_MONTH3:
if ($aMinor) {
// Set minor tick at start of each month
self::$minTickPositions[$j++] = mktime(0, 0, 0, $m, 1, $y);
}
// Major at every third month
// Get timestamp of first hour of first day in each month
if ($monthcount % 3 == 0) {
self::$tickPositions[$i++] = mktime(0, 0, 0, $m, 1, $y);
}
break;
case DSUTILS_MONTH6:
if ($aMinor) {
// Set minor tick at start of each month
self::$minTickPositions[$j++] = mktime(0, 0, 0, $m, 1, $y);
}
// Major at every third month
// Get timestamp of first hour of first day in each month
if ($monthcount % 6 == 0) {
self::$tickPositions[$i++] = mktime(0, 0, 0, $m, 1, $y);
}
break;
}
++$m;
++$monthcount;
}
$m = 1;
}
// For the case where all dates are within the same month
// we want to make sure we have at least two ticks on the scale
// since the scale want work properly otherwise
if (self::$startmonth == self::$endmonth && self::$startyear == self::$endyear && $aType == 1) {
self::$tickPositions[$i++] = mktime(0, 0, 0, self::$startmonth + 1, 1, self::$startyear);
}
return [self::$tickPositions, self::$minTickPositions];
}
public static function GetTicks($aData, $aType = 1, $aMinor = false, $aEndPoints = false)
{
$n = safe_count($aData);
return self::GetTicksFromMinMax($aData[0], $aData[$n - 1], $aType, $aMinor, $aEndPoints);
}
public static function GetAutoTicks($aMin, $aMax, $aMaxTicks = 10, $aMinor = false)
{
$diff = $aMax - $aMin;
$spd = 3600 * 24;
$spw = $spd * 7;
$spm = $spd * 30;
$spy = $spd * 352;
if (self::$iUseWeeks) {
$w = 'W';
} else {
$w = 'd M';
}
// Decision table for suitable scales
// First value: Main decision point
// Second value: Array of formatting depending on divisor for wanted max number of ticks. <divisor><formatting><format-string>,..
$tt = [
[$spw, [1, DSUTILS_DAY1, 'd M', 2, DSUTILS_DAY2, 'd M', -1, DSUTILS_DAY4, 'd M']],
[$spm, [1, DSUTILS_DAY1, 'd M', 2, DSUTILS_DAY2, 'd M', 4, DSUTILS_DAY4, 'd M', 7, DSUTILS_WEEK1, $w, -1, DSUTILS_WEEK2, $w]],
[$spy, [1, DSUTILS_DAY1, 'd M', 2, DSUTILS_DAY2, 'd M', 4, DSUTILS_DAY4, 'd M', 7, DSUTILS_WEEK1, $w, 14, DSUTILS_WEEK2, $w, 30, DSUTILS_MONTH1, 'M', 60, DSUTILS_MONTH2, 'M', -1, DSUTILS_MONTH3, 'M']],
[-1, [30, DSUTILS_MONTH1, 'M-Y', 60, DSUTILS_MONTH2, 'M-Y', 90, DSUTILS_MONTH3, 'M-Y', 180, DSUTILS_MONTH6, 'M-Y', 352, DSUTILS_YEAR1, 'Y', 704, DSUTILS_YEAR2, 'Y', -1, DSUTILS_YEAR5, 'Y']], ];
$ntt = safe_count($tt);
$nd = floor($diff / $spd);
for ($i = 0; $i < $ntt; ++$i) {
if ($diff <= $tt[$i][0] || $i == $ntt - 1) {
$t = $tt[$i][1];
$n = safe_count($t) / 3;
for ($j = 0; $j < $n; ++$j) {
if ($nd / $t[3 * $j] <= $aMaxTicks || $j == $n - 1) {
$type = $t[3 * $j + 1];
$fs = $t[3 * $j + 2];
list($tickPositions, $minTickPositions) = self::GetTicksFromMinMax($aMin, $aMax, $type, $aMinor);
return [$fs, $tickPositions, $minTickPositions, $type];
}
}
}
}
}
public static function GetTicksFromMinMax($aMin, $aMax, $aType, $aMinor = false, $aEndPoints = false)
{
self::$starthour = date('G', $aMin);
self::$startmonth = date('n', $aMin);
self::$startday = date('j', $aMin);
self::$startyear = date('Y', $aMin);
self::$endmonth = date('n', $aMax);
self::$endyear = date('Y', $aMax);
self::$endday = date('j', $aMax);
self::$iMin = $aMin;
self::$iMax = $aMax;
if ($aType <= DSUTILS_MONTH6) {
self::doMonthly($aType, $aMinor);
} elseif ($aType <= DSUTILS_WEEK4) {
self::doWeekly($aType, $aMinor);
} elseif ($aType <= DSUTILS_DAY4) {
self::doDaily($aType, $aMinor);
} elseif ($aType <= DSUTILS_YEAR5) {
self::doYearly($aType, $aMinor);
} else {
JpGraphError::RaiseL(24003);
}
// put a label at the very left data pos
if ($aEndPoints) {
$tickPositions[$i++] = $aData[0];
}
// put a label at the very right data pos
if ($aEndPoints) {
$tickPositions[$i] = $aData[$n - 1];
}
return [self::$tickPositions, self::$minTickPositions];
}
}