Issues (459)

src/graph/Legend.php (4 issues)

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