vendor/amenadiel/jpgraph/src/graph/Legend.php line 228

  1. <?php
  2. /**
  3.  * JPGraph v4.0.3
  4.  */
  5. namespace Amenadiel\JpGraph\Graph;
  6. use Amenadiel\JpGraph\Plot;
  7. use Amenadiel\JpGraph\Util;
  8. /*
  9.  * File:        JPGRAPH_LEGEND.INC.PHP
  10.  * // Description: Class to handle the legend box in the graph that gives
  11.  * //              names on the data series. The number of rows and columns
  12.  * //              in the legend are user specifyable.
  13.  * // Created:     2001-01-08 (Refactored to separate file 2008-08-01)
  14.  * // Ver:         $Id: jpgraph_legend.inc.php 1926 2010-01-11 16:33:07Z ljp $
  15.  * //
  16.  * // Copyright (c) Asial Corporation. All rights reserved.
  17.  */
  18. defined('_DEFAULT_LPM_SIZE') || define('_DEFAULT_LPM_SIZE'8); // Default Legend Plot Mark size
  19. /**
  20.  * @class Legend
  21.  * // Description: Responsible for drawing the box containing
  22.  * // all the legend text for the graph
  23.  */
  24. class Legend
  25. {
  26.     public $txtcol          = [];
  27.     public $font_family     FF_DEFAULT;
  28.     public $font_style      FS_NORMAL;
  29.     public $font_size       8// old. 12
  30.     private $color          = [120120120]; // Default frame color
  31.     private $fill_color     = [245245245]; // Default fill color
  32.     private $shadow         false// Shadow around legend "box"
  33.     private $shadow_color   'darkgray';
  34.     private $mark_abs_hsize _DEFAULT_LPM_SIZE;
  35.     private $mark_abs_vsize _DEFAULT_LPM_SIZE;
  36.     private $xmargin        10;
  37.     private $ymargin        0;
  38.     private $shadow_width   2;
  39.     private $xlmargin       4;
  40.     private $ylinespacing   5;
  41.     // We need a separate margin since the baseline of the last text would coincide with the bottom otherwise
  42.     private $ybottom_margin 8;
  43.     private $xpos         0.05;
  44.     private $ypos         0.15;
  45.     private $xabspos      = -1;
  46.     private $yabspos      = -1;
  47.     private $halign       'right';
  48.     private $valign       'top';
  49.     private $font_color   'black';
  50.     private $hide         false;
  51.     private $layout_n     1;
  52.     private $weight       1;
  53.     private $frameweight  1;
  54.     private $csimareas    '';
  55.     private $reverse      false;
  56.     private $bkg_gradtype = -1;
  57.     private $bkg_gradfrom 'lightgray';
  58.     private $bkg_gradto   'gray';
  59.     /**
  60.      * CONSTRUCTOR.
  61.      */
  62.     public function __construct()
  63.     {
  64.         // Empty
  65.     }
  66.     /**
  67.      * PUBLIC METHODS.
  68.      *
  69.      * @param mixed $aHide
  70.      */
  71.     public function Hide($aHide true)
  72.     {
  73.         $this->hide $aHide;
  74.     }
  75.     public function SetHColMargin($aXMarg)
  76.     {
  77.         $this->xmargin $aXMarg;
  78.     }
  79.     public function SetVColMargin($aSpacing)
  80.     {
  81.         $this->ylinespacing $aSpacing;
  82.     }
  83.     public function SetLeftMargin($aXMarg)
  84.     {
  85.         $this->xlmargin $aXMarg;
  86.     }
  87.     // Synonym
  88.     public function SetLineSpacing($aSpacing)
  89.     {
  90.         $this->ylinespacing $aSpacing;
  91.     }
  92.     public function SetShadow($aShow 'gray'$aWidth 4)
  93.     {
  94.         if (is_string($aShow)) {
  95.             $this->shadow_color $aShow;
  96.             $this->shadow       true;
  97.         } else {
  98.             $this->shadow $aShow;
  99.         }
  100.         $this->shadow_width $aWidth;
  101.     }
  102.     public function SetMarkAbsSize($aSize)
  103.     {
  104.         $this->mark_abs_vsize $aSize;
  105.         $this->mark_abs_hsize $aSize;
  106.     }
  107.     public function SetMarkAbsVSize($aSize)
  108.     {
  109.         $this->mark_abs_vsize $aSize;
  110.     }
  111.     public function SetMarkAbsHSize($aSize)
  112.     {
  113.         $this->mark_abs_hsize $aSize;
  114.     }
  115.     public function SetLineWeight($aWeight)
  116.     {
  117.         $this->weight $aWeight;
  118.     }
  119.     public function SetFrameWeight($aWeight)
  120.     {
  121.         $this->frameweight $aWeight;
  122.     }
  123.     public function SetLayout($aDirection LEGEND_VERT)
  124.     {
  125.         $this->layout_n $aDirection == LEGEND_VERT 99;
  126.     }
  127.     public function SetColumns($aCols)
  128.     {
  129.         $this->layout_n $aCols;
  130.     }
  131.     public function SetReverse($f true)
  132.     {
  133.         $this->reverse $f;
  134.     }
  135.     // Set color on frame around box
  136.     public function SetColor($aFontColor$aColor 'black')
  137.     {
  138.         $this->font_color $aFontColor;
  139.         $this->color      $aColor;
  140.     }
  141.     public function SetFont($aFamily$aStyle FS_NORMAL$aSize 10)
  142.     {
  143.         $this->font_family $aFamily;
  144.         $this->font_style  $aStyle;
  145.         $this->font_size   $aSize;
  146.     }
  147.     public function SetPos($aX$aY$aHAlign 'right'$aVAlign 'top')
  148.     {
  149.         $this->Pos($aX$aY$aHAlign$aVAlign);
  150.     }
  151.     public function SetAbsPos($aX$aY$aHAlign 'right'$aVAlign 'top')
  152.     {
  153.         $this->xabspos $aX;
  154.         $this->yabspos $aY;
  155.         $this->halign  $aHAlign;
  156.         $this->valign  $aVAlign;
  157.     }
  158.     public function Pos($aX$aY$aHAlign 'right'$aVAlign 'top')
  159.     {
  160.         if (!($aX && $aY 1)) {
  161.             Util\JpGraphError::RaiseL(25120); //(" Position for legend must be given as percentage in range 0-1");
  162.         }
  163.         $this->xpos   $aX;
  164.         $this->ypos   $aY;
  165.         $this->halign $aHAlign;
  166.         $this->valign $aVAlign;
  167.     }
  168.     public function SetFillColor($aColor)
  169.     {
  170.         $this->fill_color $aColor;
  171.     }
  172.     public function Clear()
  173.     {
  174.         $this->txtcol = [];
  175.     }
  176.     public function Add($aTxt$aColor$aPlotmark ''$aLinestyle 0$csimtarget ''$csimalt ''$csimwintarget '')
  177.     {
  178.         $this->txtcol[] = [$aTxt$aColor$aPlotmark$aLinestyle$csimtarget$csimalt$csimwintarget];
  179.     }
  180.     public function GetCSIMAreas()
  181.     {
  182.         return $this->csimareas;
  183.     }
  184.     public function SetBackgroundGradient($aFrom 'navy'$aTo 'silver'$aGradType 2)
  185.     {
  186.         $this->bkg_gradtype $aGradType;
  187.         $this->bkg_gradfrom $aFrom;
  188.         $this->bkg_gradto   $aTo;
  189.     }
  190.     public function HasItems()
  191.     {
  192.         return (bool) (safe_count($this->txtcol));
  193.     }
  194.     public function Stroke($aImg)
  195.     {
  196.         // Constant
  197.         $fillBoxFrameWeight 1;
  198.         if ($this->hide) {
  199.             return;
  200.         }
  201.         $aImg->SetFont($this->font_family$this->font_style$this->font_size);
  202.         if ($this->reverse) {
  203.             $this->txtcol array_reverse($this->txtcol);
  204.         }
  205.         $n safe_count($this->txtcol);
  206.         if ($n == 0) {
  207.             return;
  208.         }
  209.         // Find out the max width and height of each column to be able
  210.         // to size the legend box.
  211.         $numcolumns = ($n $this->layout_n $this->layout_n $n);
  212.         for ($i 0$i $numcolumns; ++$i) {
  213.             $colwidth[$i] = $aImg->GetTextWidth($this->txtcol[$i][0]) +
  214.             $this->xmargin $this->mark_abs_hsize;
  215.             $colheight[$i] = 0;
  216.         }
  217.         // Find our maximum height in each row
  218.         $rows         0;
  219.         $rowheight[0] = 0;
  220.         for ($i 0$i $n; ++$i) {
  221.             $h max($this->mark_abs_vsize$aImg->GetTextHeight($this->txtcol[$i][0])) + $this->ylinespacing;
  222.             // Makes sure we always have a minimum of 1/4 (1/2 on each side) of the mark as space
  223.             // between two vertical legend entries
  224.             //$h = round(max($h,$this->mark_abs_vsize+$this->ymargin));
  225.             //echo "Textheight #$i: tetxheight=".$aImg->GetTextHeight($this->txtcol[$i][0]).', ';
  226.             //echo "h=$h ({$this->mark_abs_vsize},{$this->ymargin})<br>";
  227.             if ($i $numcolumns == 0) {
  228.                 ++$rows;
  229.                 $rowheight[$rows 1] = 0;
  230.             }
  231.             $rowheight[$rows 1] = max($rowheight[$rows 1], $h) + 1;
  232.         }
  233.         $abs_height 0;
  234.         for ($i 0$i $rows; ++$i) {
  235.             $abs_height += $rowheight[$i];
  236.         }
  237.         // Make sure that the height is at least as high as mark size + ymargin
  238.         $abs_height max($abs_height$this->mark_abs_vsize);
  239.         $abs_height += $this->ybottom_margin;
  240.         // Find out the maximum width in each column
  241.         for ($i $numcolumns$i $n; ++$i) {
  242.             $colwidth[$i $numcolumns] = max(
  243.                 $aImg->GetTextWidth($this->txtcol[$i][0]) + $this->xmargin $this->mark_abs_hsize,
  244.                 $colwidth[$i $numcolumns]
  245.             );
  246.         }
  247.         // Get the total width
  248.         $mtw 0;
  249.         for ($i 0$i $numcolumns; ++$i) {
  250.             $mtw += $colwidth[$i];
  251.         }
  252.         // remove the last rows interpace margin (since there is no next row)
  253.         $abs_height -= $this->ylinespacing;
  254.         // Find out maximum width we need for legend box
  255.         $abs_width $mtw $this->xlmargin + ($numcolumns 1) * $this->mark_abs_hsize;
  256.         if ($this->xabspos === -&& $this->yabspos === -1) {
  257.             $this->xabspos $this->xpos $aImg->width;
  258.             $this->yabspos $this->ypos $aImg->height;
  259.         }
  260.         // Positioning of the legend box
  261.         if ($this->halign == 'left') {
  262.             $xp $this->xabspos;
  263.         } elseif ($this->halign == 'center') {
  264.             $xp $this->xabspos $abs_width 2;
  265.         } else {
  266.             $xp $aImg->width $this->xabspos $abs_width;
  267.         }
  268.         $yp $this->yabspos;
  269.         if ($this->valign == 'center') {
  270.             $yp -= $abs_height 2;
  271.         } elseif ($this->valign == 'bottom') {
  272.             $yp -= $abs_height;
  273.         }
  274.         // Stroke legend box
  275.         $aImg->SetColor($this->color);
  276.         $aImg->SetLineWeight($this->frameweight);
  277.         $aImg->SetLineStyle('solid');
  278.         if ($this->shadow) {
  279.             $aImg->ShadowRectangle(
  280.                 $xp,
  281.                 $yp,
  282.                 $xp $abs_width $this->shadow_width 2,
  283.                 $yp $abs_height $this->shadow_width 2,
  284.                 $this->fill_color,
  285.                 $this->shadow_width 2,
  286.                 $this->shadow_color
  287.             );
  288.         } else {
  289.             $aImg->SetColor($this->fill_color);
  290.             $aImg->FilledRectangle($xp$yp$xp $abs_width$yp $abs_height);
  291.             $aImg->SetColor($this->color);
  292.             $aImg->Rectangle($xp$yp$xp $abs_width$yp $abs_height);
  293.         }
  294.         if ($this->bkg_gradtype >= 0) {
  295.             $grad = new Plot\Gradient($aImg);
  296.             $grad->FilledRectangle(
  297.                 $xp 1,
  298.                 $yp 1,
  299.                 $xp $abs_width 3,
  300.                 $yp $abs_height 3,
  301.                 $this->bkg_gradfrom,
  302.                 $this->bkg_gradto,
  303.                 $this->bkg_gradtype
  304.             );
  305.         }
  306.         // x1,y1 is the position for the legend marker + text
  307.         // The vertical position is the baseline position for the text
  308.         // and every marker is adjusted acording to that.
  309.         // For multiline texts this get more complicated.
  310.         $x1 $xp $this->xlmargin;
  311.         $y1 $yp $rowheight[0] - $this->ylinespacing 2// The ymargin is included in rowheight
  312.         // Now, y1 is the bottom vertical position of the first legend, i.e if
  313.         // the legend has multiple lines it is the bottom line.
  314.         $grad           = new Plot\Gradient($aImg);
  315.         $patternFactory null;
  316.         // Now stroke each legend in turn
  317.         // Each plot has added the following information to  the legend
  318.         // p[0] = Legend text
  319.         // p[1] = Color,
  320.         // p[2] = For markers a reference to the PlotMark object
  321.         // p[3] = For lines the line style, for gradient the negative gradient style
  322.         // p[4] = CSIM target
  323.         // p[5] = CSIM Alt text
  324.         $i   1;
  325.         $row 0;
  326.         foreach ($this->txtcol as $p) {
  327.             // STROKE DEBUG BOX
  328.             if (_JPG_DEBUG) {
  329.                 $aImg->SetLineWeight(1);
  330.                 $aImg->SetColor('red');
  331.                 $aImg->SetLineStyle('solid');
  332.                 $aImg->Rectangle($x1$y1$xp $abs_width 1$y1 $rowheight[$row]);
  333.             }
  334.             $aImg->SetLineWeight($this->weight);
  335.             $x1 round($x1) + 1// We add one to not collide with the border
  336.             $y1 round($y1);
  337.             // This is the center offset up from the baseline which is
  338.             // considered the "center" of the marks. This gets slightly complicated since
  339.             // we need to consider if the text is a multiline paragraph or if it is only
  340.             // a single line. The reason is that for single line the y1 corresponds to the baseline
  341.             // and that is fine. However for a multiline paragraph there is no single baseline
  342.             // and in that case the y1 corresponds to the lowest y for the bounding box. In that
  343.             // case we center the mark in the middle of the paragraph
  344.             if (!preg_match('/\n/'$p[0])) {
  345.                 // Single line
  346.                 $marky ceil($y1 $this->mark_abs_vsize 2) - 1;
  347.             } else {
  348.                 // Paragraph
  349.                 $marky $y1 $aImg->GetTextHeight($p[0]) / 2;
  350.                 //  echo "y1=$y1, p[o]={$p[0]}, marky=$marky<br>";
  351.             }
  352.             //echo "<br>Mark #$i: marky=$marky<br>";
  353.             $x1 += $this->mark_abs_hsize;
  354.             if (!empty($p[2]) && $p[2]->GetType() > -1) {
  355.                 // Make a plot mark legend. This is constructed with a mark which
  356.                 // is run through with a line
  357.                 // First construct a bit of the line that looks exactly like the
  358.                 // line in the plot
  359.                 $aImg->SetColor($p[1]);
  360.                 if (is_string($p[3]) || $p[3] > 0) {
  361.                     $aImg->SetLineStyle($p[3]);
  362.                     $aImg->StyleLine($x1 $this->mark_abs_hsize$marky$x1 $this->mark_abs_hsize$marky);
  363.                 }
  364.                 // Stroke a mark using image
  365.                 if ($p[2]->GetType() == MARK_IMG) {
  366.                     $p[2]->Stroke($aImg$x1$marky);
  367.                 }
  368.                 // Stroke a mark with the standard size
  369.                 // (As long as it is not an image mark )
  370.                 if ($p[2]->GetType() != MARK_IMG) {
  371.                     // Clear any user callbacks since we ont want them called for
  372.                     // the legend marks
  373.                     $p[2]->iFormatCallback  '';
  374.                     $p[2]->iFormatCallback2 '';
  375.                     // Since size for circles is specified as the radius
  376.                     // this means that we must half the size to make the total
  377.                     // width behave as the other marks
  378.                     if ($p[2]->GetType() == MARK_FILLEDCIRCLE || $p[2]->GetType() == MARK_CIRCLE) {
  379.                         $p[2]->SetSize(min($this->mark_abs_vsize$this->mark_abs_hsize) / 2);
  380.                         $p[2]->Stroke($aImg$x1$marky);
  381.                     } else {
  382.                         $p[2]->SetSize(min($this->mark_abs_vsize$this->mark_abs_hsize));
  383.                         $p[2]->Stroke($aImg$x1$marky);
  384.                     }
  385.                 }
  386.             } elseif (!empty($p[2]) && (is_string($p[3]) || $p[3] > 0)) {
  387.                 // Draw a styled line
  388.                 $aImg->SetColor($p[1]);
  389.                 $aImg->SetLineStyle($p[3]);
  390.                 $aImg->StyleLine($x1 $this->mark_abs_hsize$marky$x1 $this->mark_abs_hsize$marky);
  391.                 $aImg->StyleLine($x1 $this->mark_abs_hsize$marky 1$x1 $this->mark_abs_hsize$marky 1);
  392.             } else {
  393.                 // Draw a colored box
  394.                 $color $p[1];
  395.                 // We make boxes slightly larger to better show
  396.                 $boxsize max($this->mark_abs_vsize$this->mark_abs_hsize) + 2;
  397.                 $ym $marky ceil($boxsize 2); // Marker y-coordinate
  398.                 // We either need to plot a gradient or a
  399.                 // pattern. To differentiate we use a kludge.
  400.                 // Patterns have a p[3] value of < -100
  401.                 if ($p[3] < -100) {
  402.                     // p[1][0] == iPattern, p[1][1] == iPatternColor, p[1][2] == iPatternDensity
  403.                     if ($patternFactory == null) {
  404.                         $patternFactory = new RectPatternFactory();
  405.                     }
  406.                     $prect $patternFactory->Create($p[1][0], $p[1][1], 1);
  407.                     $prect->SetBackground($p[1][3]);
  408.                     $prect->SetDensity($p[1][2] + 1);
  409.                     $prect->SetPos(new Util\Rectangle($x1$ym$boxsize$boxsize));
  410.                     $prect->Stroke($aImg);
  411.                     $prect null;
  412.                 } else {
  413.                     if (is_array($color) && safe_count($color) == 2) {
  414.                         // The client want a gradient color
  415.                         $grad->FilledRectangle(
  416.                             $x1 $boxsize 2,
  417.                             $ym,
  418.                             $x1 $boxsize 2,
  419.                             $ym $boxsize,
  420.                             $color[0],
  421.                             $color[1],
  422.                             -$p[3]
  423.                         );
  424.                     } else {
  425.                         $aImg->SetColor($p[1]);
  426.                         $aImg->FilledRectangle($x1 $boxsize 2$ym$x1 $boxsize 2$ym $boxsize);
  427.                     }
  428.                     // Draw a plot frame line
  429.                     $aImg->SetColor($this->color);
  430.                     $aImg->SetLineWeight($fillBoxFrameWeight);
  431.                     $aImg->Rectangle(
  432.                         $x1 $boxsize 2,
  433.                         $ym,
  434.                         $x1 $boxsize 2,
  435.                         $ym $boxsize
  436.                     );
  437.                 }
  438.             }
  439.             $aImg->SetColor($this->font_color);
  440.             $aImg->SetFont($this->font_family$this->font_style$this->font_size);
  441.             $aImg->SetTextAlign('left''baseline');
  442.             $debug false;
  443.             $aImg->StrokeText(
  444.                 $x1 $this->mark_abs_hsize $this->xmargin,
  445.                 $y1,
  446.                 $p[0],
  447.                 0,
  448.                 'left',
  449.                 $debug
  450.             );
  451.             // Add CSIM for Legend if defined
  452.             if (!empty($p[4])) {
  453.                 $xs     $x1 $this->mark_abs_hsize;
  454.                 $ys     $y1 1;
  455.                 $xe     $x1 $aImg->GetTextWidth($p[0]) + $this->mark_abs_hsize $this->xmargin;
  456.                 $ye     $y1 $rowheight[$row] + 1;
  457.                 $coords "${xs},${ys},${xe},${y1},${xe},${ye},${xs},${ye}";
  458.                 if (!empty($p[4])) {
  459.                     $this->csimareas .= "<area shape=\"poly\" coords=\"${coords}\" href=\"" htmlentities($p[4]) . '"';
  460.                     if (!empty($p[6])) {
  461.                         $this->csimareas .= ' target="' $p[6] . '"';
  462.                     }
  463.                     if (!empty($p[5])) {
  464.                         $tmp sprintf($p[5], $p[0]);
  465.                         $this->csimareas .= " title=\"${tmp}\" alt=\"${tmp}\" ";
  466.                     }
  467.                     $this->csimareas .= " />\n";
  468.                 }
  469.             }
  470.             if ($i >= $this->layout_n) {
  471.                 $x1 $xp $this->xlmargin;
  472.                 ++$row;
  473.                 if (!empty($rowheight[$row])) {
  474.                     $y1 += $rowheight[$row];
  475.                 }
  476.                 $i 1;
  477.             } else {
  478.                 $x1 += $colwidth[($i 1) % $numcolumns];
  479.                 ++$i;
  480.             }
  481.         }
  482.     }
  483. // @class