src/graph/LinearTicks.php
<?php
/**
* JPGraph v4.0.3
*/
namespace Amenadiel\JpGraph\Graph;
use Amenadiel\JpGraph\Util;
/**
* @class LinearTicks
* // Description: Draw linear ticks on axis
*/
class LinearTicks extends Ticks
{
public $minor_step = 1;
public $major_step = 2;
public $xlabel_offset = 0;
public $xtick_offset = 0;
private $label_offset = 0; // What offset should the displayed label have
// i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc
private $text_label_start = 0;
private $iManualTickPos;
private $iManualMinTickPos;
private $iManualTickLabels;
private $iAdjustForDST = false; // If a date falls within the DST period add one hour to the diaplyed time
public function __construct()
{
$this->precision = -1;
}
// Return major step size in world coordinates
public function GetMajor()
{
return $this->major_step;
}
// Return minor step size in world coordinates
public function GetMinor()
{
return $this->minor_step;
}
// Set Minor and Major ticks (in world coordinates)
public function Set($aMajStep, $aMinStep = false)
{
if ($aMinStep == false) {
$aMinStep = $aMajStep;
}
if ($aMajStep <= 0 || $aMinStep <= 0) {
Util\JpGraphError::RaiseL(25064);
//(" Minor or major step size is 0. Check that you haven't got an accidental SetTextTicks(0) in your code. If this is not the case you might have stumbled upon a bug in JpGraph. Please report this and if possible include the data that caused the problem.");
}
$this->major_step = $aMajStep;
$this->minor_step = $aMinStep;
$this->is_set = true;
}
public function SetMajTickPositions($aMajPos, $aLabels = null)
{
$this->SetTickPositions($aMajPos, null, $aLabels);
}
public function SetTickPositions($aMajPos, $aMinPos = null, $aLabels = null)
{
if (!is_array($aMajPos) || ($aMinPos !== null && !is_array($aMinPos))) {
Util\JpGraphError::RaiseL(25065); //('Tick positions must be specifued as an array()');
return;
}
$n = safe_count($aMajPos);
if (is_array($aLabels) && (safe_count($aLabels) != $n)) {
Util\JpGraphError::RaiseL(25066); //('When manually specifying tick positions and labels the number of labels must be the same as the number of specified ticks.');
}
$this->iManualTickPos = $aMajPos;
$this->iManualMinTickPos = $aMinPos;
$this->iManualTickLabels = $aLabels;
}
public function HaveManualLabels()
{
return safe_count($this->iManualTickLabels) > 0;
}
// Specify all the tick positions manually and possible also the exact labels
public function _doManualTickPos($aScale)
{
$n = safe_count($this->iManualTickPos);
$m = safe_count($this->iManualMinTickPos);
$doLbl = safe_count($this->iManualTickLabels) > 0;
$this->maj_ticks_pos = [];
$this->maj_ticklabels_pos = [];
$this->ticks_pos = [];
// Now loop through the supplied positions and translate them to screen coordinates
// and store them in the maj_label_positions
$minScale = $aScale->scale[0];
$maxScale = $aScale->scale[1];
$j = 0;
for ($i = 0; $i < $n; ++$i) {
// First make sure that the first tick is not lower than the lower scale value
if (!isset($this->iManualTickPos[$i]) || $this->iManualTickPos[$i] < $minScale || $this->iManualTickPos[$i] > $maxScale) {
continue;
}
$this->maj_ticks_pos[$j] = $aScale->Translate($this->iManualTickPos[$i]);
$this->maj_ticklabels_pos[$j] = $this->maj_ticks_pos[$j];
// Set the minor tick marks the same as major if not specified
if ($m <= 0) {
$this->ticks_pos[$j] = $this->maj_ticks_pos[$j];
}
if ($doLbl) {
$this->maj_ticks_label[$j] = $this->iManualTickLabels[$i];
} else {
$this->maj_ticks_label[$j] = $this->_doLabelFormat($this->iManualTickPos[$i], $i, $n);
}
++$j;
}
// Some sanity check
if (safe_count($this->maj_ticks_pos) < 2) {
Util\JpGraphError::RaiseL(25067); //('Your manually specified scale and ticks is not correct. The scale seems to be too small to hold any of the specified tickl marks.');
}
// Setup the minor tick marks
$j = 0;
for ($i = 0; $i < $m; ++$i) {
if (empty($this->iManualMinTickPos[$i]) || $this->iManualMinTickPos[$i] < $minScale || $this->iManualMinTickPos[$i] > $maxScale) {
continue;
}
$this->ticks_pos[$j] = $aScale->Translate($this->iManualMinTickPos[$i]);
++$j;
}
}
public function _doAutoTickPos($aScale)
{
$maj_step_abs = $aScale->scale_factor * $this->major_step;
$min_step_abs = $aScale->scale_factor * $this->minor_step;
if ($min_step_abs == 0 || $maj_step_abs == 0) {
Util\JpGraphError::RaiseL(25068); //("A plot has an illegal scale. This could for example be that you are trying to use text autoscaling to draw a line plot with only one point or that the plot area is too small. It could also be that no input data value is numeric (perhaps only '-' or 'x')");
}
// We need to make this an int since comparing it below
// with the result from round() can give wrong result, such that
// (40 < 40) == TRUE !!!
$limit = (int) $aScale->scale_abs[1];
if ($aScale->textscale) {
// This can only be true for a X-scale (horizontal)
// Define ticks for a text scale. This is slightly different from a
// normal linear type of scale since the position might be adjusted
// and the labels start at on
$label = (float) $aScale->GetMinVal() + $this->text_label_start + $this->label_offset;
$start_abs = $aScale->scale_factor * $this->text_label_start;
$nbrmajticks = round(($aScale->GetMaxVal() - $aScale->GetMinVal() - $this->text_label_start) / $this->major_step) + 1;
$x = $aScale->scale_abs[0] + $start_abs + $this->xlabel_offset * $min_step_abs;
for ($i = 0; $label <= $aScale->GetMaxVal() + $this->label_offset; ++$i) {
// Apply format to label
$this->maj_ticks_label[$i] = $this->_doLabelFormat($label, $i, $nbrmajticks);
$label += $this->major_step;
// The x-position of the tick marks can be different from the labels.
// Note that we record the tick position (not the label) so that the grid
// happen upon tick marks and not labels.
$xtick = $aScale->scale_abs[0] + $start_abs + $this->xtick_offset * $min_step_abs + $i * $maj_step_abs;
$this->maj_ticks_pos[$i] = $xtick;
$this->maj_ticklabels_pos[$i] = round($x);
$x += $maj_step_abs;
}
} else {
$label = $aScale->GetMinVal();
$abs_pos = $aScale->scale_abs[0];
$j = 0;
$i = 0;
$step = round($maj_step_abs / $min_step_abs);
if ($aScale->type == 'x') {
// For a normal linear type of scale the major ticks will always be multiples
// of the minor ticks. In order to avoid any rounding issues the major ticks are
// defined as every "step" minor ticks and not calculated separately
$nbrmajticks = round(($aScale->GetMaxVal() - $aScale->GetMinVal() - $this->text_label_start) / $this->major_step) + 1;
while (round($abs_pos) <= $limit) {
$this->ticks_pos[] = round($abs_pos);
$this->ticks_label[] = $label;
if ($step == 0 || $i % $step == 0 && $j < $nbrmajticks) {
$this->maj_ticks_pos[$j] = round($abs_pos);
$this->maj_ticklabels_pos[$j] = round($abs_pos);
$this->maj_ticks_label[$j] = $this->_doLabelFormat($label, $j, $nbrmajticks);
++$j;
}
++$i;
$abs_pos += $min_step_abs;
$label += $this->minor_step;
}
} elseif ($aScale->type == 'y') {
//@todo s=2:20,12 s=1:50,6 $this->major_step:$nbr
// abs_point,limit s=1:270,80 s=2:540,160
// $this->major_step = 50;
$nbrmajticks = round(($aScale->GetMaxVal() - $aScale->GetMinVal()) / $this->major_step) + 1;
// $step = 5;
while (round($abs_pos) >= $limit) {
$this->ticks_pos[$i] = round($abs_pos);
$this->ticks_label[$i] = $label;
if ($step == 0 || $i % $step == 0 && $j < $nbrmajticks) {
$this->maj_ticks_pos[$j] = round($abs_pos);
$this->maj_ticklabels_pos[$j] = round($abs_pos);
$this->maj_ticks_label[$j] = $this->_doLabelFormat($label, $j, $nbrmajticks);
++$j;
}
++$i;
$abs_pos += $min_step_abs;
$label += $this->minor_step;
}
}
}
}
public function AdjustForDST($aFlg = true)
{
$this->iAdjustForDST = $aFlg;
}
public function _doLabelFormat($aVal, $aIdx, $aNbrTicks)
{
// If precision hasn't been specified set it to a sensible value
if ($this->precision == -1) {
$t = log10($this->minor_step);
if ($t > 0) {
$precision = 0;
} else {
$precision = -floor($t);
}
} else {
$precision = $this->precision;
}
if ($this->label_formfunc != '') {
$f = $this->label_formfunc;
if ($this->label_formatstr == '') {
$l = call_user_func($f, $aVal);
} else {
$l = sprintf($this->label_formatstr, call_user_func($f, $aVal));
}
} elseif ($this->label_formatstr != '' || $this->label_dateformatstr != '') {
if ($this->label_usedateformat) {
// Adjust the value to take daylight savings into account
if (date('I', $aVal) == 1 && $this->iAdjustForDST) {
// DST
$aVal += 3600;
}
$l = date($this->label_formatstr, $aVal);
if ($this->label_formatstr == 'W') {
// If we use week formatting then add a single 'w' in front of the
// week number to differentiate it from dates
$l = 'w' . $l;
}
} else {
if ($this->label_dateformatstr !== '') {
// Adjust the value to take daylight savings into account
if (date('I', $aVal) == 1 && $this->iAdjustForDST) {
// DST
$aVal += 3600;
}
$l = date($this->label_dateformatstr, $aVal);
if ($this->label_formatstr == 'W') {
// If we use week formatting then add a single 'w' in front of the
// week number to differentiate it from dates
$l = 'w' . $l;
}
} else {
$l = sprintf($this->label_formatstr, $aVal);
}
}
} else {
//FIX: if negative precision is returned "0f" , instead of formatted values
$format = $precision > 0 ? '%01.' . $precision . 'f' : '%01.0f';
$l = sprintf($format, round($aVal, $precision));
}
if (($this->supress_zerolabel && $l == 0) || ($this->supress_first && $aIdx == 0) || ($this->supress_last && $aIdx == $aNbrTicks - 1)) {
$l = '';
}
return $l;
}
// Stroke ticks on either X or Y axis
public function _StrokeTicks($aImg, $aScale, $aPos)
{
$hor = $aScale->type == 'x';
$aImg->SetLineWeight($this->weight);
// We need to make this an int since comparing it below
// with the result from round() can give wrong result, such that
// (40 < 40) == TRUE !!!
$limit = (int) $aScale->scale_abs[1];
// A text scale doesn't have any minor ticks
if (!$aScale->textscale) {
// Stroke minor ticks
$yu = $aPos - $this->direction * $this->GetMinTickAbsSize();
$xr = $aPos + $this->direction * $this->GetMinTickAbsSize();
$n = safe_count($this->ticks_pos);
for ($i = 0; $i < $n; ++$i) {
if (!$this->supress_tickmarks && !$this->supress_minor_tickmarks) {
if ($this->mincolor != '') {
$aImg->PushColor($this->mincolor);
}
if ($hor) {
//if( $this->ticks_pos[$i] <= $limit )
$aImg->Line($this->ticks_pos[$i], $aPos, $this->ticks_pos[$i], $yu);
} else {
//if( $this->ticks_pos[$i] >= $limit )
$aImg->Line($aPos, $this->ticks_pos[$i], $xr, $this->ticks_pos[$i]);
}
if ($this->mincolor != '') {
$aImg->PopColor();
}
}
}
}
// Stroke major ticks
$yu = $aPos - $this->direction * $this->GetMajTickAbsSize();
$xr = $aPos + $this->direction * $this->GetMajTickAbsSize();
$nbrmajticks = round(($aScale->GetMaxVal() - $aScale->GetMinVal() - $this->text_label_start) / $this->major_step) + 1;
$n = safe_count($this->maj_ticks_pos);
for ($i = 0; $i < $n; ++$i) {
if (!($this->xtick_offset > 0 && $i == $nbrmajticks - 1) && !$this->supress_tickmarks) {
if ($this->majcolor != '') {
$aImg->PushColor($this->majcolor);
}
if ($hor) {
//if( $this->maj_ticks_pos[$i] <= $limit )
$aImg->Line($this->maj_ticks_pos[$i], $aPos, $this->maj_ticks_pos[$i], $yu);
} else {
//if( $this->maj_ticks_pos[$i] >= $limit )
$aImg->Line($aPos, $this->maj_ticks_pos[$i], $xr, $this->maj_ticks_pos[$i]);
}
if ($this->majcolor != '') {
$aImg->PopColor();
}
}
}
}
// Draw linear ticks
public function Stroke($aImg, $aScale, $aPos)
{
if ($this->iManualTickPos != null) {
$this->_doManualTickPos($aScale);
} else {
$this->_doAutoTickPos($aScale);
}
$this->_StrokeTicks($aImg, $aScale, $aPos, $aScale->type == 'x');
}
/**
* PRIVATE METHODS.
*
* @param mixed $aLabelOff
* @param mixed $aTickOff
*/
// Spoecify the offset of the displayed tick mark with the tick "space"
// Legal values for $o is [0,1] used to adjust where the tick marks and label
// should be positioned within the major tick-size
// $lo specifies the label offset and $to specifies the tick offset
// this comes in handy for example in bar graphs where we wont no offset for the
// tick but have the labels displayed halfway under the bars.
public function SetXLabelOffset($aLabelOff, $aTickOff = -1)
{
$this->xlabel_offset = $aLabelOff;
if ($aTickOff == -1) {
// Same as label offset
$this->xtick_offset = $aLabelOff;
} else {
$this->xtick_offset = $aTickOff;
}
if ($aLabelOff > 0) {
$this->SupressLast(); // The last tick wont fit
}
}
// Which tick label should we start with?
public function SetTextLabelStart($aTextLabelOff)
{
$this->text_label_start = $aTextLabelOff;
}
} // @class