HuasoFoundries/jpgraph

View on GitHub
src/plot/LinePlot.php

Summary

Maintainability
F
4 days
Test Coverage
<?php

/**
 * JPGraph v4.0.3
 */

namespace Amenadiel\JpGraph\Plot;

use Amenadiel\JpGraph\Util;

/*
 * File:           JPGRAPH_LINE.PHP
 * // Description: Line plot extension for JpGraph
 * // Created:       2001-01-08
 * // Ver:           $Id: jpgraph_line.php 1921 2009-12-11 11:46:39Z ljp $
 * //
 * // Copyright (c) Asial Corporation. All rights reserved.
 */
// constants for the (filled) area
define('LP_AREA_FILLED', true);
define('LP_AREA_NOT_FILLED', false);
define('LP_AREA_BORDER', false);
define('LP_AREA_NO_BORDER', true);

/**
 * @class LinePlot
 * // Description:
 */
class LinePlot extends Plot
{
    public $mark;
    protected $filled             = false;
    protected $fill_color         = 'blue';
    protected $step_style         = false;
    protected $center             = false;
    protected $line_style         = 1; // Default to solid
    protected $filledAreas        = []; // array of arrays(with min,max,col,filled in them)
    public $barcenter             = false; // When we mix line and bar. Should we center the line in the bar.
    protected $fillFromMin        = false;
    protected $fillFromMax        = false;
    protected $fillgrad           = false;
    protected $fillgrad_fromcolor = 'navy';
    protected $fillgrad_tocolor   = 'silver';
    protected $fillgrad_numcolors = 100;
    protected $iFastStroke        = false;

    /**
     * CONSTRUCTOR.
     *
     * @param mixed $datay
     * @param mixed $datax
     */
    public function __construct($datay, $datax = false)
    {
        parent::__construct($datay, $datax);
        $this->mark       = new PlotMark();
        $this->color      = Util\ColorFactory::getColor();
        $this->fill_color = $this->color;
    }

    /**
     * PUBLIC METHODS.
     *
     * @param mixed $aFlg
     */
    public function SetFilled($aFlg = true)
    {
        $this->filled = $aFlg;
    }

    public function SetBarCenter($aFlag = true)
    {
        $this->barcenter = $aFlag;
    }

    public function SetStyle($aStyle)
    {
        $this->line_style = $aStyle;
    }

    public function SetStepStyle($aFlag = true)
    {
        $this->step_style = $aFlag;
    }

    public function SetColor($aColor)
    {
        parent::SetColor($aColor);
    }

    public function SetFillFromYMin($f = true)
    {
        $this->fillFromMin = $f;
    }

    public function SetFillFromYMax($f = true)
    {
        $this->fillFromMax = $f;
    }

    public function SetFillColor($aColor, $aFilled = true)
    {
        //$this->color = $aColor;
        $this->fill_color = $aColor;
        $this->filled     = $aFilled;
    }

    public function SetFillGradient($aFromColor, $aToColor, $aNumColors = 100, $aFilled = true)
    {
        $this->fillgrad_fromcolor = $aFromColor;
        $this->fillgrad_tocolor   = $aToColor;
        $this->fillgrad_numcolors = $aNumColors;
        $this->filled             = $aFilled;
        $this->fillgrad           = true;
    }

    public function Legend($graph)
    {
        if ($this->legend != '') {
            if ($this->filled && !$this->fillgrad) {
                $graph->legend->Add(
                    $this->legend,
                    $this->fill_color,
                    $this->mark,
                    0,
                    $this->legendcsimtarget,
                    $this->legendcsimalt,
                    $this->legendcsimwintarget
                );
            } elseif ($this->fillgrad) {
                $color = [$this->fillgrad_fromcolor, $this->fillgrad_tocolor];
                // In order to differentiate between gradients and cooors specified as an Image\RGB triple
                $graph->legend->Add(
                    $this->legend,
                    $color,
                    '',
                    -2/* -GRAD_HOR */,
                    $this->legendcsimtarget,
                    $this->legendcsimalt,
                    $this->legendcsimwintarget
                );
            } else {
                $graph->legend->Add(
                    $this->legend,
                    $this->color,
                    $this->mark,
                    $this->line_style,
                    $this->legendcsimtarget,
                    $this->legendcsimalt,
                    $this->legendcsimwintarget
                );
            }
        }
    }

