HuasoFoundries/jpgraph

View on GitHub
src/graph/LinearTicks.php

Summary

Maintainability
F
4 days
Test Coverage
<?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