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 $config = select_config(); 136 $items = []; 137 138 for ($i = 0; $i < $this->num; $i++) { 139 $item = get_item_by_itemid($this->items[$i]['itemid']); 140 $from_time = $this->from_time; 141 $to_time = $this->to_time; 142 143 $to_resolve = []; 144 145 // Override item history setting with housekeeping settings, if they are enabled in config. 146 if ($config['hk_history_global']) { 147 $item['history'] = timeUnitToSeconds($config['hk_history']); 148 } 149 else { 150 $to_resolve[] = 'history'; 151 } 152 153 if ($config['hk_trends_global']) { 154 $item['trends'] = timeUnitToSeconds($config['hk_trends']); 155 } 156 else { 157 $to_resolve[] = 'trends'; 158 } 159 160 // Otherwise, resolve user macro and parse the string. If successful, convert to seconds. 161 if ($to_resolve) { 162 $item = CMacrosResolverHelper::resolveTimeUnitMacros([$item], $to_resolve)[0]; 163 164 $simple_interval_parser = new CSimpleIntervalParser(); 165 166 if (!$config['hk_history_global']) { 167 if ($simple_interval_parser->parse($item['history']) != CParser::PARSE_SUCCESS) { 168 show_error_message(_s('Incorrect value for field "%1$s": %2$s.', 'history', 169 _('invalid history storage period') 170 )); 171 exit; 172 } 173 $item['history'] = timeUnitToSeconds($item['history']); 174 } 175 176 if (!$config['hk_trends_global']) { 177 if ($simple_interval_parser->parse($item['trends']) != CParser::PARSE_SUCCESS) { 178 show_error_message(_s('Incorrect value for field "%1$s": %2$s.', 'trends', 179 _('invalid trend storage period') 180 )); 181 exit; 182 } 183 $item['trends'] = timeUnitToSeconds($item['trends']); 184 } 185 } 186 187 $this->data[$this->items[$i]['itemid']]['last'] = isset($history[$item['itemid']]) 188 ? $history[$item['itemid']][0]['value'] : null; 189 $this->data[$this->items[$i]['itemid']]['shift_min'] = 0; 190 $this->data[$this->items[$i]['itemid']]['shift_max'] = 0; 191 $this->data[$this->items[$i]['itemid']]['shift_avg'] = 0; 192 193 $item['source'] = ($item['trends'] == 0 || ($item['history'] > time() - ($from_time + $this->period / 2))) 194 ? 'history' 195 : 'trends'; 196 $items[] = $item; 197 } 198 199 $results = Manager::History()->getGraphAggregationByWidth($items, $from_time, $to_time); 200 $i = 0; 201 202 foreach ($items as $item) { 203 if (array_key_exists($item['itemid'], $results)) { 204 $result = $results[$item['itemid']]; 205 $this->dataFrom = $result['source']; 206 207 foreach ($result['data'] as $row) { 208 $this->data[$item['itemid']]['min'] = $row['min']; 209 $this->data[$item['itemid']]['max'] = $row['max']; 210 $this->data[$item['itemid']]['avg'] = $row['avg']; 211 $this->data[$item['itemid']]['clock'] = $row['clock']; 212 } 213 unset($result); 214 } 215 else { 216 $this->dataFrom = $item['source']; 217 } 218 219 switch ($this->items[$i]['calc_fnc']) { 220 case CALC_FNC_MIN: 221 $fncName = 'min'; 222 break; 223 case CALC_FNC_MAX: 224 $fncName = 'max'; 225 break; 226 case CALC_FNC_LST: 227 $fncName = 'last'; 228 break; 229 case CALC_FNC_AVG: 230 default: 231 $fncName = 'avg'; 232 } 233 234 $item_value = empty($this->data[$item['itemid']][$fncName]) 235 ? 0 236 : abs($this->data[$item['itemid']][$fncName]); 237 238 if ($this->items[$i]['calc_type'] == GRAPH_ITEM_SUM) { 239 $this->background = $i; 240 $graph_sum = $item_value; 241 } 242 243 $this->sum += $item_value; 244 245 $convertedUnit = strlen(convertUnits([ 246 'value' => $item_value, 247 'units' => $item['units'] 248 ])); 249 $strvaluelength = max($strvaluelength, $convertedUnit); 250 $i++; 251 } 252 253 if (isset($graph_sum)) { 254 $this->sum = $graph_sum; 255 } 256 $this->shiftlegendright += $strvaluelength * 7; 257 } 258 259 protected function drawLegend() { 260 $shiftY = $this->shiftY + $this->shiftYLegend; 261 $fontSize = 8; 262 263 // check if host name will be displayed 264 $displayHostName = (count(array_unique(zbx_objectValues($this->items, 'hostname'))) > 1); 265 266 // calculate function name X shift 267 $functionNameXShift = 0; 268 269 foreach ($this->items as $item) { 270 $name = $displayHostName ? $item['hostname'].': '.$item['name_expanded'] : $item['name_expanded']; 271 $dims = imageTextSize($fontSize, 0, $name); 272 273 if ($dims['width'] > $functionNameXShift) { 274 $functionNameXShift = $dims['width']; 275 } 276 } 277 278 // display items 279 $i = 0; 280 $top_padding = $this->with_vertical_padding ? 10 : -(static::DEFAULT_TOP_BOTTOM_PADDING / 2); 281 282 foreach ($this->items as $item) { 283 $color = $this->getColor($item['color'], 0); 284 285 // function name 286 switch ($item['calc_fnc']) { 287 case CALC_FNC_MIN: 288 $fncName = 'min'; 289 $fncRealName = _('min'); 290 break; 291 case CALC_FNC_MAX: 292 $fncName = 'max'; 293 $fncRealName = _('max'); 294 break; 295 case CALC_FNC_LST: 296 $fncName = 'last'; 297 $fncRealName = _('last'); 298 break; 299 case CALC_FNC_AVG: 300 default: 301 $fncName = 'avg'; 302 $fncRealName = _('avg'); 303 } 304 305 if (isset($this->data[$item['itemid']]) 306 && isset($this->data[$item['itemid']][$fncName])) { 307 $dataValue = $this->data[$item['itemid']][$fncName]; 308 $proc = ($this->sum == 0) ? 0 : ($dataValue * 100) / $this->sum; 309 310 $strValue = sprintf(_('Value').': %s ('.(round($proc) != round($proc, 2) ? '%0.2f' : '%0.0f').'%%)', 311 convertUnits([ 312 'value' => $dataValue, 313 'units' => $this->items[$i]['units'] 314 ]), 315 $proc 316 ); 317 318 $str = '['.$fncRealName.']'; 319 } 320 else { 321 $strValue = _('Value: no data'); 322 323 $str = '['._('no data').']'; 324 } 325 326 // item name 327 imageText( 328 $this->im, 329 $fontSize, 330 0, 331 $this->shiftXleft + 15, 332 $this->sizeY + $shiftY + 14 * $i + 5, 333 $this->getColor($this->graphtheme['textcolor'], 0), 334 $displayHostName ? $item['hostname'].': '.$item['name_expanded'] : $item['name_expanded'] 335 ); 336 337 // function name 338 imageText( 339 $this->im, 340 $fontSize, 341 0, 342 $this->shiftXleft + $functionNameXShift + 30, 343 $this->sizeY + $shiftY + 14 * $i + 5, 344 $this->getColor($this->graphtheme['textcolor'], 0), 345 $str 346 ); 347 348 // left square fill 349 imagefilledrectangle( 350 $this->im, 351 $this->shiftXleft, 352 $this->sizeY + $shiftY + 14 * $i - 5, 353 $this->shiftXleft + 10, 354 $this->sizeY + $shiftY + 5 + 14 * $i, 355 $color 356 ); 357 358 // left square frame 359 imagerectangle( 360 $this->im, 361 $this->shiftXleft, 362 $this->sizeY + $shiftY + 14 * $i - 5, 363 $this->shiftXleft + 10, 364 $this->sizeY + $shiftY + 5 + 14 * $i, 365 $this->getColor('Black No Alpha') 366 ); 367 368 $shiftX = $this->fullSizeX - $this->shiftlegendright - $this->shiftXright + 25; 369 370 // right square fill 371 imagefilledrectangle( 372 $this->im, 373 $shiftX - 10, 374 $this->shiftY + $top_padding + 14 * $i, 375 $shiftX, 376 $this->shiftY + $top_padding + 10 + 14 * $i, 377 $color 378 ); 379 380 // right square frame 381 imagerectangle( 382 $this->im, 383 $shiftX - 10, 384 $this->shiftY + $top_padding + 14 * $i, 385 $shiftX, 386 $this->shiftY + $top_padding + 10 + 14 * $i, 387 $this->GetColor('Black No Alpha') 388 ); 389 390 // item value 391 imagetext( 392 $this->im, 393 $fontSize, 394 0, 395 $shiftX + 5, 396 $this->shiftY + $top_padding + 14 * $i + 10, 397 $this->getColor($this->graphtheme['textcolor'], 0), 398 $strValue 399 ); 400 401 $i++; 402 } 403 404 if ($this->sizeY < 120) { 405 return; 406 } 407 } 408 409 protected function drawElementPie($values) { 410 $sum = $this->sum; 411 412 if ($this->background !== false) { 413 $least = 0; 414 foreach ($values as $item => $value) { 415 if ($item != $this->background) { 416 $least += $value; 417 } 418 } 419 $values[$this->background] -= $least; 420 } 421 422 if ($sum <= 0) { 423 $values = [0 => 1]; 424 $sum = 1; 425 $isEmptyData = true; 426 } 427 else { 428 $isEmptyData = false; 429 } 430 431 $sizeX = $this->sizeX; 432 $sizeY = $this->sizeY; 433 434 if ($this->type == GRAPH_TYPE_EXPLODED) { 435 list($sizeX, $sizeY) = $this->calcExplodedRadius($sizeX, $sizeY, count($values)); 436 } 437 438 $xc = $x = (int) $this->sizeX / 2 + $this->shiftXleft; 439 $yc = $y = (int) $this->sizeY / 2 + $this->shiftY; 440 441 $anglestart = 0; 442 $angleend = 0; 443 444 foreach ($values as $item => $value) { 445 if ($value == 0) { 446 continue; 447 } 448 449 $angleend += (int) (360 * $value / $sum) + 1; 450 $angleend = ($angleend > 360) ? 360 : $angleend; 451 452 if (($angleend - $anglestart) < 1) { 453 continue; 454 } 455 456 if ($this->type == GRAPH_TYPE_EXPLODED) { 457 list($x, $y) = $this->calcExplodedCenter($anglestart, $angleend, $xc, $yc, count($values)); 458 } 459 460 imagefilledarc( 461 $this->im, 462 $x, 463 $y, 464 $sizeX, 465 $sizeY, 466 $anglestart, 467 $angleend, 468 $this->getColor((!$isEmptyData ? $this->items[$item]['color'] : 'FFFFFF'), 0), 469 IMG_ARC_PIE 470 ); 471 imagefilledarc( 472 $this->im, 473 $x, 474 $y, 475 $sizeX, 476 $sizeY, 477 $anglestart, 478 $angleend, 479 $this->getColor('Black'), 480 IMG_ARC_PIE | IMG_ARC_EDGED | IMG_ARC_NOFILL 481 ); 482 $anglestart = $angleend; 483 } 484 } 485 486 protected function drawElementPie3D($values) { 487 $sum = $this->sum; 488 489 if ($this->background !== false) { 490 $least = 0; 491 foreach ($values as $item => $value) { 492 if ($item != $this->background) { 493 $least += $value; 494 } 495 } 496 $values[$this->background] -= $least; 497 } 498 499 if ($sum <= 0) { 500 $values = [0 => 1]; 501 $sum = 1; 502 $isEmptyData = true; 503 } 504 else { 505 $isEmptyData = false; 506 } 507 508 $sizeX = $this->sizeX; 509 $sizeY = $this->sizeY; 510 511 $this->exploderad = $this->exploderad3d; 512 513 if ($this->type == GRAPH_TYPE_3D_EXPLODED) { 514 list($sizeX, $sizeY) = $this->calcExplodedRadius($sizeX, $sizeY, count($values)); 515 } 516 517 list($sizeX, $sizeY) = $this->calc3DAngle($sizeX, $sizeY); 518 519 $xc = $x = (int) $this->sizeX / 2 + $this->shiftXleft; 520 $yc = $y = (int) $this->sizeY / 2 + $this->shiftY; 521 522 // bottom angle line 523 $anglestart = 0; 524 $angleend = 0; 525 526 foreach ($values as $item => $value) { 527 if ($value == 0) { 528 continue; 529 } 530 531 $angleend += (int) (360 * $value / $sum) + 1; 532 $angleend = ($angleend > 360) ? 360 : $angleend; 533 534 if (($angleend - $anglestart) < 1) { 535 continue; 536 } 537 538 if ($this->type == GRAPH_TYPE_3D_EXPLODED) { 539 list($x, $y) = $this->calcExplodedCenter($anglestart, $angleend, $xc, $yc, count($values)); 540 } 541 542 imagefilledarc( 543 $this->im, 544 $x, 545 $y + $this->graphheight3d + 1, 546 $sizeX, 547 $sizeY, 548 $anglestart, 549 $angleend, 550 $this->getShadow((!$isEmptyData ? $this->items[$item]['color'] : 'FFFFFF'), 0), 551 IMG_ARC_PIE 552 ); 553 imagefilledarc( 554 $this->im, 555 $x, 556 $y + $this->graphheight3d + 1, 557 $sizeX, 558 $sizeY, 559 $anglestart, 560 $angleend, 561 $this->getColor('Black'), 562 IMG_ARC_PIE | IMG_ARC_EDGED | IMG_ARC_NOFILL 563 ); 564 $anglestart = $angleend; 565 } 566 567 // 3d effect 568 for ($i = $this->graphheight3d; $i > 0; $i--) { 569 $anglestart = 0; 570 $angleend = 0; 571 572 foreach ($values as $item => $value) { 573 if ($value == 0) { 574 continue; 575 } 576 577 $angleend += (int) (360 * $value / $sum) + 1; 578 $angleend = ($angleend > 360) ? 360 : $angleend; 579 580 if (($angleend - $anglestart) < 1) { 581 continue; 582 } 583 elseif ($this->sum == 0) { 584 continue; 585 } 586 587 if ($this->type == GRAPH_TYPE_3D_EXPLODED) { 588 list($x, $y) = $this->calcExplodedCenter($anglestart, $angleend, $xc, $yc, count($values)); 589 } 590 591 imagefilledarc( 592 $this->im, 593 $x, 594 $y + $i, 595 $sizeX, 596 $sizeY, 597 $anglestart, 598 $angleend, 599 $this->getShadow((!$isEmptyData ? $this->items[$item]['color'] : 'FFFFFF'), 0), 600 IMG_ARC_PIE 601 ); 602 $anglestart = $angleend; 603 } 604 } 605 606 $anglestart = 0; 607 $angleend = 0; 608 609 foreach ($values as $item => $value) { 610 if ($value == 0) { 611 continue; 612 } 613 614 $angleend += (int) (360 * $value / $sum) + 1; 615 $angleend = ($angleend > 360) ? 360 : $angleend; 616 617 if (($angleend - $anglestart) < 1) { 618 continue; 619 } 620 621 if ($this->type == GRAPH_TYPE_3D_EXPLODED) { 622 list($x, $y) = $this->calcExplodedCenter($anglestart, $angleend, $xc, $yc, count($values)); 623 } 624 625 imagefilledarc( 626 $this->im, 627 $x, 628 $y, 629 $sizeX, 630 $sizeY, 631 $anglestart, 632 $angleend, 633 $this->getColor((!$isEmptyData ? $this->items[$item]['color'] : 'FFFFFF'), 0), 634 IMG_ARC_PIE 635 ); 636 imagefilledarc( 637 $this->im, 638 $x, 639 $y, 640 $sizeX, 641 $sizeY, 642 $anglestart, 643 $angleend, 644 $this->getColor('Black'), 645 IMG_ARC_PIE | IMG_ARC_EDGED | IMG_ARC_NOFILL 646 ); 647 $anglestart = $angleend; 648 } 649 } 650 651 public function draw() { 652 $debug_mode = CWebUser::getDebugMode(); 653 if ($debug_mode) { 654 $start_time = microtime(true); 655 } 656 set_image_header(); 657 $this->calculateTopPadding(); 658 659 $this->selectData(); 660 if (hasErrorMesssages()) { 661 show_messages(); 662 } 663 664 $this->shiftYLegend = 20; 665 $this->shiftXleft = 10; 666 $this->shiftXright = 0; 667 $this->fullSizeX = $this->sizeX; 668 $this->fullSizeY = $this->sizeY; 669 670 if ($this->sizeX < 300 || $this->sizeY < 200) { 671 $this->showLegend(0); 672 } 673 674 if ($this->drawLegend == 1) { 675 $this->sizeX -= $this->shiftXleft + $this->shiftXright + $this->shiftlegendright; 676 $this->sizeY -= $this->shiftY + $this->shiftYLegend + 14 * $this->num + 8; 677 } 678 elseif ($this->with_vertical_padding) { 679 $this->sizeX -= $this->shiftXleft * 2; 680 $this->sizeY -= $this->shiftY * 2; 681 } 682 683 if (!$this->with_vertical_padding) { 684 if ($this->drawLegend == 1) { 685 // Increase size of graph by sum of: 8px legend font size and 5px legend item bottom shift. 686 $this->sizeY += 13; 687 } 688 else { 689 // Remove y shift if only graph is rendered (no labels, header, vertical paddings). 690 $this->shiftY = 0; 691 } 692 } 693 694 $this->sizeX = min($this->sizeX, $this->sizeY); 695 $this->sizeY = min($this->sizeX, $this->sizeY); 696 697 if ($this->sizeX + $this->shiftXleft > $this->fullSizeX) { 698 $this->sizeX = $this->fullSizeX - $this->shiftXleft - $this->shiftXleft; 699 $this->sizeY = min($this->sizeX, $this->sizeY); 700 } 701 702 $this->calc3dheight($this->sizeY); 703 704 $this->exploderad = (int) $this->sizeX / 100; 705 $this->exploderad3d = (int) $this->sizeX / 60; 706 707 if (function_exists('ImageColorExactAlpha') && function_exists('ImageCreateTrueColor') && @imagecreatetruecolor(1, 1)) { 708 $this->im = imagecreatetruecolor($this->fullSizeX, $this->fullSizeY); 709 } 710 else { 711 $this->im = imagecreate($this->fullSizeX, $this->fullSizeY); 712 } 713 $this->initColors(); 714 $this->drawRectangle(); 715 $this->drawHeader(); 716 717 // for each metric 718 $values = []; 719 for ($i = 0; $i < $this->num; $i++) { 720 $data = &$this->data[$this->items[$i]['itemid']]; 721 722 if (!isset($data)) { 723 continue; 724 } 725 726 switch ($this->items[$i]['calc_fnc']) { 727 case CALC_FNC_MIN: 728 $fncName = 'min'; 729 break; 730 case CALC_FNC_MAX: 731 $fncName = 'max'; 732 break; 733 case CALC_FNC_LST: 734 $fncName = 'last'; 735 break; 736 case CALC_FNC_AVG: 737 default: 738 $fncName = 'avg'; 739 } 740 741 $values[$i] = empty($this->data[$this->items[$i]['itemid']][$fncName]) 742 ? 0 743 : abs($this->data[$this->items[$i]['itemid']][$fncName]); 744 } 745 746 switch ($this->type) { 747 case GRAPH_TYPE_EXPLODED: 748 $this->drawElementPie($values); 749 break; 750 case GRAPH_TYPE_3D: 751 $this->drawElementPie3D($values); 752 break; 753 case GRAPH_TYPE_3D_EXPLODED: 754 $this->drawElementPie3D($values); 755 break; 756 default: 757 $this->drawElementPie($values); 758 } 759 760 if ($this->drawLegend == 1) { 761 $this->drawLegend(); 762 } 763 764 if ($debug_mode) { 765 $str = sprintf('%0.2f', microtime(true) - $start_time); 766 imageText( 767 $this->im, 768 6, 769 90, 770 $this->fullSizeX - 2, 771 $this->fullSizeY - 5, 772 $this->getColor('Gray'), 773 _s('Data from %1$s. Generated in %2$s sec.', $this->dataFrom, $str) 774 ); 775 } 776 777 unset($this->items, $this->data); 778 779 imageOut($this->im); 780 } 781} 782