    public function AddArea($aMin = 0, $aMax = 0, $aFilled = LP_AREA_NOT_FILLED, $aColor = 'gray9', $aBorder = LP_AREA_BORDER)
    {
        if ($aMin > $aMax) {
            // swap
            $tmp  = $aMin;
            $aMin = $aMax;
            $aMax = $tmp;
        }
        $this->filledAreas[] = [$aMin, $aMax, $aColor, $aFilled, $aBorder];
    }

    // Gets called before any axis are stroked
    public function PreStrokeAdjust($graph)
    {
        // If another plot type have already adjusted the
        // offset we don't touch it.
        // (We check for empty in case the scale is  a log scale
        // and hence doesn't contain any xlabel_offset)
        if (empty($graph->xaxis->scale->ticks->xlabel_offset) || $graph->xaxis->scale->ticks->xlabel_offset == 0) {
            if ($this->center) {
                ++$this->numpoints;
                $a = 0.5;
                $b = 0.5;
            } else {
                $a = 0;
                $b = 0;
            }
            $graph->xaxis->scale->ticks->SetXLabelOffset($a);
            $graph->SetTextScaleOff($b);
            //$graph->xaxis->scale->ticks->SupressMinorTickMarks();
        }
    }

    public function SetFastStroke($aFlg = true)
    {
        $this->iFastStroke = $aFlg;
    }

    public function FastStroke($img, $xscale, $yscale, $aStartPoint = 0, $exist_x = true)
    {
        // An optimized stroke for many data points with no extra
        // features but 60% faster. You can't have values or line styles, or null
        // values in plots.
        $numpoints = safe_count($this->coords[0]);
        if ($this->barcenter) {
            $textadj = 0.5 - $xscale->text_scale_off;
        } else {
            $textadj = 0;
        }

        $img->SetColor($this->color);
        $img->SetLineWeight($this->weight);
        $pnts = $aStartPoint;
        while ($pnts < $numpoints) {
            if ($exist_x) {
                $x = $this->coords[1][$pnts];
            } else {
                $x = $pnts + $textadj;
            }
            $xt = $xscale->Translate($x);
            $y  = $this->coords[0][$pnts];
            $yt = $yscale->Translate($y);
            if (is_numeric($y)) {
                $cord[] = $xt;
                $cord[] = $yt;
            } elseif ($y == '-' && $pnts > 0) {
                // Just ignore
            } else {
                Util\JpGraphError::RaiseL(10002); //('Plot too complicated for fast line Stroke. Use standard Stroke()');
            }
            ++$pnts;
        } // WHILE

        $img->Polygon($cord, false, true);
    }

