1<?php
2/*
3** Zabbix
4** Copyright (C) 2001-2021 Zabbix SIA
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19**/
20
21
22class CPieGraphDraw extends CGraphDraw {
23
24	const DEFAULT_HEADER_PADDING_TOP = 30;
25
26	const GRAPH_WIDTH_MIN = 20;
27	const GRAPH_HEIGHT_MIN = 20;
28
29	public function __construct($type = GRAPH_TYPE_PIE) {
30		parent::__construct($type);
31		$this->background = false;
32		$this->sum = false;
33		$this->exploderad = 1;
34		$this->exploderad3d = 3;
35		$this->graphheight3d = 12;
36		$this->shiftlegendright = 17 * 7 + 7 + 10; // count of static chars * px/char + for color rectangle + space
37	}
38
39	/********************************************************************************************************/
40	/* PRE CONFIG: ADD / SET / APPLY
41	/********************************************************************************************************/
42	public function addItem($itemid, $calc_fnc = CALC_FNC_AVG, $color = null, $type = null) {
43		$items = CMacrosResolverHelper::resolveItemNames([get_item_by_itemid($itemid)]);
44
45		$this->items[$this->num] = reset($items);
46
47		$host = get_host_by_hostid($this->items[$this->num]['hostid']);
48
49		$this->items[$this->num]['host'] = $host['host'];
50		$this->items[$this->num]['hostname'] = $host['name'];
51		$this->items[$this->num]['color'] = is_null($color) ? 'Dark Green' : $color;
52		$this->items[$this->num]['calc_fnc'] = is_null($calc_fnc) ? CALC_FNC_AVG : $calc_fnc;
53		$this->items[$this->num]['calc_type'] = is_null($type) ? GRAPH_ITEM_SIMPLE : $type;
54
55		$this->num++;
56	}
57
58	public function switchPie3D($type = false) {
59		if ($type) {
60			$this->type = $type;
61		}
62		else {
63			switch ($this->type) {
64				case GRAPH_TYPE_EXPLODED:
65					$this->type = GRAPH_TYPE_3D_EXPLODED;
66					break;
67				case GRAPH_TYPE_3D_EXPLODED:
68					$this->type = GRAPH_TYPE_EXPLODED;
69					break;
70				case GRAPH_TYPE_3D:
71					$this->type = GRAPH_TYPE_PIE;
72					break;
73				case GRAPH_TYPE_PIE:
74					$this->type = GRAPH_TYPE_3D;
75					break;
76				default:
77					$this->type = GRAPH_TYPE_PIE;
78			}
79		}
80		return $this->type;
81	}
82
83	protected function calc3dheight($height) {
84		$this->graphheight3d = (int) ($height / 20);
85	}
86
87	protected function calcExplodedCenter($anglestart, $angleend, $x, $y, $count) {
88		$count *= $this->exploderad;
89		$anglemid = (int) (($anglestart + $angleend) / 2);
90
91		$y+= round($count * sin(deg2rad($anglemid)));
92		$x+= round($count * cos(deg2rad($anglemid)));
93
94		return [$x, $y];
95	}
96
97	protected function calcExplodedRadius($sizeX, $sizeY, $count) {
98		$count *= $this->exploderad * 2;
99		$sizeX -= $count;
100		$sizeY -= $count;
101		return [$sizeX, $sizeY];
102	}
103
104	protected function calc3DAngle($sizeX, $sizeY) {
105		$sizeY *= GRAPH_3D_ANGLE / 90;
106		return [$sizeX, round($sizeY)];
107	}
108
109	protected function selectData() {
110		$this->data = [];
111		$now = time();
112
113		if (isset($this->stime)) {
114			$this->from_time = $this->stime;
115			$this->to_time = $this->stime + $this->period;
116		}
117		else {
118			$this->to_time = $now;
119			$this->from_time = $this->to_time - $this->period;
120		}
121
122		$strvaluelength = 0; // we need to know how long in px will be our legend
123
124		// fetch values for items with the "last" function
125		$lastValueItems = [];
126		foreach ($this->items as $item) {
127			if ($item['calc_fnc'] == CALC_FNC_LST) {
128				$lastValueItems[] = $item;
129			}
130		}
131		if ($lastValueItems) {
132			$history = Manager::History()->getLastValues($lastValueItems);
133		}
134
135		$items = [];
136
137		for ($i = 0; $i < $this->num; $i++) {
138			$item = get_item_by_itemid($this->items[$i]['itemid']);
139			$from_time = $this->from_time;
140			$to_time = $this->to_time;
141
142			$to_resolve = [];
143
144			// Override item history setting with housekeeping settings, if they are enabled in config.
145			if (CHousekeepingHelper::get(CHousekeepingHelper::HK_HISTORY_GLOBAL)) {
146				$item['history'] = timeUnitToSeconds(CHousekeepingHelper::get(CHousekeepingHelper::HK_HISTORY));
147			}
148			else {
149				$to_resolve[] = 'history';
150			}
151
152			if (CHousekeepingHelper::get(CHousekeepingHelper::HK_TRENDS_GLOBAL)) {
153				$item['trends'] = timeUnitToSeconds(CHousekeepingHelper::get(CHousekeepingHelper::HK_TRENDS));
154			}
155			else {
156				$to_resolve[] = 'trends';
157			}
158
159			// Otherwise, resolve user macro and parse the string. If successful, convert to seconds.
160			if ($to_resolve) {
161				$item = CMacrosResolverHelper::resolveTimeUnitMacros([$item], $to_resolve)[0];
162
163				$simple_interval_parser = new CSimpleIntervalParser();
164
165				if (!CHousekeepingHelper::get(CHousekeepingHelper::HK_HISTORY_GLOBAL)) {
166					if ($simple_interval_parser->parse($item['history']) != CParser::PARSE_SUCCESS) {
167						show_error_message(_s('Incorrect value for field "%1$s": %2$s.', 'history',
168							_('invalid history storage period')
169						));
170						exit;
171					}
172					$item['history'] = timeUnitToSeconds($item['history']);
173				}
174
175				if (!CHousekeepingHelper::get(CHousekeepingHelper::HK_TRENDS_GLOBAL)) {
176					if ($simple_interval_parser->parse($item['trends']) != CParser::PARSE_SUCCESS) {
177						show_error_message(_s('Incorrect value for field "%1$s": %2$s.', 'trends',
178							_('invalid trend storage period')
179						));
180						exit;
181					}
182					$item['trends'] = timeUnitToSeconds($item['trends']);
183				}
184			}
185
186			$this->data[$this->items[$i]['itemid']]['last'] = isset($history[$item['itemid']])
187				? $history[$item['itemid']][0]['value'] : null;
188			$this->data[$this->items[$i]['itemid']]['shift_min'] = 0;
189			$this->data[$this->items[$i]['itemid']]['shift_max'] = 0;
190			$this->data[$this->items[$i]['itemid']]['shift_avg'] = 0;
191
192			$item['source'] = ($item['trends'] == 0 || ($item['history'] > time() - ($from_time + $this->period / 2)))
193				? 'history'
194				: 'trends';
195			$items[] = $item;
196		}
197
198		$results = Manager::History()->getGraphAggregationByWidth($items, $from_time, $to_time);
199		$i = 0;
200
201		foreach ($items as $item) {
202			if (array_key_exists($item['itemid'], $results)) {
203				$result = $results[$item['itemid']];
204				$this->dataFrom = $result['source'];
205
206				foreach ($result['data'] as $row) {
207					$this->data[$item['itemid']]['min'] = $row['min'];
208					$this->data[$item['itemid']]['max'] = $row['max'];
209					$this->data[$item['itemid']]['avg'] = $row['avg'];
210					$this->data[$item['itemid']]['clock'] = $row['clock'];
211				}
212				unset($result);
213			}
214			else {
215				$this->dataFrom = $item['source'];
216			}
217
218			switch ($this->items[$i]['calc_fnc']) {
219				case CALC_FNC_MIN:
220					$fncName = 'min';
221					break;
222				case CALC_FNC_MAX:
223					$fncName = 'max';
224					break;
225				case CALC_FNC_LST:
226					$fncName = 'last';
227					break;
228				case CALC_FNC_AVG:
229				default:
230					$fncName = 'avg';
231			}
232
233			$item_value = empty($this->data[$item['itemid']][$fncName])
234				? 0
235				: abs($this->data[$item['itemid']][$fncName]);
236
237			if ($this->items[$i]['calc_type'] == GRAPH_ITEM_SUM) {
238				$this->background = $i;
239				$graph_sum = $item_value;
240			}
241
242			$this->sum += $item_value;
243
244			$convertedUnit = strlen(convertUnits([
245				'value' => $item_value,
246				'units' => $item['units']
247			]));
248			$strvaluelength = max($strvaluelength, $convertedUnit);
249			$i++;
250		}
251
252		if (isset($graph_sum)) {
253			$this->sum = $graph_sum;
254		}
255		$this->shiftlegendright += $strvaluelength * 7;
256	}
257
258	protected function drawLegend() {
259		$shiftY = $this->shiftY + $this->shiftYLegend;
260		$fontSize = 8;
261
262		// check if host name will be displayed
263		$displayHostName = (count(array_unique(zbx_objectValues($this->items, 'hostname'))) > 1);
264
265		// calculate function name X shift
266		$functionNameXShift = 0;
267
268		foreach ($this->items as $item) {
269			$name = $displayHostName ? $item['hostname'].': '.$item['name_expanded'] : $item['name_expanded'];
270			$dims = imageTextSize($fontSize, 0, $name);
271
272			if ($dims['width'] > $functionNameXShift) {
273				$functionNameXShift = $dims['width'];
274			}
275		}
276
277		// display items
278		$i = 0;
279		$top_padding = $this->with_vertical_padding ? 10 : -(static::DEFAULT_TOP_BOTTOM_PADDING / 2);
280
281		foreach ($this->items as $item) {
282			$color = $this->getColor($item['color'], 0);
283
284			// function name
285			switch ($item['calc_fnc']) {
286				case CALC_FNC_MIN:
287					$fncName = 'min';
288					$fncRealName = _('min');
289					break;
290				case CALC_FNC_MAX:
291					$fncName = 'max';
292					$fncRealName = _('max');
293					break;
294				case CALC_FNC_LST:
295					$fncName = 'last';
296					$fncRealName = _('last');
297					break;
298				case CALC_FNC_AVG:
299				default:
300					$fncName = 'avg';
301					$fncRealName = _('avg');
302			}
303
304			if (isset($this->data[$item['itemid']])
305					&& isset($this->data[$item['itemid']][$fncName])) {
306				$dataValue = $this->data[$item['itemid']][$fncName];
307				$proc = ($this->sum == 0) ? 0 : ($dataValue * 100) / $this->sum;
308
309				$strValue = sprintf(_('Value').': %s ('.(round($proc) != round($proc, 2) ? '%0.2f' : '%0.0f').'%%)',
310					convertUnits([
311						'value' => $dataValue,
312						'units' => $this->items[$i]['units']
313					]),
314					$proc
315				);
316
317				$str = '['.$fncRealName.']';
318			}
319			else {
320				$strValue = _('Value: no data');
321
322				$str = '['._('no data').']';
323			}
324
325			// item name
326			imageText(
327				$this->im,
328				$fontSize,
329				0,
330				$this->shiftXleft + 15,
331				$this->sizeY + $shiftY + 14 * $i + 5,
332				$this->getColor($this->graphtheme['textcolor'], 0),
333				$displayHostName ? $item['hostname'].': '.$item['name_expanded'] : $item['name_expanded']
334			);
335
336			// function name
337			imageText(
338				$this->im,
339				$fontSize,
340				0,
341				$this->shiftXleft + $functionNameXShift + 30,
342				$this->sizeY + $shiftY + 14 * $i + 5,
343				$this->getColor($this->graphtheme['textcolor'], 0),
344				$str
345			);
346
347			// left square fill
348			imagefilledrectangle(
349				$this->im,
350				$this->shiftXleft,
351				$this->sizeY + $shiftY + 14 * $i - 5,
352				$this->shiftXleft + 10,
353				$this->sizeY + $shiftY + 5 + 14 * $i,
354				$color
355			);
356
357			// left square frame
358			imagerectangle(
359				$this->im,
360				$this->shiftXleft,
361				$this->sizeY + $shiftY + 14 * $i - 5,
362				$this->shiftXleft + 10,
363				$this->sizeY + $shiftY + 5 + 14 * $i,
364				$this->getColor('Black No Alpha')
365			);
366
367			$shiftX = $this->fullSizeX - $this->shiftlegendright - $this->shiftXright + 25;
368
369			// right square fill
370			imagefilledrectangle(
371				$this->im,
372				$shiftX - 10,
373				$this->shiftY + $top_padding + 14 * $i,
374				$shiftX,
375				$this->shiftY + $top_padding + 10 + 14 * $i,
376				$color
377			);
378
379			// right square frame
380			imagerectangle(
381				$this->im,
382				$shiftX - 10,
383				$this->shiftY + $top_padding + 14 * $i,
384				$shiftX,
385				$this->shiftY + $top_padding + 10 + 14 * $i,
386				$this->GetColor('Black No Alpha')
387			);
388
389			// item value
390			imagetext(
391				$this->im,
392				$fontSize,
393				0,
394				$shiftX + 5,
395				$this->shiftY + $top_padding + 14 * $i + 10,
396				$this->getColor($this->graphtheme['textcolor'], 0),
397				$strValue
398			);
399
400			$i++;
401		}
402
403		if ($this->sizeY < 120) {
404			return;
405		}
406	}
407
408	protected function drawElementPie($values) {
409		$sum = $this->sum;
410
411		if ($this->background !== false) {
412			$least = 0;
413			foreach ($values as $item => $value) {
414				if ($item != $this->background) {
415					$least += $value;
416				}
417			}
418			$values[$this->background] -= $least;
419		}
420
421		if ($sum <= 0) {
422			$values = [0 => 1];
423			$sum = 1;
424			$isEmptyData = true;
425		}
426		else {
427			$isEmptyData = false;
428		}
429
430		$sizeX = $this->sizeX;
431		$sizeY = $this->sizeY;
432
433		if ($this->type == GRAPH_TYPE_EXPLODED) {
434			list($sizeX, $sizeY) = $this->calcExplodedRadius($sizeX, $sizeY, count($values));
435		}
436
437		$xc = $x = (int) $this->sizeX / 2 + $this->shiftXleft;
438		$yc = $y = (int) $this->sizeY / 2 + $this->shiftY;
439
440		$anglestart = 0;
441		$angleend = 0;
442
443		foreach ($values as $item => $value) {
444			if ($value == 0) {
445				continue;
446			}
447
448			$angleend += (int) (360 * $value / $sum) + 1;
449			$angleend = ($angleend > 360) ? 360 : $angleend;
450
451			if (($angleend - $anglestart) < 1) {
452				continue;
453			}
454
455			if ($this->type == GRAPH_TYPE_EXPLODED) {
456				list($x, $y) = $this->calcExplodedCenter($anglestart, $angleend, $xc, $yc, count($values));
457			}
458
459			imagefilledarc(
460				$this->im,
461				$x,
462				$y,
463				$sizeX,
464				$sizeY,
465				$anglestart,
466				$angleend,
467				$this->getColor((!$isEmptyData ? $this->items[$item]['color'] : 'FFFFFF'), 0),
468				IMG_ARC_PIE
469			);
470			imagefilledarc(
471				$this->im,
472				$x,
473				$y,
474				$sizeX,
475				$sizeY,
476				$anglestart,
477				$angleend,
478				$this->getColor('Black'),
479				IMG_ARC_PIE | IMG_ARC_EDGED | IMG_ARC_NOFILL
480			);
481			$anglestart = $angleend;
482		}
483	}
484
485	protected function drawElementPie3D($values) {
486		$sum = $this->sum;
487
488		if ($this->background !== false) {
489			$least = 0;
490			foreach ($values as $item => $value) {
491				if ($item != $this->background) {
492					$least += $value;
493				}
494			}
495			$values[$this->background] -= $least;
496		}
497
498		if ($sum <= 0) {
499			$values = [0 => 1];
500			$sum = 1;
501			$isEmptyData = true;
502		}
503		else {
504			$isEmptyData = false;
505		}
506
507		$sizeX = $this->sizeX;
508		$sizeY = $this->sizeY;
509
510		$this->exploderad = $this->exploderad3d;
511
512		if ($this->type == GRAPH_TYPE_3D_EXPLODED) {
513			list($sizeX, $sizeY) = $this->calcExplodedRadius($sizeX, $sizeY, count($values));
514		}
515
516		list($sizeX, $sizeY) = $this->calc3DAngle($sizeX, $sizeY);
517
518		$xc = $x = (int) $this->sizeX / 2 + $this->shiftXleft;
519		$yc = $y = (int) $this->sizeY / 2 + $this->shiftY;
520
521		// bottom angle line
522		$anglestart = 0;
523		$angleend = 0;
524
525		foreach ($values as $item => $value) {
526			if ($value == 0) {
527				continue;
528			}
529
530			$angleend += (int) (360 * $value / $sum) + 1;
531			$angleend = ($angleend > 360) ? 360 : $angleend;
532
533			if (($angleend - $anglestart) < 1) {
534				continue;
535			}
536
537			if ($this->type == GRAPH_TYPE_3D_EXPLODED) {
538				list($x, $y) = $this->calcExplodedCenter($anglestart, $angleend, $xc, $yc, count($values));
539			}
540
541			imagefilledarc(
542				$this->im,
543				$x,
544				$y + $this->graphheight3d + 1,
545				$sizeX,
546				$sizeY,
547				$anglestart,
548				$angleend,
549				$this->getShadow((!$isEmptyData ? $this->items[$item]['color'] : 'FFFFFF'), 0),
550				IMG_ARC_PIE
551			);
552			imagefilledarc(
553				$this->im,
554				$x,
555				$y + $this->graphheight3d + 1,
556				$sizeX,
557				$sizeY,
558				$anglestart,
559				$angleend,
560				$this->getColor('Black'),
561				IMG_ARC_PIE | IMG_ARC_EDGED | IMG_ARC_NOFILL
562			);
563			$anglestart = $angleend;
564		}
565
566		// 3d effect
567		for ($i = $this->graphheight3d; $i > 0; $i--) {
568			$anglestart = 0;
569			$angleend = 0;
570
571			foreach ($values as $item => $value) {
572				if ($value == 0) {
573					continue;
574				}
575
576				$angleend += (int) (360 * $value / $sum) + 1;
577				$angleend = ($angleend > 360) ? 360 : $angleend;
578
579				if (($angleend - $anglestart) < 1) {
580					continue;
581				}
582				elseif ($this->sum == 0) {
583					continue;
584				}
585
586				if ($this->type == GRAPH_TYPE_3D_EXPLODED) {
587					list($x, $y) = $this->calcExplodedCenter($anglestart, $angleend, $xc, $yc, count($values));
588				}
589
590				imagefilledarc(
591					$this->im,
592					$x,
593					$y + $i,
594					$sizeX,
595					$sizeY,
596					$anglestart,
597					$angleend,
598					$this->getShadow((!$isEmptyData ? $this->items[$item]['color'] : 'FFFFFF'), 0),
599					IMG_ARC_PIE
600				);
601				$anglestart = $angleend;
602			}
603		}
604
605		$anglestart = 0;
606		$angleend = 0;
607
608		foreach ($values as $item => $value) {
609			if ($value == 0) {
610				continue;
611			}
612
613			$angleend += (int) (360 * $value / $sum) + 1;
614			$angleend = ($angleend > 360) ? 360 : $angleend;
615
616			if (($angleend - $anglestart) < 1) {
617				continue;
618			}
619
620			if ($this->type == GRAPH_TYPE_3D_EXPLODED) {
621				list($x, $y) = $this->calcExplodedCenter($anglestart, $angleend, $xc, $yc, count($values));
622			}
623
624			imagefilledarc(
625				$this->im,
626				$x,
627				$y,
628				$sizeX,
629				$sizeY,
630				$anglestart,
631				$angleend,
632				$this->getColor((!$isEmptyData ? $this->items[$item]['color'] : 'FFFFFF'), 0),
633				IMG_ARC_PIE
634			);
635			imagefilledarc(
636				$this->im,
637				$x,
638				$y,
639				$sizeX,
640				$sizeY,
641				$anglestart,
642				$angleend,
643				$this->getColor('Black'),
644				IMG_ARC_PIE | IMG_ARC_EDGED | IMG_ARC_NOFILL
645			);
646			$anglestart = $angleend;
647		}
648	}
649
650	public function draw() {
651		$debug_mode = CWebUser::getDebugMode();
652		if ($debug_mode) {
653			$start_time = microtime(true);
654		}
655		set_image_header();
656		$this->calculateTopPadding();
657
658		$this->selectData();
659		if (hasErrorMesssages()) {
660			show_messages();
661		}
662
663		$this->shiftYLegend = 20;
664		$this->shiftXleft = 10;
665		$this->shiftXright = 0;
666		$this->fullSizeX = $this->sizeX;
667		$this->fullSizeY = $this->sizeY;
668
669		if ($this->sizeX < 300 || $this->sizeY < 200) {
670			$this->showLegend(0);
671		}
672
673		if ($this->drawLegend == 1) {
674			$this->sizeX -= $this->shiftXleft + $this->shiftXright + $this->shiftlegendright;
675			$this->sizeY -= $this->shiftY + $this->shiftYLegend + 14 * $this->num + 8;
676		}
677		elseif ($this->with_vertical_padding) {
678			$this->sizeX -= $this->shiftXleft * 2;
679			$this->sizeY -= $this->shiftY * 2;
680		}
681
682		if (!$this->with_vertical_padding) {
683			if ($this->drawLegend == 1) {
684				// Increase size of graph by sum of: 8px legend font size and 5px legend item bottom shift.
685				$this->sizeY += 13;
686			}
687			else {
688				// Remove y shift if only graph is rendered (no labels, header, vertical paddings).
689				$this->shiftY = 0;
690			}
691		}
692
693		$this->sizeX = min($this->sizeX, $this->sizeY);
694		$this->sizeY = min($this->sizeX, $this->sizeY);
695
696		if ($this->sizeX + $this->shiftXleft > $this->fullSizeX) {
697			$this->sizeX = $this->fullSizeX - $this->shiftXleft - $this->shiftXleft;
698			$this->sizeY = min($this->sizeX, $this->sizeY);
699		}
700
701		$this->calc3dheight($this->sizeY);
702
703		$this->exploderad = (int) $this->sizeX / 100;
704		$this->exploderad3d = (int) $this->sizeX / 60;
705
706		if (function_exists('ImageColorExactAlpha') && function_exists('ImageCreateTrueColor') && @imagecreatetruecolor(1, 1)) {
707			$this->im = imagecreatetruecolor($this->fullSizeX, $this->fullSizeY);
708		}
709		else {
710			$this->im = imagecreate($this->fullSizeX, $this->fullSizeY);
711		}
712		$this->initColors();
713		$this->drawRectangle();
714		$this->drawHeader();
715
716		// for each metric
717		$values = [];
718		for ($i = 0; $i < $this->num; $i++) {
719			$data = &$this->data[$this->items[$i]['itemid']];
720
721			if (!isset($data)) {
722				continue;
723			}
724
725			switch ($this->items[$i]['calc_fnc']) {
726				case CALC_FNC_MIN:
727					$fncName = 'min';
728					break;
729				case CALC_FNC_MAX:
730					$fncName = 'max';
731					break;
732				case CALC_FNC_LST:
733					$fncName = 'last';
734					break;
735				case CALC_FNC_AVG:
736				default:
737					$fncName = 'avg';
738			}
739
740			$values[$i] = empty($this->data[$this->items[$i]['itemid']][$fncName])
741				? 0
742				: abs($this->data[$this->items[$i]['itemid']][$fncName]);
743		}
744
745		switch ($this->type) {
746			case GRAPH_TYPE_EXPLODED:
747				$this->drawElementPie($values);
748				break;
749			case GRAPH_TYPE_3D:
750				$this->drawElementPie3D($values);
751				break;
752			case GRAPH_TYPE_3D_EXPLODED:
753				$this->drawElementPie3D($values);
754				break;
755			default:
756				$this->drawElementPie($values);
757		}
758
759		if ($this->drawLegend == 1) {
760			$this->drawLegend();
761		}
762
763		if ($debug_mode) {
764			$str = sprintf('%0.2f', microtime(true) - $start_time);
765			imageText(
766				$this->im,
767				6,
768				90,
769				$this->fullSizeX - 2,
770				$this->fullSizeY - 5,
771				$this->getColor('Gray'),
772				_s('Data from %1$s. Generated in %2$s sec.', $this->dataFrom, $str)
773			);
774		}
775
776		unset($this->items, $this->data);
777
778		imageOut($this->im);
779	}
780}
781