    public function Stroke($img, $xscale, $yscale)
    {
        $idx       = 0;
        $numpoints = safe_count($this->coords[0]);
        if (isset($this->coords[1])) {
            if (safe_count($this->coords[1]) != $numpoints) {
                Util\JpGraphError::RaiseL(2003, safe_count($this->coords[1]), $numpoints);
            //("Number of X and Y points are not equal. Number of X-points:". safe_count($this->coords[1])." Number of Y-points:$numpoints");
            } else {
                $exist_x = true;
            }
        } else {
            $exist_x = false;
        }

        if ($this->barcenter) {
            $textadj = 0.5 - $xscale->text_scale_off;
        } else {
            $textadj = 0;
        }

        // Find the first numeric data point
        $startpoint = 0;
        while ($startpoint < $numpoints && !is_numeric($this->coords[0][$startpoint])) {
            ++$startpoint;
        }

        // Bail out if no data points
        if ($startpoint == $numpoints) {
            return;
        }

        if ($this->iFastStroke) {
            $this->FastStroke($img, $xscale, $yscale, $startpoint, $exist_x);

            return;
        }

        if ($exist_x) {
            $xs = $this->coords[1][$startpoint];
        } else {
            $xs = $textadj + $startpoint;
        }

        $img->SetStartPoint(
            $xscale->Translate($xs),
            $yscale->Translate($this->coords[0][$startpoint])
        );

        if ($this->filled) {
            if ($this->fillFromMax) {
                //$max = $yscale->GetMaxVal();
                $cord[$idx++] = $xscale->Translate($xs);
                $cord[$idx++] = $yscale->scale_abs[1];
            } else {
                $min = $yscale->GetMinVal();
                if ($min > 0 || $this->fillFromMin) {
                    $fillmin = $yscale->scale_abs[0]; //Translate($min);
                } else {
                    $fillmin = $yscale->Translate(0);
                }

                $cord[$idx++] = $xscale->Translate($xs);
                $cord[$idx++] = $fillmin;
            }
        }
        $xt           = $xscale->Translate($xs);
        $yt           = $yscale->Translate($this->coords[0][$startpoint]);
        $cord[$idx++] = $xt;
        $cord[$idx++] = $yt;
        $yt_old       = $yt;
        $xt_old       = $xt;
        $y_old        = $this->coords[0][$startpoint];

        $this->value->Stroke($img, $this->coords[0][$startpoint], $xt, $yt);

        $img->SetColor($this->color);
        $img->SetLineWeight($this->weight);
        $img->SetLineStyle($this->line_style);
        $pnts           = $startpoint + 1;
        $firstnonumeric = false;

        while ($pnts < $numpoints) {
            if ($exist_x) {
                $x = $this->coords[1][$pnts];
            } else {
                $x = $pnts + $textadj;
            }
            $xt = $xscale->Translate($x);
            $yt = $yscale->Translate($this->coords[0][$pnts]);

            $y = $this->coords[0][$pnts];
            if ($this->step_style) {
                // To handle null values within step style we need to record the
                // first non numeric value so we know from where to start if the
                // non value is '-'.
                if (is_numeric($y)) {
                    $firstnonumeric = false;
                    if (is_numeric($y_old)) {
                        $img->StyleLine($xt_old, $yt_old, $xt, $yt_old);
                        $img->StyleLine($xt, $yt_old, $xt, $yt);
                    } elseif ($y_old == '-') {
                        $img->StyleLine($xt_first, $yt_first, $xt, $yt_first);
                        $img->StyleLine($xt, $yt_first, $xt, $yt);
                    } else {
                        $yt_old = $yt;
                        $xt_old = $xt;
                    }
                    $cord[$idx++] = $xt;
                    $cord[$idx++] = $yt_old;
                    $cord[$idx++] = $xt;
                    $cord[$idx++] = $yt;
                } elseif ($firstnonumeric == false) {
                    $firstnonumeric = true;
                    $yt_first       = $yt_old;
                    $xt_first       = $xt_old;
                }
            } else {
                $tmp1 = $y;
                $prev = $this->coords[0][$pnts - 1];
                if ($tmp1 === '' || $tmp1 === null || $tmp1 === 'X') {
                    $tmp1 = 'x';
                }

                if ($prev === '' || $prev === null || $prev === 'X') {
                    $prev = 'x';
                }

                if (is_numeric($y) || (is_string($y) && $y != '-')) {
                    if (is_numeric($y) && (is_numeric($prev) || $prev === '-')) {
                        $img->StyleLineTo($xt, $yt);
                    } else {
                        $img->SetStartPoint($xt, $yt);
                    }
                }
                if ($this->filled && $tmp1 !== '-') {
                    if ($tmp1 === 'x') {
                        $cord[$idx++] = $cord[$idx - 3];
                        $cord[$idx++] = $fillmin;
                    } elseif ($prev === 'x') {
                        $cord[$idx++] = $xt;
                        $cord[$idx++] = $fillmin;
                        $cord[$idx++] = $xt;
                        $cord[$idx++] = $yt;
                    } else {
                        $cord[$idx++] = $xt;
                        $cord[$idx++] = $yt;
                    }
                } else {
                    if (is_numeric($tmp1) && (is_numeric($prev) || $prev === '-')) {
                        $cord[$idx++] = $xt;
                        $cord[$idx++] = $yt;
                    }
                }
            }
            $yt_old = $yt;
            $xt_old = $xt;
            $y_old  = $y;

            $this->StrokeDataValue($img, $this->coords[0][$pnts], $xt, $yt);

            ++$pnts;
        }

        if ($this->filled) {
            $cord[$idx++] = $xt;
            if ($this->fillFromMax) {
                $cord[$idx++] = $yscale->scale_abs[1];
            } else {
                if ($min > 0 || $this->fillFromMin) {
                    $cord[$idx++] = $yscale->Translate($min);
                } else {
                    $cord[$idx++] = $yscale->Translate(0);
                }
            }
            if ($this->fillgrad) {
                $img->SetLineWeight(1);
                $grad = new Gradient($img);
                $grad->SetNumColors($this->fillgrad_numcolors);
                $grad->FilledFlatPolygon($cord, $this->fillgrad_fromcolor, $this->fillgrad_tocolor);
                $img->SetLineWeight($this->weight);
            } else {
                $img->SetColor($this->fill_color);
                $img->FilledPolygon($cord);
            }
            if ($this->weight > 0) {
                $img->SetLineWeight($this->weight);
                $img->SetColor($this->color);
                // Remove first and last coordinate before drawing the line
                // sine we otherwise get the vertical start and end lines which
                // doesn't look appropriate
                $img->Polygon(array_slice($cord, 2, safe_count($cord) - 4));
            }
        }

        if (!empty($this->filledAreas)) {
            $minY   = $yscale->Translate($yscale->GetMinVal());
            $factor = ($this->step_style ? 4 : 2);

            for ($i = 0; $i < safe_count($this->filledAreas); ++$i) {
                // go through all filled area elements ordered by insertion
                // fill polygon array
                $areaCoords[] = $cord[$this->filledAreas[$i][0] * $factor];
                $areaCoords[] = $minY;

                $areaCoords =
                    array_merge(
                        $areaCoords,
                        array_slice(
                            $cord,
                            $this->filledAreas[$i][0] * $factor,
                            ($this->filledAreas[$i][1] - $this->filledAreas[$i][0] + ($this->step_style ? 0 : 1)) * $factor
                        )
                    );
                $areaCoords[] = $areaCoords[safe_count($areaCoords) - 2]; // last x
                $areaCoords[] = $minY; // last y

                if ($this->filledAreas[$i][3]) {
                    $img->SetColor($this->filledAreas[$i][2]);
                    $img->FilledPolygon($areaCoords);
                    $img->SetColor($this->color);
                }
                // Check if we should draw the frame.
                // If not we still re-draw the line since it might have been
                // partially overwritten by the filled area and it doesn't look
                // very good.
                if ($this->filledAreas[$i][4]) {
                    $img->Polygon($areaCoords);
                } else {
                    $img->Polygon($cord);
                }

                $areaCoords = [];
            }
        }

        if (!is_object($this->mark) || $this->mark->type == -1 || $this->mark->show == false) {
            return;
        }

        for ($pnts = 0; $pnts < $numpoints; ++$pnts) {
            if ($exist_x) {
                $x = $this->coords[1][$pnts];
            } else {
                $x = $pnts + $textadj;
            }
            $xt = $xscale->Translate($x);
            $yt = $yscale->Translate($this->coords[0][$pnts]);

            if (is_numeric($this->coords[0][$pnts])) {
                if (!empty($this->csimtargets[$pnts])) {
                    if (!empty($this->csimwintargets[$pnts])) {
                        $this->mark->SetCSIMTarget($this->csimtargets[$pnts], $this->csimwintargets[$pnts]);
                    } else {
                        $this->mark->SetCSIMTarget($this->csimtargets[$pnts]);
                    }
                    $this->mark->SetCSIMAlt($this->csimalts[$pnts]);
                }
                if ($exist_x) {
                    $x = $this->coords[1][$pnts];
                } else {
                    $x = $pnts;
                }
                $this->mark->SetCSIMAltVal($this->coords[0][$pnts], $x);
                $this->mark->Stroke($img, $xt, $yt);
                $this->csimareas .= $this->mark->GetCSIMAreas();
            }
        }
    }
} // @class